Compare commits

...
685 Commits
Author SHA1 Message Date
José Valim 243433d9d3 Release v0.7.2 2012-12-04 19:58:32 +01:00
Yurii Rashkovskii 7a69a0335c Fix return value of String.at/2 with negative position out of bounds 2012-12-04 01:43:16 -08:00
Yurii Rashkovskii 16da928df8 Fix String.at/1 documentation example 2012-12-04 01:36:32 -08:00
José Valim 4583162eda Improve docs for mix run 2012-12-03 22:47:27 +01:00
José Valim b20fea7c4f Ensure update_* fields work with record rewriter 2012-12-03 20:51:29 +01:00
José Valim d9029c7f98 Add a test for conflicting record inference 2012-12-03 20:43:39 +01:00
José Valim 362ff69e65 Move more behaviour to Enum 2012-12-03 20:23:22 +01:00
José Valim ba43183f1b Ensure RecordRewriter is no-op for non record fields 2012-12-03 14:48:11 +01:00
Yurii Rashkovskii af959582d7 Decapitalize Erlang-style named variables in type specifications' annotations when converting to AST 2012-12-03 04:11:42 -08:00
Yurii Rashkovskii 46b73173b5 Minor CHANGELOG improvements 2012-12-03 03:27:44 -08:00
José Valim 8b66976c96 Ensure Macro.to_binary work with unary not 2012-12-03 12:24:26 +01:00
José Valim 36e6261164 Update CHANGELOG 2012-12-03 12:08:57 +01:00
José Valim a629fdac0d Document the record access rewrite feature 2012-12-03 11:35:34 +01:00
José Valim 3c7cc9ab84 Merge branch 'jv-tuple-opt' 2012-12-03 11:23:02 +01:00
José Valim c7f67bc52a Rewrite records accessors when possible 2012-12-03 11:19:51 +01:00
Yurii Rashkovskii dffd8c59ad Improve documentation for the /> operator 2012-12-03 01:24:32 -08:00
José Valim fa9a78f0dc Merge pull request #683 from devinus/unicode-strip
Add String.Unicode.{lstrip,rstrip}
2012-12-03 00:06:22 -08:00
José Valim 82cf622e9b Enable record rewriter inference 2012-12-03 00:47:34 +01:00
Devin Torres 5c29d76bca Add String.Unicode.{lstrip,rstrip} 2012-12-02 17:42:32 -06:00
José Valim 088ef2fbe8 Support try/receive in record rewriter 2012-12-03 00:24:09 +01:00
José Valim 498ac849fb Record rewriting works with case clauses 2012-12-02 23:37:53 +01:00
José Valim d4c2436521 Extend reach of record rewriting 2012-12-02 22:33:12 +01:00
Yurii Rashkovskii 0f9b717c03 Improve IEX's s helper implementation 2012-12-02 12:14:47 -08:00
Yurii Rashkovskii 295d275d13 Fix IEX's s helper to correctly handle MF and MFA requests 2012-12-02 12:06:40 -08:00
Yurii Rashkovskii 4c1f4451e2 Type specification to AST conversion should support paren_type 2012-12-02 11:58:58 -08:00
José Valim 0f1584c0fa Initial work on RecordRewriter 2012-12-02 20:58:57 +01:00
Yurii Rashkovskii 0a42d7aa3c IEX's s helper should include callbacks in its output 2012-12-02 11:41:17 -08:00
Yurii Rashkovskii 31dcbfb8de mix compile.app should allow custom keys in .app 2012-12-02 11:01:50 -08:00
José Valim 49fa59d3a9 Improve Typespec stacktraces 2012-12-02 16:13:29 +01:00
José Valim c06a44c2fd Add Macro.extract_args, closes #677 2012-12-02 16:10:46 +01:00
José Valim 4e8fb9915b Validate all app properties and do not accept nil values 2012-12-02 15:18:21 +01:00
Yurii Rashkovskii cf5f57f07a Implement Mix.Project.application values validation
Closes #685

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2012-12-02 14:33:16 +01:00
José Valim fc03c8b43d Get rid of unused compilation mode 2012-12-02 14:32:36 +01:00
José Valim b32f6ef51f Keep variables in a single dictionary
This ensures we give the same treatment to quoted
and non-quoted variables.
2012-12-02 12:22:10 +01:00
José Valim 9b3d851a9c Deprecate record default-based generated functions 2012-12-01 11:14:45 +01:00
José Valim 8df803471c IEx.preload is gone 2012-11-30 17:20:31 +01:00
José Valim d55afa44c6 Ensure new IEx works on Windows 2012-11-30 17:07:12 +01:00
José Valim a061528731 Ensure IEx runs only after Elixir's CLI process its options 2012-11-30 17:07:12 +01:00
Yurii Rashkovskii 9e030a6102 Improve System module type specifications coverage 2012-11-30 05:54:05 -08:00
Yurii Rashkovskii 7c5ce0ce74 Fix System.halt/1 and System.halt/0 type specifications
Dialyzer no longer complains about them.
2012-11-30 05:29:44 -08:00
José Valim 73340a466d Consider zero-arity on Macro.to_binary, closes #682 2012-11-30 14:15:02 +01:00
Yurii Rashkovskii 94cc204e4f Add yrashk to .travis.yml notification recipients 2012-11-30 05:01:37 -08:00
José Valim 2ae2b88ea8 Fix mix executable 2012-11-30 13:56:18 +01:00
José Valim b3d203712a Fix mix iex 2012-11-30 13:49:18 +01:00
Yurii Rashkovskii 486cb79fab Store @specs in the order they were specified in the source code 2012-11-30 04:47:43 -08:00
José Valim fe13173aaf Update lib/iex/lib/iex/helpers.ex 2012-11-30 13:38:14 +01:00
Yurii Rashkovskii d8a070d145 Fix typespec to AST conversion for functions with variable arity (...) 2012-11-30 04:34:05 -08:00
Yurii Rashkovskii 8a6fddd477 Add a note about string(), char_list() and String.t() types 2012-11-30 04:03:00 -08:00
Yurii Rashkovskii 57a09f25eb Fix a bug that prevented from inspecting immediate values in typespecs (such as ...) 2012-11-30 03:59:24 -08:00
Yurii Rashkovskii 3e320fd10c Add IEx helper l/1 2012-11-30 03:48:11 -08:00
Yurii Rashkovskii 97bc7a3727 Mention r/1 helper in IEx help 2012-11-30 03:44:29 -08:00
José Valim 252886c2dc Simplify API exposed in IEx.Helpers as h/1, s/1 and t/1 are more flexible now 2012-11-30 11:29:27 +01:00
Yurii Rashkovskii 909db1170c Add IEx helper flush/0 2012-11-30 02:25:35 -08:00
José Valim 6a68bef35a Improve shelling out on mix, closes #492 2012-11-30 11:22:48 +01:00
Yurii Rashkovskii b04351f625 Add support for "local" function syntax in IEx's s helper
Example:

  s(atom_to_list)
  s(atom_to_list/1)
2012-11-30 02:17:28 -08:00
Yurii Rashkovskii 624f781c07 Catch invalid IEx's h helper invocations 2012-11-30 02:10:48 -08:00
Yurii Rashkovskii 5cd8de0493 Fix indentation 2012-11-30 02:08:03 -08:00
Yurii Rashkovskii 8742673da2 Make h/0 IEx helper work again 2012-11-30 02:04:03 -08:00
Yurii Rashkovskii 2fcbf077d0 Minor IEx helper improvement 2012-11-30 02:03:08 -08:00
Yurii Rashkovskii 2aee6bdf2d Add support for h(function_name) helper in IEx
Allows printing documentation for IEx helpers and Kernel's functions/macros without specifying arity.
2012-11-30 01:53:05 -08:00
Yurii Rashkovskii 51f2530f79 Add support for h(Module.function) helper in IEx
Allows printing documentation for functions/macros without specifying arity
2012-11-30 01:39:58 -08:00
Yurii Rashkovskii 1e2f170e95 Initial piece of documentation on type specification syntax
Closes #681
2012-11-30 01:25:29 -08:00
Yurii Rashkovskii 9ed5936f9a Fix a bug in macro_exported?/3
Previously, it would fail if a macro with multiple arities was defined but the first occurence of it won't match the requested arity.
2012-11-30 00:56:58 -08:00
Yurii Rashkovskii bf5431a105 Minor documentation fix in IEx.Helpers 2012-11-30 00:53:39 -08:00
Yurii Rashkovskii ab8da88aad Improve h/1 IEx helper to handle some cases that were not handled before
For example, this didn't work:

    iex(1)> h function_exported?/3
    ** (UndefinedFunctionError) undefined function: IEx.Helpers.function_exported?/0
2012-11-30 00:49:09 -08:00
Yurii Rashkovskii 670822ce63 IEx's h/1 helper should check IEx.Helper's macros before handing off to Kernel 2012-11-30 00:43:46 -08:00
Yurii Rashkovskii d731e8ca4a Add macro_exported?/3 helper 2012-11-30 00:41:21 -08:00
José Valim a18dbf8704 Update CHANGELOG 2012-11-30 09:35:41 +01:00
José Valim bc53005f98 Add new typespec syntax to defcallback too 2012-11-30 09:35:41 +01:00
José Valim d538bbbd22 Ensure proper stacktrace is shown in macros, closes #676 2012-11-30 09:35:41 +01:00
Yurii Rashkovskii 82716f12cb Add support for s/2 and h/2 helpers
These helpers will print all matching specs and helpers for specified module:name with any arity

Example:

    t(Enum.t)
    s(Enum.all?)
2012-11-30 00:21:10 -08:00
Yurii Rashkovskii f84800a9d3 AST representation for the :elixir.char_list() type should be char_list() as it is possible to use this type directly without specifying :elixir module name 2012-11-30 00:00:22 -08:00
José Valim 3eb509fdd2 Support -> in Macro.to_binary and improve fn printing too 2012-11-30 08:38:25 +01:00
José Valim 71de5f0524 Merge branch 'jv-new-spec' 2012-11-30 08:13:35 +01:00
José Valim c2076c79df Merge pull request #678 from yrashk/jv-new-spec
Change fun(... :: signature) specification format to (... -> signature)
2012-11-29 23:13:01 -08:00
Yurii Rashkovskii f0ab87fc1e Add a more explicit syntax for ((...) -> ...): (fun(...) -> ...) 2012-11-29 20:44:06 -08:00
Yurii Rashkovskii b1cb6801c7 Change fun(... :: signature) specification format to (... -> signature) 2012-11-29 15:53:37 -08:00
José Valim f249eab01e Ensure parenthesis are allowed in call args 2012-11-29 23:58:25 +01:00
José Valim ced5a4386b Support -> inside parenthesis
This is consistent with the fact that do...end
is a direct translation to do: (...).
2012-11-29 23:47:53 +01:00
José Valim e11285007e More work on improved typespec syntax 2012-11-29 14:13:26 +01:00
Yurii Rashkovskii 0db1f7279b Change function type specification syntax (WIP)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2012-11-29 13:34:06 +01:00
José Valim f1b81fd726 Merge pull request #675 from yrashk/code-server-terminate
elixir_code_server should not trap exits and should not log on terminate
2012-11-29 00:18:15 -08:00
Yurii Rashkovskii b8d16dae6f elixir_code_server should not trap exits and should not log on terminate 2012-11-28 18:51:42 -08:00
José Valim b25d5f27d7 Ensure IEx boots fast and with Elixir available, closes #673 2012-11-28 23:19:00 +01:00
José Valim b9e5e490cd Do not add lines to __info__ function 2012-11-28 11:57:57 +01:00
José Valim 956773ee7e Make ExUnit exit happen in two steps.
This allows developers to hook custom mechanisms
after the tests are run but before the system halts.
2012-11-28 11:57:38 +01:00
José Valim 401ad781c3 Ensure debug_info is actually set to true 2012-11-28 09:48:04 +01:00
José Valim 4227d94603 Support \xXX and \x{HEX} escape sequences in strings and char lists
It implements the same behaviour as seen in regexes. Closes #661.
2012-11-27 15:50:45 +01:00
José Valim bf745b352e Make CHANGELOG more consistent 2012-11-27 15:09:12 +01:00
José Valim 3de677607c Deprecated assert left in right in favor of assert left inlist right, closes #662 2012-11-27 15:05:03 +01:00
José Valim f178d999b3 Update CHANGELOG 2012-11-27 14:52:19 +01:00
José Valim bce07f3c27 --debug-info is now true by default 2012-11-27 14:51:10 +01:00
José Valim c7f83ccda3 Merge pull request #665 from yrashk/function-reference-syntax
Add function(Module.fun/arity) and function(fun/arity) syntax
2012-11-27 01:37:29 -08:00
Yurii Rashkovskii e174cbfbfe Add function(Module.fun/arity) and function(fun/arity) syntax
These are equivalent to function(Module, :fun, arity) and function(:fun, arity) constructs, respectively
2012-11-27 01:35:06 -08:00
José Valim 703a7d833f Revert "/u is not required for Elixir regexes"
unicode option is actually required in order to
match \p escape groups.

This reverts commit 0f75b92b25.
2012-11-26 08:36:42 +01:00
José Valim 29f76a106c Merge pull request #660 from carlosmn/mix-dep-fmt
Fix mix dependency format error string
2012-11-25 23:09:12 -08:00
José Valim ba70744f14 Merge pull request #658 from kimshrier/kernel_special_forms_typo
fix some doc typos
2012-11-25 23:07:38 -08:00
Carlos Martín Nieto 54d72121c3 Fix mix dependency format error string
The app name is an atom, but the error message says it should be a
binary, which causes confusion.

Fix that and also add a version without the "requirement" field, which
is optional.
2012-11-26 02:42:56 +01:00
Kim Shrier 1b19c9a7bd fix some doc typos 2012-11-25 15:35:39 -06:00
José Valim 13abbeb1b2 Update lib/elixir/lib/record.ex
Use record field default value as the value
still can be a function/port/etc
2012-11-25 21:22:00 +01:00
José Valim f97b3cea18 Update lib/elixir/lib/record.ex
Use record field value in the exception message
2012-11-25 20:15:32 +01:00
José Valim 9a7bdc158a Speed up native compilation by skipping it in the bootstrap process 2012-11-25 19:09:55 +01:00
José Valim ed0eff4d1d Fix failing erlang tests 2012-11-25 18:25:03 +01:00
José Valim b8f05b2b37 Raise an exception if invalid record field is given, closes #554 2012-11-25 18:13:53 +01:00
José Valim b9600fb6c4 More improvements to typespecs, closes #617 2012-11-25 17:43:56 +01:00
José Valim 5e26cd01b6 Improve error message when invalid token is followed by new line 2012-11-25 17:26:47 +01:00
Yurii Rashkovskii 775803be1a WIP on bounded_fun support
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2012-11-25 17:06:30 +01:00
José Valim 9f16178898 Raise an error if clauses with different arities are defined in the same function, closes #657 2012-11-25 17:01:54 +01:00
José Valim 7ddab53707 Also allow multiclause functions on fn
The function macro is still necessary for function
retrieval and it will be extended when R16 is out
to supported named functions
2012-11-25 00:06:39 +01:00
José Valim 05e694f7e2 Improvements to Regex and String 2012-11-24 21:56:58 +01:00
José Valim 768ede8a1c Merge pull request #656 from l4u/string_strip
String.strip should strip other whitespace characters
2012-11-24 12:13:16 -08:00
Leo Lou 2984771883 Add String.strip/1 2012-11-24 22:59:51 +08:00
Leo Lou 875d1007a9 Strip whitespace characters instead of space in String.rstrip 2012-11-24 22:44:37 +08:00
Leo Lou 1c8993695d Strip whitespace characters instead of space in String.lstrip 2012-11-24 21:07:18 +08:00
José Valim 71837bfbf0 Show warning if a rescue clause can never match, closes #641 2012-11-24 12:55:28 +01:00
José Valim df8a33b16a Show better error message when rebar is not available, closes #647 2012-11-24 12:04:08 +01:00
José Valim 600c8022ba Deprecated Enum.qsort and List.sort in favor of Enum.sort 2012-11-23 21:51:11 +01:00
José Valim 5c967ace04 Add Enum.sort which uses merge sort and performs considerably faster than Enum.qsort 2012-11-23 20:10:26 +01:00
José Valim 87ab504e06 Perfomance improvements to Enum.drop/take 2012-11-23 18:51:10 +01:00
José Valim 36ceb48e21 Do not exit after each run on ExUnit 2012-11-22 10:21:56 +01:00
José Valim 678fafe7c1 Improve IEx by using the -user option (ht @yrashk) 2012-11-22 10:01:57 +01:00
José Valim b5eb2dbd3e Updated rebar with more env variable fixes 2012-11-20 15:44:39 +01:00
José Valim 44bcc00fce Remove tests since utf-8 names on env variables are poorly supported depending on the OS and ERTS 2012-11-20 11:30:53 +01:00
José Valim 2016b4b236 Remove previously deprecated code 2012-11-20 11:17:13 +01:00
José Valim bd67df4d8c CI doesn't like utf-8 chars as env, let's try iso 2012-11-20 11:16:05 +01:00
José Valim 69ba46ec3e Update version to 0.7.2.dev 2012-11-20 11:15:28 +01:00
José Valim 3cd1a7d1ce Ensure we are inside a git repository before retrieve git info, closes #649 2012-11-20 11:10:25 +01:00
José Valim 224f5e47fe Ensure System env functions properly handle utf-8 data 2012-11-20 11:09:53 +01:00
José Valim 0f75b92b25 /u is not required for Elixir regexes 2012-11-20 10:52:57 +01:00
José Valim 7b7b08c49e Update rebar executable with utf-8 env fixes 2012-11-20 10:31:42 +01:00
José Valim 46a293d6f5 Merge pull request #648 from devinus/install-task
Add `make install` task
2012-11-20 01:09:51 -08:00
José Valim 166217a105 Merge pull request #650 from tim/fix-uri-encode
Fix percent encoding algorithm used by URI.encode
2012-11-19 23:51:27 -08:00
Tim Fletcher bddb614927 Fix percent encoding algorithm used by URI.encode 2012-11-20 00:09:59 +00:00
José Valim 80866e3544 Improve docs and style on unicode.ex 2012-11-20 00:15:00 +01:00
Devin Torres 131db13763 Add make install task
To change the install path, simply override the `INSTALL_PATH`:

    $ make INSTALL_PATH=/opt/local install
2012-11-19 11:28:19 -06:00
José Valim 8cede65b0f Release 0.7.1 2012-11-18 20:51:37 +01:00
José Valim bb2e1d5d7c Merge pull request #646 from burningTyger/master
enum doc typo
2012-11-17 02:48:25 -08:00
burningTyger e4f5f9d9ed fix typo in enum docs
RuPy 2012 in talk bug hunt
2012-11-17 11:16:27 +01:00
José Valim e801cbd380 Convert integer to a list in order to generate the module name 2012-11-16 09:50:13 +01:00
José Valim 7894ba0fc1 Add IO.binread, IO.binwrite and friends, closes #645 2012-11-16 09:42:35 +01:00
José Valim ad37d5f67a No need for lists:concat 2012-11-15 16:56:29 +01:00
José Valim 40efeb48d7 Reduce atom footprint when compiling code
Previously, we were generating atom names based on
the file or the module name. This commit replaces
it by a pool of atom names. After a module name
is used and purged with success, it is added back
to the pool so it can be reused many times.
2012-11-15 16:44:27 +01:00
José Valim d03d647560 Bring the same __ENV__ semantics to Module.create 2012-11-15 16:31:26 +01:00
José Valim c3b58eef16 file needs to be a binary 2012-11-15 16:30:55 +01:00
José Valim 32764b3370 Copy all __ENV__ information on eval 2012-11-15 10:01:18 +01:00
José Valim 2104b292c1 Ensure rstrip and strip work on emoty strings, closes #643 2012-11-14 10:34:43 +01:00
José Valim e0bbd47d7b Support R15B 2012-11-13 09:50:28 +01:00
José Valim 743213d8c1 halt was moved to System module
`halt` is a function that is less frequently used than the other
functions in Kernel, not allowed in guard clauses and its outcome
is to shutdown the whole runtime, being a great fit to the System
module.
2012-11-13 09:23:21 +01:00
José Valim 2db5b9c76c Make update no-op if no args are given 2012-11-13 09:23:21 +01:00
José Valim ba6a23a1a4 Merge pull request #640 from clofresh/master
default extracted record vals to :undefined instead of :nil
2012-11-11 23:44:20 -08:00
Carlo Cabanilla b9692fc2fe default record vals to :undefined instead of :nil
When translating erlang records into elixir records, use :undefined
as the default record value since that's what erlang uses, and
presumably you're going to be using these records with erlang apis.
2012-11-12 01:05:17 -05:00
José Valim 8a5f3f3a53 Fix documentation for defrecordp 2012-11-10 21:08:24 +01:00
José Valim c0a42c95e0 Do not print warnings for injected private macros 2012-11-10 12:30:24 +01:00
José Valim 9cad7eb1e2 Use new defrecordp in Regex module 2012-11-10 12:17:52 +01:00
José Valim fcb1a610de Add support for defrecordp 2012-11-10 11:51:13 +01:00
José Valim 04d96f7f18 Update CHANGELOG 2012-11-08 21:20:47 +01:00
José Valim d117dbf04c Filter out compilable files from stale ones in Mix 2012-11-08 21:19:40 +01:00
José Valim ce489eae2b Only allow () under special circunstances 2012-11-08 16:00:16 +01:00
José Valim 02e63e4849 Ensure parenthesis can be used in macros and fn shortcut, closes #634 2012-11-08 15:52:52 +01:00
José Valim cae328112c Fix unecessary double file read 2012-11-08 13:42:25 +01:00
José Valim b2931135d0 Use File.read instead of File.exists? + File.read! 2012-11-08 13:34:47 +01:00
José Valim bddaed1284 Merge pull request #636 from yrashk/exunit-user-config
Add support for ExUnit user configs
2012-11-08 04:30:51 -08:00
José Valim f78433c892 Clean up ExUnit.Formatter 2012-11-08 13:29:45 +01:00
Yurii Rashkovskii f1bbd3f37e Improve mix test helpers with respect to user configs 2012-11-08 03:23:35 -08:00
José Valim b159b1c6bf Merge pull request #637 from elixir-lang/ex_unit_formatter
Flexible formatters for ExUnit
2012-11-08 03:15:49 -08:00
José Valim e6c75bbe98 Merge pull request #632 from yrashk/is_keyword
Add keyword?/1 function
2012-11-08 03:08:58 -08:00
Yurii Rashkovskii c6a012602c Add Keyword.keyword?/1 function 2012-11-08 03:04:55 -08:00
José Valim cc89cf942f Flexible formatters for ExUnit 2012-11-08 12:01:07 +01:00
Yurii Rashkovskii 73d7de7bc9 Add support for ExUnit user configs 2012-11-08 02:49:59 -08:00
José Valim 1ba159ddd7 Merge pull request #635 from yrashk/deps_test-fix
Bring one more check back in mix's deps.git_test
2012-11-07 15:05:49 -08:00
Yurii Rashkovskii 8c3d8fc676 Bring one more check back in mix's deps.git_test 2012-11-07 15:00:51 -08:00
José Valim 8ac3844955 Merge pull request #633 from yrashk/test-kernel-warn-suppress
Suppress an unnecessary (optimization-related) warning in one of the ker...
2012-11-07 07:55:41 -08:00
Yurii Rashkovskii 1b15972d07 Suppress an unnecessary (optimization-related) warning in one of the kernel tests 2012-11-07 06:14:33 -08:00
José Valim 4a2d773e34 Merge pull request #631 from yrashk/defrecord-doc-improvement
Improve defrecord documentation
2012-11-07 05:59:56 -08:00
Yurii Rashkovskii 8185ff7333 Improve defrecord documentation 2012-11-07 05:57:39 -08:00
José Valim 2263d44ccf Merge pull request #630 from yrashk/t-helper-doc-improvement
Improve t helper documentation
2012-11-07 05:05:53 -08:00
Yurii Rashkovskii 46f3696799 Improve t helper documentation 2012-11-07 04:52:30 -08:00
José Valim 9b276f52d9 Update lib/iex/lib/iex/helpers.ex
Fix grammar and improve docs
2012-11-07 13:46:00 +01:00
José Valim 175a2dc1eb Merge pull request #628 from yrashk/t3-helper
Resurrect t/3 iex helper
2012-11-07 04:42:43 -08:00
Yurii Rashkovskii aba27a27e3 Resurrect t/3 iex helper
Closes #627
2012-11-07 04:38:11 -08:00
José Valim 1f56b673bd Support -q when running tests to compile only changed files 2012-11-07 13:04:51 +01:00
José Valim 41dbbbf714 Merge pull request #626 from yrashk/typespec-inspect-improvements-record
Add support for printing record typespecs
2012-11-07 04:01:48 -08:00
Yurii Rashkovskii 1331f23a27 Add support for printing record typespecs 2012-11-07 02:40:45 -08:00
José Valim 52a5f5c9c9 Generate a proper clause var name 2012-11-06 10:19:23 +01:00
José Valim 1ae30a56e2 Do not name quoted variables 2012-11-06 09:54:17 +01:00
José Valim 30bfd4736d Remove Calendar from the upcoming release 2012-11-05 15:19:10 +01:00
José Valim e9334fb7dc Tidy up Typespec API, improve docs 2012-11-05 14:51:51 +01:00
José Valim bd8648d490 Only generate needed clauses in Protocol 2012-11-05 11:20:29 +01:00
José Valim ec68b7dd8a Compile specs and callbacks to @spec and @callback so they are easier to access 2012-11-05 10:26:02 +01:00
José Valim a5539beb37 Use $(RELEASE_FLAG) in Makefile 2012-11-05 09:26:02 +01:00
José Valim 47ce7e3fd8 Actually define types for Macro.Env 2012-11-05 09:21:31 +01:00
José Valim 1113d3cfc9 Add support to record_type in defrecord
Closes #613
Closes #452
2012-11-05 09:14:07 +01:00
José Valim f160f3ee6f Merge remote-tracking branch 'yrashk/record-typespec' into record-typespec 2012-11-05 07:53:36 +01:00
José Valim 1bf06bbc1a Speed up protocols by concatenating modules at compilation time 2012-11-03 10:52:22 +01:00
José Valim 8b981efecd Improve shadowed variable warning 2012-11-03 09:15:46 +01:00
José Valim f95e4512b5 Update CHANGELOG 2012-11-02 10:19:18 +01:00
José Valim 12d14edab5 Deprecate Enum.times, closes #619 2012-11-02 10:17:39 +01:00
José Valim caa50368a7 Check if the first aliases argument is indeed an alias atom 2012-11-02 10:15:19 +01:00
José Valim a144d27886 Warn that function definitions should start with lowercase characters or underscore, closes #620 2012-11-02 10:05:17 +01:00
José Valim 413fcfdd65 Reorganize keyword handling clauses 2012-11-02 10:05:17 +01:00
José Valim 78c6c7201f Keywords are allowed after . identifier, closes #621 2012-11-02 10:05:17 +01:00
José Valim 76cf3e5bba Merge pull request #618 from yrashk/typespec-to_binary-ann_type
Support annotated types in Kernel.Typespec.to_binary
2012-11-01 07:07:51 -07:00
Yurii Rashkovskii 5d2336d23e Support annotated types in Kernel.Typespec.to_binary 2012-11-01 07:05:54 -07:00
José Valim 8280de5fa9 Merge pull request #615 from yrashk/specs-retrieval-fixes
Kernel.Typespec.types/1,3 should recognize typeps and opaques
2012-11-01 06:48:16 -07:00
Yurii Rashkovskii 8cadba5ea4 Kernel.Typespec.types/1,3 should recognize typeps and opaques
Also improves code reuse
2012-11-01 06:37:59 -07:00
José Valim 3750bcc8d1 Remove unused variables 2012-11-01 14:11:10 +01:00
José Valim 3fac73ea17 Merge pull request #609 from yrashk/iex-t
Implement a simple version of the t helper for the interactive shell
2012-11-01 05:23:03 -07:00
José Valim 01f9518899 Keep user's given name when rebinding variables
The following code:

    counter = 1
    counter = 2

Is now rewritten internally to:

    counter   = 1
    counter@1 = 2

Which gives us a couple things:

1. Better debugging
2. We can now show warnings for rebound variables not used

Elixir also keeps `_@` prefix to store internal variables
(for example, `_@1`, `_@MODULE`, etc).

Closes #607.
2012-11-01 12:37:08 +01:00
José Valim 11eb2d6d3c Do not print elixir modules as keywords, closes #603 2012-11-01 10:04:05 +01:00
Yurii Rashkovskii c00381780f Implement a simple version of the t helper for the interactive shell (prints out type/spec information)
Also implements corresponding helper functions in Kernel.Typespec
2012-10-31 22:26:59 -07:00
Yurii Rashkovskii b325e29d23 Record field types definition support 2012-10-31 16:07:13 -07:00
José Valim e2e1747acb Merge pull request #610 from yrashk/fix-typespec-to_binary_tuple_any
Fix Kernel.Typespec.to_binary for tuple() type (with no element type spe...
2012-10-31 08:42:58 -07:00
Yurii Rashkovskii 84501090ce Fix Kernel.Typespec.to_binary for tuple() type (with no element type specified) 2012-10-31 07:26:30 -07:00
José Valim c9dead1fc1 Merge pull request #608 from yrashk/typespec-misc
Typespec misc
2012-10-31 06:33:54 -07:00
Yurii Rashkovskii 4906fac3cf Remove dialyzer warnings from elixir_errors 2012-10-31 06:05:57 -07:00
Yurii Rashkovskii 1c827af937 Remove dialyzer warnings from ExUnit.Assertions 2012-10-31 05:56:38 -07:00
José Valim 06d9b26f94 Merge pull request #606 from yrashk/typespec-dict
Add typespecs for Dict
2012-10-31 05:42:47 -07:00
José Valim aecb1b3716 Merge pull request #605 from yrashk/typespec-enum
Add typespecs for Enum
2012-10-31 05:42:24 -07:00
Yurii Rashkovskii a322635971 Add typespecs for Dict 2012-10-31 05:40:55 -07:00
Yurii Rashkovskii 88fdb20eb0 Add typespecs for Enum
Also fixes some documentation mistakes
2012-10-31 05:29:33 -07:00
José Valim 6d5c2e1d22 Merge pull request #602 from yrashk/typespec-string
Add typespecs for String
2012-10-31 04:59:17 -07:00
José Valim 437ef403c4 Merge pull request #601 from yrashk/typespec-keyword
Add typespecs for Keyword
2012-10-31 04:44:28 -07:00
José Valim d5b6340977 Support (parallel) requires on mix run, closes #495 2012-10-31 12:43:24 +01:00
Yurii Rashkovskii 99befc6715 Add typespecs for Keyword 2012-10-31 04:41:59 -07:00
Yurii Rashkovskii e97e2b310e Add typespecs for String 2012-10-31 04:40:52 -07:00
José Valim d305c96762 Keep arguments order on OptionParser 2012-10-31 12:40:03 +01:00
José Valim e99f623b03 Purge loaded modules on tests 2012-10-31 12:37:18 +01:00
José Valim 562e7e391e Changing deps information on mix.exs forces users to fetch new dependencies
Closes #557
2012-10-31 11:22:09 +01:00
José Valim 69bccec7ae Make SCM formatting more flexible 2012-10-31 10:20:13 +01:00
José Valim e65b18b203 Wrap mix escriptize in Elixir's default CLI setup, closes #560 2012-10-31 10:10:38 +01:00
José Valim 3b33478cc0 Make typespecs compilation happen in two steps, closes #598 2012-10-31 09:44:16 +01:00
José Valim 4d427f9f50 Merge pull request #600 from yrashk/typespecs
Use Behaviour in Dict
2012-10-30 12:59:31 -07:00
Yurii Rashkovskii ea3775ecf0 Use Behaviour in Dict 2012-10-30 07:13:43 -07:00
José Valim 7607051224 Merge Typespec and Kernel.Typespec 2012-10-30 11:47:57 +01:00
José Valim 4fdec2c780 Only define types in protocols and records if one was not defined yet 2012-10-30 11:42:07 +01:00
José Valim 335ba59712 Merge pull request #599 from yrashk/typespec-to_binary
Add Typespec.to_binary/1
2012-10-30 03:41:59 -07:00
Yurii Rashkovskii 84e04b6a8b Add Typespec.to_binary/1 2012-10-30 03:37:44 -07:00
José Valim 06d1b47cc2 Support @opaque 2012-10-30 10:56:53 +01:00
José Valim c21c00d42f Add Kernel.Typespec.defines_type? 2012-10-30 10:48:33 +01:00
José Valim 097a427cb7 Ensure defoverridable functions can be referred in many clauses, closes #595 2012-10-30 10:37:10 +01:00
José Valim 8c317e3d43 Do not check clauses just after non quoted ones, closes #591 2012-10-30 10:13:32 +01:00
José Valim f7490bd5af Provide faster, with docs and debug info first compilation 2012-10-30 10:01:09 +01:00
José Valim 6aec85fae5 Avoid possible race conditions on test suite 2012-10-30 09:17:16 +01:00
José Valim cc22787c65 Do actual matching on = assertions 2012-10-30 09:17:16 +01:00
José Valim c547d70c5f Merge pull request #596 from devinus/dict-force-get
Add get! method to dict behaviour
2012-10-30 01:16:49 -07:00
José Valim a35b3f8ac1 Merge pull request #597 from yrashk/typespec-access
Add support for access macro in typespecs
2012-10-30 01:16:18 -07:00
Yurii Rashkovskii 856cd70d86 Add support for access macro in typespecs 2012-10-29 17:42:14 -07:00
Devin Torres beac96cd55 Add get! method to dict behaviour 2012-10-29 17:17:10 -05:00
José Valim edf34edf2a Merge pull request #593 from yrashk/dialyze-release
Run make dialyze only after .release
2012-10-29 02:06:13 -07:00
José Valim f4daf0a39d Merge pull request #592 from yrashk/char_list_string_t-types
Add String.t() and char_list() types, discourage use of string() because...
2012-10-29 02:04:46 -07:00
Yurii Rashkovskii a67df523ce Run make dialyze only after .release 2012-10-29 02:01:38 -07:00
Yurii Rashkovskii 154e73b0d4 Add String.t() and char_list() types, discourage use of string() because it might lead to confusion.
Note, however, that dialyzer will often talk about string() anyway.

Addresses #588
2012-10-29 01:55:30 -07:00
José Valim 18fd04010e Merge pull request #590 from yrashk/typespec-build-fix
Typespec build fix
2012-10-29 00:40:38 -07:00
Yurii Rashkovskii a6d86d9f44 Protocol compilation happens during the internal phase, therefore @callback and @type are not yet available 2012-10-28 20:01:42 -07:00
Yurii Rashkovskii a2b79e4331 Correct dialyzer option use when adding Elixir to PLT 2012-10-28 19:42:19 -07:00
Yurii Rashkovskii 6a9a3b3efe Reorder core modules so that elixir will compile 2012-10-28 19:31:31 -07:00
José Valim 591b95bfb9 Protocols should define a @callback implementation 2012-10-29 01:38:37 +01:00
José Valim 833a153b33 Avoid dispatching to locals from compiled structures 2012-10-29 00:45:32 +01:00
José Valim fdc2657af8 Deprecate Behaviour.defcallback/1 in favor of the typespecs wrapper, closes #520 2012-10-29 00:04:43 +01:00
José Valim f2a284e268 Use new callbacks on Mix.SCM 2012-10-28 23:08:07 +01:00
José Valim d75fb11dc4 Disable internal as soon as core is fully compiled
This allows us to compile callbacks for the following
modules, which would trigger missing behaviour warnings
otherwise.
2012-10-28 22:33:13 +01:00
José Valim 6edab89372 Add Module.to_binary 2012-10-28 21:56:50 +01:00
José Valim 078da93886 Use new callbacks in URI.Parser 2012-10-28 21:48:53 +01:00
José Valim ee380fe79c Add support to new style behaviours 2012-10-28 21:41:53 +01:00
José Valim ce26312803 Define a type t for records and protocols 2012-10-28 21:29:47 +01:00
José Valim 7c78f22a95 Allow to define a callback from a spec 2012-10-28 21:20:11 +01:00
José Valim 925ab81ca2 Define __info__(:module) instead of __info__(:self) 2012-10-28 21:20:11 +01:00
José Valim 4301d14555 Merge pull request #586 from yrashk/dialyzer-makefile
Add make dialyze target
2012-10-28 02:26:08 -07:00
Yurii Rashkovskii 523170228a Add make dialyze target 2012-10-27 17:02:38 -07:00
José Valim 2b9d67dd9d More dialyzer fixes 2012-10-28 01:36:38 +02:00
José Valim 7eae5a5e8e Avoid negation in unless, simply invert the clauses 2012-10-28 01:15:54 +02:00
José Valim 87391a5832 Implement more returns boolean checks 2012-10-28 01:06:00 +02:00
José Valim b9dbd56f3a Do not take :__exception__ for granted as a record 2012-10-28 00:26:21 +02:00
José Valim 215a28ceed Avoid generating extra clauses for protocols 2012-10-27 23:52:40 +02:00
José Valim f95529b7b3 Many dialyzer fixes 2012-10-27 22:20:15 +02:00
José Valim 21d2fec7aa Filter no auto import list 2012-10-27 21:45:18 +02:00
José Valim 6afb2684c6 Merge pull request #584 from yrashk/kernel-typespec-fix
Typespec fix in Kernel
2012-10-27 12:44:26 -07:00
Yurii Rashkovskii 93f426fcff Typespec fix in Kernel 2012-10-27 12:31:49 -07:00
José Valim 94b4831709 Revert "Work around a dialyzer bug"
The dialyzer bug was caused because orelse clauses was generated
from inside out. For example, the following clause:

    var in [:a, :b, :c]

Was generating:

    ((var == :a orelse var == :b) orelse var == :c)

Instead of:

    var == :a orelse var == :b orelse var == :c

While it isn't clear why the first one caused the dialyzer to
hang, the `in` operator was fixed to output the second in the
previous commit.

This reverts commit 3c15bcc36f.
2012-10-27 21:01:50 +02:00
José Valim b497632540 Invert the order arguments are handled on in 2012-10-27 20:55:51 +02:00
José Valim d1cbefdbe0 No need to special case the compiler_clause anymore 2012-10-27 20:54:37 +02:00
José Valim 2704ba1a07 Convert file to char list, closes #583 2012-10-27 19:57:04 +02:00
José Valim 3c15bcc36f Work around a dialyzer bug 2012-10-27 12:34:33 +02:00
José Valim 40cda34a23 Direct to Binary.Inspect 2012-10-27 12:18:44 +02:00
José Valim 95bea1a4c7 Fix delegations in Process.ex 2012-10-27 11:19:07 +02:00
José Valim 85fce7aaef Fix delegations in Node.ex (ht @yrashk) 2012-10-27 11:15:56 +02:00
José Valim fc76de175c Also add debug_info to Erlang source 2012-10-27 11:04:15 +02:00
José Valim bae135c0ef Use inspect to convert atoms 2012-10-27 10:53:03 +02:00
José Valim e653dece8d Merge pull request #579 from yrashk/macro-to-binary-call-atom
Macro.to_binary should correctly process expressions involving regular a...
2012-10-27 01:47:57 -07:00
José Valim 71368b6590 Compile with --debug-info by default, closes #580 2012-10-27 10:43:23 +02:00
Yurii Rashkovskii 50960e0e88 Macro.to_binary should correctly process expressions involving regular atom module names (e.g. :lists.seq(1,100)) 2012-10-26 15:15:06 -07:00
José Valim 03bb0831e5 Support do blocks on __scope__ special form 2012-10-26 20:37:25 +02:00
José Valim 194647ef5a Only unquote() if unquote is permitted 2012-10-26 18:54:51 +02:00
José Valim 1514ffbabc Refactor the Makefile and recompile kernel by default 2012-10-26 18:32:46 +02:00
José Valim c3da51f32c Use String.printable? instead of Enum.all? on tests
When we first run the tests, docs are not compiled
and therefore Enum.all?/1 and Enum.all?/2 are shown
in the autocompletion results.

We chose String.printable? because it also ends
with a question mark but has only one arity.

Closes #578
2012-10-26 18:12:24 +02:00
José Valim 91b43f244c Also autocomplete when / was already typed in 2012-10-25 14:32:37 +02:00
José Valim fa10442606 Show available arities when function name is complete in autocomplete 2012-10-25 14:08:38 +02:00
José Valim 35fd0bf896 Show docs for default generated functions 2012-10-25 13:36:08 +02:00
José Valim 7a8fa34020 Only show documented functions on autocomplete
Autocompletion is used for discovery by other developers
and we should avoid exposing non documented functions.
2012-10-25 13:36:08 +02:00
José Valim 9a949702f1 Merge pull request #575 from yrashk/patch-3
Minor documentation fix for defdelegate
2012-10-24 14:32:58 -07:00
Yurii Rashkovskii e8ea3e834c Minor documentation fix for defdelegate 2012-10-24 14:15:37 -07:00
José Valim 2f3289aaef Try a different and simpler approach to clauses warnings, closes #567 2012-10-24 16:57:23 +02:00
José Valim 2de1102544 Merge pull request #573 from yrashk/string-at-fix
Out-of-bounds String.at should return nil
2012-10-24 03:59:41 -07:00
Yurii Rashkovskii 445398b7bc Out-of-bounds String.at should return nil 2012-10-24 03:53:42 -07:00
José Valim e2e6e5d0d4 Store overridable funtions defined in before_compile callbacks, closes #572 2012-10-24 11:55:12 +02:00
José Valim d33eff2aec Improve docs for __aliases__ 2012-10-24 11:35:45 +02:00
José Valim 9a2ae1bac7 Rename String.Unicode.sequences to graphemes 2012-10-24 11:20:45 +02:00
José Valim 31e1965ea2 Merge pull request #566 from yrashk/unicode-sequences
Add support for named sequences and make String module aware of it so it...
2012-10-24 01:57:22 -07:00
José Valim dc169dc783 Fix an issue where variables inside clauses remained unassigned 2012-10-23 11:48:34 +02:00
José Valim a617628848 Atoms true and false are also boolean returns 2012-10-23 11:48:34 +02:00
José Valim 431c8739cc Merge pull request #569 from yrashk/patch-2
README: List all applications provided
2012-10-22 01:43:54 -07:00
Yurii Rashkovskii 96c84370e3 README: List all applications provided 2012-10-22 01:32:36 -07:00
Yurii Rashkovskii 4717c9a10b Add support for named sequences and make String module aware of it so it does correct job at calculating length, picking characters, etc. 2012-10-22 01:10:49 -07:00
José Valim 78230df154 Update Makefile 2012-10-22 01:41:07 +03:00
José Valim b1e130cace Merge pull request #563 from yrashk/unicode-makefile
Minor improvements to the unicode compilation step
2012-10-21 15:40:16 -07:00
Yurii Rashkovskii 354be2383d Minor improvements to the unicode compilation step 2012-10-21 15:17:47 -07:00
José Valim 3b63ce2ab1 Merge pull request #562 from yrashk/unicode-version
Add String.Unicode.version/0
2012-10-21 14:55:27 -07:00
José Valim d220618f12 Compare all known compilation types 2012-10-21 23:53:28 +02:00
Yurii Rashkovskii 5c4a5db828 Add String.Unicode.version/0 2012-10-21 14:52:15 -07:00
José Valim 146dddf0b5 Use bin/elixirc (fix the build) 2012-10-21 23:37:43 +02:00
José Valim b2c8669c4c Support String.downcase and String.upcase according to Unicode 6.2.0 2012-10-21 23:35:03 +02:00
José Valim 318454c092 Optimize range match when values are known at compile time 2012-10-21 20:56:47 +02:00
José + Yehuda 0aa4c7227e Rename Mix.SCM behaviour functions 2012-10-21 20:56:17 +02:00
José Valim a17140ea50 Remove unused function 2012-10-21 19:44:13 +02:00
José + Yehuda e03bf4d7d8 Initial work on Calendar.format (based on ICU) 2012-10-21 19:43:11 +02:00
José + Yehuda 85a9d70544 Improvements to Calendar module 2012-10-21 19:00:16 +02:00
José Valim 1cfdb00c4d Improvements to SCM behaviour specs 2012-10-21 18:08:18 +02:00
José Valim b94def32d2 Add package.exs, update RELEASE file 2012-10-20 22:25:19 +02:00
José Valim f438630e82 Remove deprecated branches 2012-10-20 22:06:03 +02:00
José Valim 293044c168 Give more context to h() 2012-10-20 20:24:15 +02:00
José Valim e975336d5d Bump to 0.7.1.dev 2012-10-20 20:18:19 +02:00
José Valim 43b8a26b36 Revert "Remove Calendar from the upcoming release"
This reverts commit 411f1ce55d.
2012-10-20 20:10:59 +02:00
José Valim 26d35eead1 Use Keyword and check for atom key 2012-10-20 20:09:42 +02:00
José Valim 833e9e9783 Release v0.7.0 2012-10-20 19:05:21 +02:00
José Valim 411f1ce55d Remove Calendar from the upcoming release 2012-10-20 18:59:08 +02:00
José Valim 5e6c761b73 Improve wording in CHANGELOG
[ci skip]
2012-10-20 18:17:54 +02:00
José Valim bd3eb2a10f Update CHANGELOG 2012-10-20 15:16:47 +02:00
José Valim ab25aa72b1 Normalize __ENV__ as the last argument, closes #552 2012-10-20 14:58:38 +02:00
José Valim 8304ec0e91 Improve IO docs 2012-10-19 11:58:30 +02:00
José Valim ca0dbe8f46 Merge pull request #555 from elixir-lang/jv-unordered-keywords
Unordered keywords
2012-10-19 02:54:14 -07:00
José Valim 03f30b985b Use Keyword.equal? instead of sorting 2012-10-19 11:36:48 +02:00
José Valim 152911a03d Add Keyword.equal? 2012-10-18 20:51:40 +02:00
José Valim 923eb5d63d Fix Keyword.merge, speed up Keyword.delete 2012-10-18 20:12:48 +02:00
José Valim dc4029d577 Improve docs for IEx 2012-10-18 17:19:13 +02:00
José Valim 90ab4e3160 Merge pull request #551 from yrashk/iex-helper-renaming
Rename d helper to h, and h helper to v/0
2012-10-18 08:11:36 -07:00
Yurii Rashkovskii b757cfcb59 Rename d helper to h, and h helper to v/0 2012-10-18 08:00:16 -07:00
José Valim 3a1d97550c Merge pull request #550 from yrashk/iex-better-help
Slightly better helpers documentation in IEx
2012-10-18 07:43:56 -07:00
Yurii Rashkovskii 9ddfa0bf41 Slightly better helpers documentation in IEx 2012-10-18 07:42:51 -07:00
José Valim 673f27d1a8 unquote_splicing should work with pipes 2012-10-18 16:38:42 +02:00
José Valim 01f6d4b1d9 Improve Code.string_to_ast docs 2012-10-18 15:17:24 +02:00
José Valim 5592b7b264 Merge branch 'existing-atoms' 2012-10-18 15:13:17 +02:00
José Valim 43d3e3eba6 Remove convertion to atom from parser 2012-10-18 15:12:52 +02:00
José Valim 4f3e66cdfd Refactor elixir_tokenizer scope options 2012-10-18 14:46:54 +02:00
Yurii Rashkovskii 435c4615d1 Make tokenizer be able to limit atoms to existing atoms only 2012-10-18 13:10:35 +02:00
José Valim 5e886ffd97 Merge pull request #547 from devinus/binary-to-number
Add binary_to_(integer|float) to (integer|float)_to_binary
2012-10-18 02:18:28 -07:00
Rafael Mendonça França 6cece2c074 Merge pull request #548 from guedes/master
fixing indentation in docs examples
2012-10-17 21:24:32 -07:00
Dickson S. Guedes 7219324f47 fixing examples in kernel 2012-10-17 23:50:34 -03:00
Dickson S. Guedes 8ea024e06b fixing examples in string 2012-10-17 23:48:47 -03:00
Dickson S. Guedes 3f8a0b91e6 fixing examples in file 2012-10-17 23:45:40 -03:00
Dickson S. Guedes eb7b9146be fixing examples in enum 2012-10-17 23:36:00 -03:00
Devin Torres 17b5ede88f Add binary_to_(integer|float) to (integer|float)_to_binary 2012-10-17 16:35:42 -05:00
Yurii Rashkovskii baa28932f9 Keywords are no longer sorted 2012-10-17 00:06:41 -07:00
José Valim 347a9dee7b Merge pull request #543 from devinus/better-float-inspect
Better inspect for floats
2012-10-16 03:13:18 -07:00
Devin Torres ab4c9322cf Better inspect for floats 2012-10-15 16:18:43 -05:00
José Valim 044380871e Deprecate List.zip/2 in favor of Enum.zip/2 2012-10-15 14:51:48 +02:00
José Valim 15c7d8b2af Improve comments and remove docs for Dict impl's functions 2012-10-15 13:08:36 +02:00
José Valim ac188369ac Merge pull request #539 from yrashk/zip-fix
Prevent List.zip([]) and List.unzip([]) from hanging forever
2012-10-15 03:37:15 -07:00
Yurii Rashkovskii 6ca846fb4d Prevent List.zip([]) and List.unzip([]) from hanging forever 2012-10-14 15:18:39 -07:00
José Valim 8477357c33 Merge pull request #538 from yrashk/file-cp-permission
File.cp family should preserve file's mode
2012-10-14 09:56:06 -07:00
José Valim f19b9d7cfd Merge pull request #532 from yrashk/multi-impl
Add support for defining multiple protocol implementations in one go
2012-10-14 09:24:16 -07:00
Yurii Rashkovskii 9fc710abf6 File.cp family should preserve file's mode 2012-10-14 07:19:27 -07:00
José Valim 436f634f86 Merge pull request #536 from elixir-lang/rm-remove-behavior_info-docs
A protocol should not generate documentation to behavior_info
2012-10-14 02:24:48 -07:00
José Valim d55591fbbc Merge pull request #537 from yrashk/mix-app-file
Make mix compile.app task populate description & registered properties
2012-10-14 02:22:43 -07:00
Yurii Rashkovskii 4502af2f85 Make mix compile.app task populate description & registered properties
This is required by systools

Closes #533
2012-10-13 17:39:41 -07:00
Rafael Mendonça França a8349424ba A protocol should not generate documentation to behavior_info 2012-10-13 17:49:28 -03:00
Rafael Mendonça França 42b3157bcd Whitespaces ✂️
[ci skip]
2012-10-13 17:08:09 -03:00
Yurii Rashkovskii 64bdb7a086 Add support for defining multiple protocol implementations in one go
defimpl SomeProtocol, for: [Atom, Number, List] do
    ...
   end
2012-10-12 14:19:17 -07:00
José Valim aa7f7e4fdb Merge pull request #531 from khia/bugs/incorrect_call_to_code_server
Fixing bug introduced in 'Protocols are powered by behaviours...'(11f934...
2012-10-12 11:05:55 -07:00
ILYA Khlopotov 3c1b93f514 Fixing bug introduced in 'Protocols are powered by behaviours...'(11f934b) 2012-10-12 10:53:49 -07:00
José Valim 052d23c90f Merge pull request #530 from devinus/spelling-mistakes
Fix some spelling mistakes in Kernel
2012-10-12 09:25:44 -07:00
Devin Torres a95b1737ad Fix some spelling mistakes in Kernel 2012-10-12 10:38:55 -05:00
José Valim c5e19a32bd Merge pull request #529 from guedes/master
fixing some docs indentation
2012-10-12 01:13:00 -07:00
Dickson S. Guedes c5876b25da fixing docs indentation 2012-10-11 22:46:54 -03:00
José Valim be3c2f5cde Update System.build_info docs 2012-10-11 21:47:39 +02:00
José Valim 4063af6ed9 Merge pull request #527 from yrashk/git-describe
Include the most recent tag into System.build_info
2012-10-11 12:46:37 -07:00
Yurii Rashkovskii 9b4a086f15 Include the most recent tag into the System.build_info tuple 2012-10-11 12:43:02 -07:00
José Valim 601cbdfb3a Allow line in quote to be an expression, closes #525 2012-10-11 21:22:49 +02:00
José Valim 89d31e9306 Get first element from module name 2012-10-11 21:11:36 +02:00
José Valim 0fe7a46d47 Merge pull request #526 from yrashk/record-update
Add support for record.update/2
2012-10-11 12:09:48 -07:00
Yurii Rashkovskii 4a349927be Add support for record.update/2 2012-10-11 12:07:59 -07:00
José Valim 444cec2d24 Support bytes, bits, utf8, utf16 and utf32 2012-10-11 13:58:42 +02:00
José Valim d888fbe340 Optimize Binary.Inspect.Utils.escape 2012-10-11 13:48:55 +02:00
José Valim c977170331 Fix failing erlang test 2012-10-11 13:48:46 +02:00
José Valim cfbdd835fe Optimize protocols when there are no fallbacks 2012-10-11 12:46:12 +02:00
José Valim 2a98788ca3 Solve a::b ambiguity 2012-10-11 12:24:51 +02:00
José Valim 83ca39b6aa If :B is allowed, B: should be too 2012-10-11 06:46:19 +02:00
José Valim 6a45bcfc37 Pass context in setup/teardown callbacks 2012-10-11 06:39:53 +02:00
José Valim 76197dfec6 Quote keywords on inspect 2012-10-11 06:21:16 +02:00
José Valim c13f5a4a7e Fix failing tests 2012-10-11 06:13:54 +02:00
José Valim a04ed73cba Solve inconsistencies in the Dict API 2012-10-11 06:00:21 +02:00
José Valim 48112e349f Rename Macro.term? to Macro.safe_term
The main goal is to return which term is not unsafe.
2012-10-11 05:44:22 +02:00
José Valim 26f9a60256 Use cond instead of nested ifs 2012-10-11 05:36:13 +02:00
José Valim c9ac4176a0 Merge pull request #523 from yrashk/inspect-keyword
Add support for inspecting keywords
2012-10-10 20:32:16 -07:00
Yurii Rashkovskii 9d1fdae915 Add support for inspecting keywords 2012-10-10 17:52:18 -07:00
José Valim 0aa0a404f9 Support unquote_splicing on root 2012-10-10 19:21:37 +02:00
José Valim b4ccac21f4 By default embed elixir in escripts 2012-10-10 18:06:40 +02:00
José Valim 09f9f1faa3 Fix partial application on lists with many elements and tail 2012-10-10 13:45:48 +02:00
José Valim 69e4f6c24c Make function naming consistent across modules 2012-10-10 08:54:38 +02:00
José Valim 4cd14e1334 Allow partial application on literals 2012-10-10 08:44:32 +02:00
José Valim 01678161c4 Merge pull request #521 from yrashk/decreasing-range
Support for decreasing ranges
2012-10-09 13:31:01 -07:00
Yurii Rashkovskii ed89fdb732 Support for decreasing ranges
Closes #518
2012-10-09 13:29:12 -07:00
José Valim 2053aba138 Remove unused calendar functions and warnings 2012-10-09 21:59:55 +02:00
José Valim 98efb89125 Make +:foo behaviour consistent with foo:bar (both still raise a warning though) 2012-10-09 21:56:36 +02:00
José Valim 563ca3fb58 Remove extra parens 2012-10-09 21:34:30 +02:00
Yehuda Katz b2e96d3901 More Calendar implementation:
* from_tuple
* to_tuple
* add
* subtract
2012-10-09 16:21:37 +02:00
José Valim 54224c0a61 Support unary plus and minus in Macro.term? 2012-10-09 07:56:34 +02:00
José Valim 901d00b398 Merge pull request #517 from yrashk/macro-is-term
Add Macro.term?/1
2012-10-08 21:56:42 -07:00
Yurii Rashkovskii c8aab86122 Add Macro.term?/1 2012-10-08 21:01:20 -07:00
José Valim dc8c2059e6 Print environment when dep cannot be found 2012-10-08 21:28:09 +02:00
José Valim 11f934b74a Protocols are powered by behaviours behind the scenes 2012-10-08 20:13:45 +02:00
José Valim 77677d5f90 Use local rebar, closes #515 2012-10-08 19:15:29 +02:00
José Valim ee69cb4f74 Avoid false negatives in protocol dispatch, closes #503 2012-10-08 16:00:27 +02:00
José Valim 1256388109 Improve env related error messages 2012-10-08 14:43:40 +02:00
k1complete c8161be644 add iex reltool.config
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2012-10-07 15:50:50 +02:00
José Valim 3f2a2d0cb6 Merge pull request #514 from yrashk/cp_r_c
cp_r/c test file should be managed in tests' setup/teardown
2012-10-07 06:47:09 -07:00
Yurii Rashkovskii 37b4073435 cp_r/c test file should be managed in tests' setup/teardown
The reason for this is because if it is stored in the repository, reltool can't build elixir's release.
2012-10-07 02:02:24 -07:00
José Valim 95286df0c4 Fix a bug where a partial alias could not be expanded on iex 2012-10-05 18:30:42 -07:00
José Valim 69e36c731c Simplify __scope__ API 2012-10-05 18:26:52 -07:00
José Valim 626ed9532b Erlang. syntax is deprecated in favor of simply using atoms 2012-10-05 17:08:22 -07:00
José Valim 441bf29f16 Remove special case for specs in elixir_module.erl
Previously, since the functions were not concrete
but stubs replaced at compilation time, we had to
special the module definition to not actually include
the specs, which would become compilation errors.

Now we provide a concrete implementation of the
functions and therefore the spec will be available
in the final source. Those implementations are not
actually invoked in practice, since they are still
transformed into erlang ones for performance reasons.
2012-10-05 15:45:29 -07:00
José Valim fac57e78b2 Ensure Kernel.__info__(:functions|:macros) returns the complete set 2012-10-05 15:45:28 -07:00
José Valim 30365b5d4f Merge pull request #507 from yrashk/patch-1
Typo fix
2012-10-05 14:40:19 -07:00
Yurii Rashkovskii ecf4ed56f2 Typo fix 2012-10-05 13:14:43 -07:00
José Valim 4dcff67b47 Unify Regex and String APIs, closes #505 2012-10-04 16:25:04 -07:00
José Valim 4863d054c3 Tidy up CHANGELOG 2012-10-03 23:32:34 -07:00
José Valim e6dba433eb Add Module.create 2012-10-03 23:27:34 -07:00
José Valim adbe6fc3b6 Support <%% and <%# in EEx, closes #287 2012-10-03 21:42:10 -07:00
José Valim a192f7778d use proper offset on record extensions 2012-10-03 17:14:56 -07:00
José Valim 0632e01f44 Use references instead of relying on file names 2012-10-01 23:39:40 -07:00
José Valim 124342552f Support watch_exts 2012-10-01 18:53:26 -07:00
José Valim 001c242e74 Reset internal variable data when assigning binding 2012-10-01 14:24:01 -07:00
José Valim 8a83a7c5d9 Merge pull request #499 from devinus/sigil-tests
Separate sigil tests into their own test case
2012-10-01 13:36:33 -07:00
Devin Torres bdb14b3d2d Separate sigil tests into their own test case 2012-10-01 15:29:34 -05:00
Yehuda Katz 84a5404468 Added initial work on Calendar module 2012-09-29 20:20:08 -07:00
José Valim 10f8d5ddc8 Merge pull request #494 from yrashk/escriptize-fix
Fix escriptize mix task
2012-09-28 23:23:22 -07:00
José Valim 9367ec709e Fix regression in mix 2012-09-28 23:19:07 -07:00
Yurii Rashkovskii 2df7a3cd82 Fix escriptize mix task 2012-09-28 22:24:42 -07:00
José Valim a22530baf9 Simplify import api 2012-09-28 15:48:23 -07:00
José Valim 412e5ea9d1 Clean ebin dir on make clean 2012-09-27 17:06:48 -07:00
José Valim 4859264fe3 Import ignores function/macros starting with underscore, default callbacks to underscore style 2012-09-27 16:00:26 -07:00
José Valim 010faad0ce Improve tests for bitstring macros 2012-09-26 23:30:45 -05:00
José Valim e42840bf81 Allow macros as bitstrings specifiers 2012-09-26 20:43:25 -05:00
José Valim e7e5e5d72a Supports --detached option on command line, closes #487 2012-09-26 19:26:34 -05:00
José Valim 80bc2c9825 Check if tty is actually supported before invoking it, closes #489 2012-09-25 14:06:22 -05:00
José Valim 19065ac43e Simplify protocol handling 2012-09-25 14:06:22 -05:00
José Valim 05eefdfc5c Merge pull request #488 from bellthoven/string_improvements
Properly idented case statement
2012-09-24 08:30:42 -07:00
Gustavo Dutra 7438d4b9f7 Properly idented missing case 2012-09-24 08:50:20 -03:00
José Valim 78d80334c2 Merge pull request #485 from bellthoven/string_improvements
String improvements
2012-09-23 21:55:43 -07:00
Gustavo Dutra 418d590668 Properly identing case statement 2012-09-23 21:40:08 -03:00
Gustavo Dutra 76eef91ff3 Refactoring in order to move the logic to functions signatures 2012-09-23 14:49:31 -03:00
José Valim e9ff6a57da Add nil? macro 2012-09-23 12:09:22 -05:00
José Valim fe788964bd Add optional comma in lists, tuples, bit strings and keywords, closes #479 2012-09-23 11:43:38 -05:00
Rafael Mendonça França cc624348f3 Whitespaces ✂️ [ci skip] 2012-09-21 17:21:18 -03:00
Gustavo Dutra 7ef9edd523 Implemented String.at/2 2012-09-20 13:08:35 -03:00
Gustavo Dutra 8788e82d1c Implemented String.length/1 2012-09-20 13:08:35 -03:00
Gustavo Dutra 8a318256e0 Implemented String.last/1 2012-09-20 13:08:35 -03:00
Gustavo Dutra 909a4eb33a Implemented String.first/1 2012-09-20 13:08:35 -03:00
Gustavo Dutra 984dc056ee Refactored String.codepoints to use String.codepoint which in now the main entry point for utf8 strings 2012-09-20 13:08:35 -03:00
José Valim 5778d25ead Merge pull request #484 from khia/feature/defmacros
Feature/defmacros
2012-09-19 20:32:41 -07:00
ILYA Khlopotov 52d12a35fb Implementation of to_list macro for Record.defmacros 2012-09-19 18:34:18 -07:00
ILYA Khlopotov 602ee253b8 Implementation of to_keywords macro for Record.defmacros 2012-09-19 18:32:38 -07:00
ILYA Khlopotov 503ccae3ab Fixing compilation warning about unused variable. 2012-09-19 18:29:23 -07:00
José Valim 8566cdb7b8 Merge pull request #483 from khia/feature/read_macro_in_records
Feature/read macro in records
2012-09-19 11:52:31 -07:00
ILYA Khlopotov 6d2c2867c4 Get macro implementation for Record.defmacros 2012-09-19 11:20:35 -07:00
ILYA Khlopotov acd12e637e For consistency use find_index in Record. 2012-09-19 11:07:15 -07:00
José Valim fed42dc885 Merge pull request #480 from guedes/implementing_codepoints
Implementing codepoints
2012-09-18 05:12:38 -07:00
José Valim b2037b0fcc Module.read_attribute and Module.add_attribute deprecated
In favor of `Module.get_attribute` and `Module.put_attribute`
which mimics Dict API
2012-09-18 13:51:32 +02:00
José Valim a0255b6ef8 Simplify Enum protocol 2012-09-17 22:32:08 +02:00
Dickson S. Guedes b8019e64af removing some unused code 2012-09-16 22:19:48 -03:00
Dickson S. Guedes e5d000e672 adding more tests 2012-09-16 16:28:23 -03:00
Dickson S. Guedes 802d5ebc00 added some examples 2012-09-16 13:52:43 -03:00
Dickson S. Guedes cd8837752f Merge branch 'master' into implementing_codepoints 2012-09-16 11:53:25 -03:00
Dickson S. Guedes 5932ee6013 supporting more codepoints 2012-09-16 11:52:27 -03:00
José Valim ecb6163e82 Ensure proper order of dependencies 2012-09-15 22:09:22 +02:00
José Valim 7e51b4c1e6 Mix now supports nested dependencies 2012-09-15 21:44:43 +02:00
José Valim 20f1115f50 Enum.split_with -> Enum.split_while 2012-09-15 21:44:43 +02:00
José Valim 1fd9ab6a9a Convert internal API to use the convergences one 2012-09-15 21:44:43 +02:00
José Valim d6edca01c5 Initial work on recursive dependencies 2012-09-15 21:44:43 +02:00
José Valim d472ea98a1 Merge pull request #477 from alco/enum-doc-fix
Improve the docstring for reduce
2012-09-14 09:36:28 -07:00
Alexei Sholik d06313a750 Improve the docstring for reduce 2012-09-14 18:57:57 +03:00
José Valim e386312c95 mix test automatically sets the environment to test unless another is explicitly given 2012-09-14 12:49:56 +02:00
José Valim 211f325631 Improve handling and messages around nested deps 2012-09-14 12:36:56 +02:00
José Valim 804b0f9445 We need to refresh the project configuration in case we change the environment 2012-09-14 12:17:30 +02:00
José Valim be9675491b Runs deps on prod by default (since we don't want to carry dev deps over) 2012-09-14 11:58:18 +02:00
José Valim e86a650d33 Reword slightly text, keep line length 2012-09-14 00:53:05 +02:00
José Valim 65e911e5bc Merge pull request #476 from devinus/exec-in-scripts
Use exec to replace the shell process instead of forking
2012-09-13 15:09:21 -07:00
Devin Torres 8a80ead20c Use exec to replace the shell process instead of forking 2012-09-13 17:04:35 -05:00
José Valim 01182a288a Merge pull request #475 from yrashk/defmodule-fix
defmodule returns module name, binary and the result
2012-09-13 12:44:41 -07:00
José Valim 2779501ae9 Merge pull request #474 from khia/fix/documentation
Updating documantation in regards to renaming of Binary into String.
2012-09-13 12:42:18 -07:00
ILYA Khlopotov fc4d4645c3 Updating documantation in regards to renaming of Binary into String. 2012-09-13 12:35:58 -07:00
Yurii Rashkovskii 9e835246d6 defmodule returns module name, binary and the result
Previously, 58eea96fc2 was
attempting to solve the ambiguity of records and defmodule result.

This solves the problem completely by tagging defmodule results with
the :module atom and it brings the module name back into the tuple
2012-09-13 12:35:21 -07:00
José Valim 670f2a66ad Merge pull request #471 from alco/dict-doc-fixes
Fix examples in dict docs.
2012-09-13 11:04:16 -07:00
Alexei Sholik a2cb5e9a6c Fix examples in dict docs. 2012-09-13 20:54:07 +03:00
José Valim d025e9f149 Fix regression in parser 2012-09-13 14:01:21 +02:00
José Valim 58eea96fc2 defmodule only returns the result and the binary
We avoid returning { module, binary, result } because
it can be mistakenly confused as a record named module.
Before this patch, defrecord Foo, bar: nil, baz: nil
in IEx was returning:

    Foo[bar: <<binary>>, baz: nil]
2012-09-13 13:56:43 +02:00
José Valim 5c4fddefbb Limit output in IEx to 50 items 2012-09-13 13:52:59 +02:00
José Valim 69c198630c Inspect accepts limit and raw as options 2012-09-13 13:23:10 +02:00
José Valim 9fceea3284 GenServer behaviour also defines init, closes #467 2012-09-13 11:30:55 +02:00
José Valim 20ec00e2fb Load records before inspecting them, closes #469 2012-09-13 11:30:55 +02:00
José Valim b8b7d5764e :trim has no effect here 2012-09-13 11:30:55 +02:00
José Valim 27ba6c644b Update inspect on bitstring to use new notation 2012-09-13 11:30:55 +02:00
José Valim e32380ea6c Keep Binary.Inspect (no need to rename it) 2012-09-13 11:30:55 +02:00
José Valim 85755a12e5 Update lib/elixir/lib/behaviour.ex
default_port() does not receive arguments
2012-09-12 20:22:28 +03:00
José Valim b232455377 Clean up mix tmp on make clean 2012-09-12 14:19:38 +02:00
José Valim e4ad1c14e4 Docs for functions without implementation are automatically marked as defcallback 2012-09-12 12:59:22 +02:00
José Valim 718eae73f8 Improve docs 2012-09-12 11:50:43 +02:00
José Valim 2431aa5183 Module.split also accepts binaries 2012-09-12 11:26:22 +02:00
Dickson S. Guedes 93885ec48b initial working on codepoints 2012-09-11 19:59:42 -03:00
José Valim c7a7022bf3 Properly expand other unquote expressions 2012-09-11 22:19:31 +02:00
José Valim 4855daf011 Improve Behaviour docs 2012-09-11 20:30:27 +02:00
José Valim 49ea60ca8f Calculate the stacktrace before calling inspect, closes #457 2012-09-11 19:54:09 +02:00
José Valim 1638cf49d8 Merge pull request #466 from bellthoven/improve_string_module
Implementing String.replace/3, String.replace/4 and String.duplicate/2
2012-09-11 09:52:55 -07:00
Gustavo Dutra f82d45fc02 Fixed typos 2012-09-11 13:47:28 -03:00
Gustavo Dutra 46bfc3f4dc Changed argument var name to better reflect erlang's documentation terminology 2012-09-11 13:15:31 -03:00
Gustavo Dutra 5bb8cca386 Added more tests 2012-09-11 13:13:19 -03:00
Gustavo Dutra a5cebfbd3a Added support for String.duplicate/2 2012-09-11 12:05:59 -03:00
Gustavo Dutra 41e550aadb Added support for String.replace/3 and String.replace/4 2012-09-11 12:05:59 -03:00
José Valim 40652bd879 Merge pull request #465 from joshrotenberg/master
s/keywords lists/keyword lists/
2012-09-11 08:03:29 -07:00
josh rotenberg 03c12b2ede s/keywords list/keyword list/ in docs 2012-09-11 07:45:08 -07:00
José Valim 284093e3a6 Use new bitstring syntax, make rstrip tail recursive 2012-09-11 11:00:25 +02:00
José Valim ede6e604c0 Merge remote-tracking branch 'guedes/string_upcase_downcase_implementation'
Conflicts:
	lib/elixir/lib/string.ex
2012-09-11 10:49:37 +02:00
Dickson S. Guedes e7c1473d7a String improvements
This implements rstrip, lstrip and strip. refs #456
2012-09-10 20:19:12 -03:00
José Valim f5560da26d Docs now use the new on_definition hook 2012-09-10 22:00:25 +02:00
José Valim 6c96c3473a Add support to on_definition hooks, closes #448 2012-09-10 21:50:23 +02:00
José Valim 6ffcd51f1a Make overridable internal function more readable 2012-09-10 20:47:41 +02:00
José Valim 26204441bb Implement new bitstring syntax, closes #421 2012-09-10 13:45:45 +02:00
José Valim c63a07f928 Now we employ consistently zero-based indexes, closes #459 2012-09-10 10:39:21 +02:00
José Valim 76cc6599a9 Improvements to escriptize task 2012-09-10 09:44:12 +02:00
José Valim 51ab06f115 Small improvements to Mix.Utils 2012-09-10 08:44:45 +02:00
José Valim 5ba2f24400 Merge pull request #429 from thomasc/features/mix-escriptize
Add initial escript support to mix
2012-09-09 23:44:23 -07:00
José Valim 1fa253086d Merge pull request #460 from guedes/string_upcase_downcase_implementation
Improvements on String module
2012-09-09 23:12:39 -07:00
José Valim 064d449a0a Merge pull request #449 from bellthoven/support_negative_numbers
Adding support for Enum.split to accept negative numbers
2012-09-09 23:09:44 -07:00
José Valim 0ad79506fe Merge pull request #461 from khia/features/443_exclude_hidden_files_on_mix_compilation
Features/443 exclude hidden files on mix compilation
2012-09-09 23:08:56 -07:00
José Valim 3f162d7b95 Update lib/elixir/lib/string.ex
Fixed wrong example
2012-09-10 09:08:05 +03:00
ILYA Khlopotov a4bf18d024 Ignore hidden files in mix 'test'. 2012-09-09 20:46:08 -07:00
ILYA Khlopotov 06d991df71 Ignore hidden files on mix compilation. 2012-09-09 20:45:06 -07:00
Dickson S. Guedes 2ea812f7da implemented String.(r|l)strip 2012-09-09 23:55:45 -03:00
Dickson S. Guedes 2a14b6e44e hard coding calculation 2012-09-09 22:29:32 -03:00
Dickson S. Guedes 35a6e24020 implementation of upcase and downcase 2012-09-09 19:27:20 -03:00
José Valim aebbf68d73 Binary module is gone, long live String 2012-09-09 23:14:55 +02:00
José Valim 8f962fd739 Ensure we don't try to print <<193>> 2012-09-09 22:48:30 +02:00
José Valim dcae67ee1b Add a String module responsible for handling UTf-8 binaries 2012-09-09 22:24:41 +02:00
José Valim bac9ceae4f Extract Mix.Utils.extract_files 2012-09-09 16:05:00 +02:00
José Valim 0cbf8dc95f Organize changelog entries alphabetically, closes #455 2012-09-08 14:19:36 +02:00
José Valim ce37416cc6 defmodule returns the module binary 2012-09-07 19:19:05 +02:00
José Valim 52d406dac2 Simplify some assertion tests 2012-09-07 12:19:30 +02:00
José Valim d14687b3bc Raw function definition now evaluates all arguments
This applies to def/4, defp/4, defmacro/4, defmacrop/4.
The previous behaviour was accidental and did not properly
evaluate all arguments.
2012-09-07 12:19:03 +02:00
José Valim 32febf9b49 Also add after_spawn support to IEx 2012-09-06 14:04:26 +02:00
José Valim fe381f970e Break IEx into its own application 2012-09-06 13:50:51 +02:00
José Valim 5df46d54ef ExUnit now supports after_spawn callbacks
Such callbacks are invoked after each process is spawned.

This commit also refactors ExUnit.Runner slightly to consume
information from ExUnit.Server gradually instead of retrieving
it all up-front. This small step allows us to have ExUnit
running in the background in the future or to run many suites
at once.
2012-09-06 12:52:46 +02:00
José Valim 6f8bfbaed8 Remove unecessary new line 2012-09-06 12:52:46 +02:00
José Valim 88670c9ea8 Merge pull request #454 from michaelklishin/patch-1
travis-ci.org now provides R15B02
2012-09-05 11:08:32 -07:00
Michael Klishin 0245de8e85 travis-ci.org now provides R15B02 2012-09-05 22:02:36 +04:00
Gustavo Dutra 6887ac7309 Enum.split/2 with negative numbers iterates just once through the collection 2012-09-05 11:41:08 -03:00
José Valim ef4415339e File.iterator should strip new lines 2012-09-05 10:16:03 +02:00
José Valim f4ade44414 Add ensure_loaded? and ensure_compiled? conveniences 2012-09-05 09:45:16 +02:00
José Valim e518c5263e Use default test environment for test task 2012-09-04 22:26:21 +02:00
José Valim b586273baf Remove unused --no-check option 2012-09-04 14:46:50 +02:00
José Valim a4f7c89938 Add a prepare task 2012-09-04 14:15:44 +02:00
José Valim 55d2c137b4 Add tests for working with nested dependencies in mix 2012-09-04 13:37:55 +02:00
José Valim 0dcf8c23d4 Ensure pipe op can start at new line, closes #450 2012-09-04 11:49:04 +02:00
José Valim 7e48450296 Leave it up to tasks to remove code paths 2012-09-03 15:30:32 +02:00
José Valim 0f07aef3b2 No need to pass default value 2012-09-03 15:16:00 +02:00
Gustavo Dutra 2d1de31278 Adding support for Enum.split to accept negative numbers 2012-08-31 13:49:25 -03:00
José Valim be1b4a12af Provide just Module.split/1 for now that returns binaries 2012-08-30 14:29:51 -03:00
José Valim 8ad9bcc04a Update lib/elixir/lib/gen_server/behaviour.ex
Closes #447
2012-08-30 13:45:57 -03:00
José Valim 32bcc1165e Skip deps check when compiling deps 2012-08-30 11:31:23 -03:00
José Valim fa9bf99a13 Use binary for the splitting and split docs 2012-08-30 09:42:50 -03:00
José Valim 8cd66cf336 Merge pull request #444 from khia/features/indexes_in_records
Implementation for Record.__index__
2012-08-30 05:24:53 -07:00
José Valim 83c3f59330 Merge pull request #446 from khia/features/module_split_name
Implementation of Module.split_name
2012-08-30 05:18:31 -07:00
ILYA Khlopotov 567dfd21d7 Implementation of Module.split_name 2012-08-29 23:46:30 -07:00
ILYA Khlopotov f7aa17a2fd Implementation for Record.__index__ 2012-08-29 20:34:32 -07:00
José Valim 98479a61d8 Check if the file really belongs to the compilable set 2012-08-29 18:07:47 -03:00
José Valim 719deda1a4 Allow file names to be passed to compile and test 2012-08-29 17:50:15 -03:00
José Valim cfad689b63 Add compile_exts, add default config to mix 2012-08-29 17:18:57 -03:00
José Valim fec12a6fd1 Show proper messages in case of failures 2012-08-29 15:42:15 -03:00
José Valim 29a66c7393 Only touch directory after all tasks 2012-08-29 10:03:29 -03:00
José Valim 33331799a2 Rename MIXENV to MIX_ENV 2012-08-29 09:37:21 -03:00
José Valim 6a516efc1b Allow mod.unquote(fun)(args) on quote 2012-08-28 16:10:08 -03:00
José Valim e4f018226b Improve mix new docs 2012-08-28 11:15:41 -03:00
José Valim c229d34c03 Fix precedene of @ operator 2012-08-27 19:12:09 -03:00
José Valim cb78831b4e Properly expand access on module attributes 2012-08-27 15:54:43 -03:00
José Valim 7d92b1b8e6 Give apps back in the proper order 2012-08-27 15:40:01 -03:00
José Valim 3505417989 Add -f support to common mix tasks, closes #431 2012-08-27 09:20:55 -03:00
José Valim fd963b16e0 Provide Regex.captures instead of Regex.match 2012-08-26 23:24:50 -03:00
José Valim 89b5e340e9 Merge pull request #434 from khia/features/named_groups_in_regexp
Support for named groups in Regexp.scan and Regexp.run
2012-08-26 18:22:23 -07:00
José Valim 202072321c Add support to mix environments, closes #397 2012-08-26 21:50:41 -03:00
José Valim 1f8562b96d Mix should load paths before running tasks 2012-08-26 18:37:40 -03:00
José Valim e0d585b1d2 Improvements to the access protocol
* Allow @config[:access] to expand to (@config)[:access]

* Allow nil[:access] to work as expected
2012-08-24 13:03:02 -03:00
José Valim b39fd27636 Merge pull request #441 from khia/issues/macro_to_binary_with_lists
Fixing the crash in Macro.to_binary when it receives a list.
2012-08-22 15:57:54 -07:00
ILYA Khlopotov c6629af897 Fixing the crash in Macro.to_binary when it receives a list. I.e. something like (quote do: foo([1, 2, 3])). 2012-08-22 14:41:14 -07:00
José Valim 36896c939b Do not complain if docs are the same 2012-08-22 17:55:58 -03:00
José Valim 996e107073 Merge pull request #440 from manpages/master
[SOLVED] #439
2012-08-22 09:56:00 -07:00
Jonns Mostovoys f345d8ddc0 Added ;'s to the end of the lines where calls to the time'. 2012-08-22 19:03:49 +03:00
José Valim 92e7535972 Mix now exits with status 1 in case of failures 2012-08-21 11:47:25 -03:00
José Valim 68828d2b49 Code.require_file and Code.load_file now expect the full name as argument 2012-08-21 11:21:21 -03:00
José Valim 2a94699d79 Allow files to be unloaded 2012-08-21 10:37:31 -03:00
José Valim 19ca5b6020 Erlang already logs when it really matters 2012-08-20 20:02:34 -03:00
José Valim 8d0ac17d57 Use elixir_ensure_compiled to enable Code.ensure_compiled 2012-08-20 19:26:12 -03:00
José Valim e4d680448f Accept atoms on Mix.Utils.underscore 2012-08-20 18:55:54 -03:00
José Valim 2d1fe4f8a5 handle dots and slashes in underscore and camelize 2012-08-20 18:35:16 -03:00
José Valim cd6aedc8f2 Code.require_file blocks until file is available 2012-08-20 18:14:41 -03:00
José Valim b43cf970ff Add guards to assert_raise to avoid passing a block 2012-08-20 08:33:51 -03:00
José Valim 74a8cc8c90 compile callbacks must be executed on reverse order 2012-08-17 19:16:07 -03:00
José Valim 4df5019b7e Fix build for bootstraping 2012-08-17 16:22:34 -03:00
José Valim 24bc3668a6 Add Enum.count, allow behaviour to define default functions 2012-08-17 16:08:38 -03:00
José Valim 9aa32423b0 Provide some behaviour conveniences 2012-08-17 15:49:00 -03:00
José Valim 6d4f54c4ec Update CHANGELOG 2012-08-17 11:13:58 -03:00
José Valim bc4918ee0d Ensure quoted variables are properly carried in clauses 2012-08-17 11:11:04 -03:00
José Valim bd09818898 Ensure hygiene works with temp vars 2012-08-17 10:48:48 -03:00
José Valim 6abdea44db Use orddicts to store vars, since we rarely have more than 20 vars 2012-08-17 09:32:18 -03:00
José Valim 4dc1431179 Matching ^ inside quotes should work as expected 2012-08-17 08:50:17 -03:00
José Valim 705aa84c5a Fix fake warning when a match happens after a default arg 2012-08-16 14:19:07 -03:00
José Valim 6a0e633353 We no longer need to close io port because we are turning tty off before. This also fix an issue where IEx was hanging when printing to console. 2012-08-16 13:56:49 -03:00
José Valim 2a26eb4e52 Add Record.defmacros 2012-08-15 15:36:34 -03:00
José Valim 7e039f7fcf Allow access protocol to be invoked on the currently being defined record 2012-08-15 14:21:45 -03:00
ILYA Khlopotov 317819319c Support for named groups in Regexp. 2012-08-15 09:19:40 -07:00
José Valim 9b0f00f075 Improvements to the record API 2012-08-15 11:56:38 -03:00
José Valim 2297a06a14 Merge pull request #437 from khia/features/return_option_in_regex
Features/return option in regex
2012-08-15 04:10:09 -07:00
ILYA Khlopotov fdc08d15a9 'return' option is added to Regex.run and Regex.scan 2012-08-15 00:25:15 -07:00
ILYA Khlopotov 66955b18cd Adding test case for Regex.index with list arguments. 2012-08-15 00:23:50 -07:00
José Valim 9259cfc56e Get only specified dependencies 2012-08-12 18:30:38 -03:00
José Valim 4ae3eed03e Add Keyword.update/3 and Keyword.update/4 2012-08-12 12:09:55 -03:00
José Valim 965c5197fa Merge pull request #432 from thomasc/features/dot-elixirrc
Make elixir source $HOME/.elixirrc if it exists
2012-08-10 09:52:19 -07:00
thomasc b0454837d2 Make elixir source $HOME/.elixirrc if it exists 2012-08-10 18:44:33 +02:00
José Valim b5cf14949d Linify def args and guards 2012-08-10 13:20:10 -03:00
José Valim 1c5e43370c Add Module.overridable? to check if a definition is overridable or not 2012-08-10 09:27:17 -03:00
José Valim d1f5682baa Add Process.group_leader 2012-08-09 12:37:20 -03:00
José Valim dc51521927 Bring some behavior from Erlang's shell to iex 2012-08-09 11:59:41 -03:00
José Valim 6c87df4c7a If we are connected to a remote node, print "rem" in iex
This makes it easy to check if we are on a remote node or not.
2012-08-09 10:42:54 -03:00
José Valim 10cf2266fa Add support to function on Enum.OrdIterator 2012-08-09 09:05:29 -03:00
José Valim a00c64681d Add Enum.reverse 2012-08-09 08:53:48 -03:00
José Valim 3cedb8fc25 URI.decoder -> URI.query_decoder 2012-08-08 16:36:40 -03:00
José Valim a4e3ec5f45 Improve Enum implementation for functions, provide a decoder iterator 2012-08-08 16:34:24 -03:00
José Valim dade8f4495 Merge pull request #430 from thomasc/features/access-for-nil
Allow the access protocol on nil, and always return nil
2012-08-08 07:50:41 -07:00
thomasc cc68813523 Allow the access protocol on nil, and always return nil 2012-08-08 16:40:29 +02:00
José Valim fe5adccc29 Fix build: solve dependency graph on bootstrap 2012-08-08 09:35:32 -03:00
José Valim 7c4e7fe077 Allow nested access 2012-08-08 09:23:43 -03:00
José Valim 4112f45c48 Add a Dict binary that converts its keys to binaries on insertion 2012-08-08 08:43:31 -03:00
José Valim 41f08de173 Simplify Dict, speed up some transforms, define access protocol 2012-08-07 19:34:31 -03:00
José Valim e28d19d031 Optimize protocols, improve documentation signature 2012-08-07 19:34:31 -03:00
Rondy Sousa 5203b940d0 Add documentation for Binary.part and Binary.split 2012-08-07 19:27:26 -03:00
Rondy Sousa 015034c6e9 Regex.split accepts 'parts' as an option param 2012-08-07 16:00:18 -03:00
Rondy Sousa 49475ade58 Test when Binary.split doesn't match the given pattern 2012-08-07 16:00:18 -03:00
Rondy Sousa 5ce4be4d5c Binary.split accepts 'global' option in order to match all occurrences 2012-08-07 16:00:18 -03:00
Rondy Sousa 7e9facdc80 Binary.split supports regex 2012-08-07 16:00:18 -03:00
Rondy Sousa ef113e15a0 Add Binary.split 2012-08-07 16:00:18 -03:00
Rondy Sousa 189970a841 Reorganize Binary test in different modules (Literal tests/API tests) 2012-08-07 16:00:18 -03:00
José Valim 8d0b15050e Add support to List.keyreplace and List.keystore 2012-08-07 14:53:19 -03:00
José Valim 0dd1e52429 Add a test case to import with empty except 2012-08-07 14:19:10 -03:00
José Valim 1f2b9d3c14 Merge pull request #428 from thomasc/fix/allow-empty-except-in-import
Allow empty except option in import statements
2012-08-07 10:08:00 -07:00
José Valim cc58c24d76 Compile Range before Binary 2012-08-07 14:01:37 -03:00
Jose+Rondy 9f77dd5b59 Add Binary.part 2012-08-07 13:49:34 -03:00
thomasc a82a1baf23 Fix small error in mix escriptize documentation 2012-08-07 18:44:41 +02:00
thomasc c1d0bca70f Allow finer grained control of included apps in mix escriptize 2012-08-07 18:37:19 +02:00
thomasc d6d1691559 Properly handle dependencies in mix escriptize 2012-08-07 17:45:36 +02:00
thomasc 258d0c0884 Add miminal mix escriptize test 2012-08-07 16:59:55 +02:00
thomasc 2e1136eef3 Allow elixir itself to be built as an escript 2012-08-07 15:34:58 +02:00
thomasc c5a35af3fb Add initial escript support to mix 2012-08-07 13:00:31 +02:00
thomasc 3658693588 Allow empty except option in import statements 2012-08-07 09:32:26 +02:00
José Valim c3ba5aaca8 mix deps.unlock should not complain on unavailable deps 2012-08-06 16:13:46 -03:00
José Valim c2746101ab Expand @attributes in Macro.expand if possible 2012-08-04 13:02:19 -03:00
José Valim 280a1e7b0f Merge pull request #427 from ekosz/update_access_docs
Remove referance to accessing tuples.
2012-08-04 03:56:41 -07:00
Eric Koslow b8e79dc360 Remove referance to accessing tuples. 2012-08-03 10:59:14 -05:00
José Valim 62e375bc71 Fix a bug in Macro.expand that returned false for not expandable aliases 2012-08-02 15:45:15 +02:00
José Valim 92e8ffb658 Update and reformat CHANGELOG 2012-08-02 10:29:01 +02:00
José Valim 3ad0abfc67 Better error messages when invalid options are given to import, alias or require 2012-08-02 10:28:15 +02:00
José Valim 70b977a897 Validates import, alias and require opts 2012-08-02 09:58:19 +02:00
José Valim c9273fa383 Setup a name for the git repo in case the build machine does not have one 2012-08-01 18:46:42 +02:00
José Valim 9e9274d9b2 Update makefile clean 2012-08-01 18:10:37 +02:00
José Valim 55027e3ac3 Update version, remove deprecated APIs 2012-08-01 13:23:35 +02:00
José Valim 2e86b8b5e4 Merge pull request #426 from ekosz/list_uri_parse_fix
Allow URI.parse/1 to take a char_list without throwing an error
2012-08-01 00:39:53 -07:00
José Valim 8972a4c9f1 Update release process 2012-08-01 08:46:14 +02:00
Eric Koslow 48b07eaa3d Use to_binary/1 instead of guard for URI.parse/1 2012-07-31 16:04:35 -05:00
Eric Koslow 947edcba9a Allows URI.parse/1 to take a char_list without throwing an error 2012-07-31 15:37:23 -05:00
245 changed files with 38504 additions and 6030 deletions
+4 -2
View File
@@ -1,5 +1,5 @@
/.eunit/*
/.full
/.release
/lib/*/ebin/*
/lib/*/tmp
/lib/*/test/tmp
@@ -10,4 +10,6 @@
/deps/*
/ebin
/rel/elixir
erl_crash.dump
erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
+3 -1
View File
@@ -1,9 +1,11 @@
language: erlang
script: "make compile && make .full test"
script: "make compile && make .release test"
notifications:
irc: "irc.freenode.org#elixir-lang"
recipients:
- jose.valim@plataformatec.com.br
- yrashk@gmail.com
otp_release:
- R15B02
- R15B01
- R15B
+149 -42
View File
@@ -1,52 +1,159 @@
# v0.7.2 (2012-12-04)
* enhancements
* [CLI] `--debug-info` is now true by default
* [ExUnit] Make ExUnit exit happen in two steps allowing developers to add custom `at_exit` hooks
* [IEx] Many improvements to helpers functions `h/1`, `s/1` and others
* [Kernel] Functions defined with `fn` can now handle many clauses
* [Kernel] Raise an error if clauses with different arities are defined in the same function
* [Kernel] `function` macro now accepts arguments in `M.f/a` and `f/a` formats
* [Macro] Improvements to `Macro.to_binary`
* [Mix] Mix now echoes the output as it comes when executing external commands such as git or rebar
* [Mix] Mix now validates `application` callback's values
* [Record] Record accessors are now optimized and can be up to 6x faster in some cases
* [String] Support `\xXX` and `\x{HEX}` escape sequences in strings, char lists and regexes
* bug fix
* [Bootstrap] Compiling Elixir source no longer fails if environment variables contain utf-8 entries
* [IEx] IEx will now wait for all command line options to be processed before starting
* [Kernel] Ensure proper stacktraces when showing deprecations
* deprecations
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
* [ExUnit] `assert left in right` is deprecated in favor of `assert left inlist right`
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
* [Record] Default-based generated functions are deprecated
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
# v0.7.1 (2012-11-18)
* enhancements
* [IEx] Only show documented functions and also show docs for default generated functions
* [IO] Add `IO.binread`, `IO.binwrite` and `IO.binreadline` to handle raw binary file operations
* [ExUnit] Add support for user configuration at `HOME/.ex_unit.exs`
* [ExUnit] Add support for custom formatters via a well-defined behaviour
* [Kernel] Add support for `defrecordp`
* [Kernel] Improved dialyzer support
* [Kernel] Improved error messages when creating functions with aliases names
* [Mix] Improve SCM behaviour to allow more robust integration
* [Mix] Changing deps information on `mix.exs` forces users to fetch new dependencies
* [Mix] Support (parallel) requires on mix run
* [Mix] Support `-q` when running tests to compile only changed files
* [String] Support `String.downcase` and `String.upcase` according to Unicode 6.2.0
* [String] Add support for graphemes in `String.length`, `String.at` and others
* [Typespec] Support `@opaque` as attribute
* [Typespec] Define a default type `t` for protocols and records
* [Typespec] Add support for the access protocol in typespecs
* bug fix
* [Kernel] Fix an issue where variables inside clauses remained unassigned
* [Kernel] Ensure `defoverridable` functions can be referred in many clauses
* [Kernel] Allow keywords as function names when following a dot (useful when integrating with erlang libraries)
* [File] File is opened by default on binary mode instead of utf-8
* deprecations
* [Behaviour] `defcallback/1` is deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
* [Enum] `Enum.times` is deprecated in favor of using ranges
* [System] `halt` moved to `System` module
# v0.7.0 (2012-10-20)
* enhancements
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
* [Binary] Add a Dict binary that converts its keys to binaries on insertion
* [Binary] Optimize `Binary.Inspect` and improve inspect for floats
* [CLI] Support `--detached` option
* [Code] `Code.string_to_ast` supports `:existing_atoms_only` as an option in order to guarantee no new atoms is generated when parsing the code
* [EEx] Support `<%%` and `<%#` tags
* [ExUnit] Support `after_spawn` callbacks which are invoked after each process is spawned
* [ExUnit] Support context data in `setup_all`, `setup`, `teardown` and `teardown_all` callbacks
* [IEx] Support `after_spawn` callbacks which are invoked after each process is spawned
* [Kernel] Better error messages when invalid options are given to `import`, `alias` or `require`
* [Kernel] Allow partial application on literals, for example: `{ &1, &2 }` to build tuples or `[&1|&2]` to build cons cells
* [Kernel] Added `integer_to_binary` and `binary_to_integer`
* [Kernel] Added `float_to_binary` and `binary_to_float`
* [Kernel] Many improvements to `unquote` and `unquote_splicing`. For example, `unquote(foo).unquote(bar)(args)` is supported and no longer need to be written via `apply`
* [Keyword] Keyword list is no longer ordered according to Erlang terms but the order in which they are specified
* [List] Add `List.keyreplace` and `List.keystore`
* [Macro] Support `Macro.safe_term` which returns `:ok` if an expression does not execute code and is made only of raw data types
* [Mix] Add support for environments - the current environment can be set via `MIX_ENV`
* [Mix] Add support for handling and fetching dependencies' dependencies
* [Module] Support module creation via `Module.create`
* [Range] Support decreasing ranges
* [Record] Improvements to the Record API, added `Record.defmacros`
* [Regex] Add `:return` option to `Regex.run` and `Regex.scan`
* [String] Add a String module responsible for handling UTf-8 binaries
* bug fix
* [File] `File.cp` and `File.cp_r` now preserves the file's mode
* [IEx] Fix a bug where printing to `:stdio` on `IEx` was causing it to hang
* [Macro] Fix a bug where quoted expressions were not behaving the same as their non-quoted counterparts
* [Mix] `mix deps.get [DEPS]` now only gets the specified dependencies
* [Mix] Mix now exits with status 1 in case of failures
* [Protocol] Avoid false positives on protocol dispatch (a bug caused the dispatch to be triggered to an invalid protocol)
* backwards incompatible changes
* [ExUnit] `setup` and `teardown` callbacks now receives the test name as second argument
* [Kernel] Raw function definition with `def/4`, `defp/4`, `defmacro/4`, `defmacrop/4` now evaluates all arguments. The previous behaviour was accidental and did not properly evaluate all arguments
* [Kernel] Change tuple-related (`elem` and `setelem`), Enum functions (`find_index`, `nth!` and `times`) and List functions (List.key*) to zero-index
* deprecations
* [Code] `Code.require_file` and `Code.load_file` now expect the full name as argument
* [Enum] `List.reverse/1` and `List.zip/2` were moved to `Enum`
* [GenServer] Rename `GenServer.Behavior` to `GenServer.Behaviour`
* [Kernel] Bitstring syntax now uses `::` instead of `|`
* [Kernel] `Erlang.` syntax is deprecated in favor of simply using atoms
* [Module] `Module.read_attribute` and `Module.add_attribute` deprecated in favor of `Module.get_attribute` and `Module.put_attribute` which mimics Dict API
# v0.6.0 (2012-08-01)
* incompatible changes
* [Kernel] Compiled files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace;
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`;
* [Module] Removed data functions in favor of unifying the attributes API;
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior;
* [Kernel] loop and recur were removed in favor of recursion with named functions;
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
* [Kernel] loop and recur are removed in favor of recursion with named functions
* [Module] Removed data functions in favor of unifying the attributes API
* deprecations
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access;
* [Module] `Module.add_compile_callback(module, target, callback)` was deprecated in favor of `Module.add_attribute(module, :before_compile, { target, callback })`;
* [Module] `Module.function_defined?` was deprecated in favor of `Module.defines?`;
* [Module] `Module.defined_functions` was deprecated in favor of `Module.definitions_in`;
* [File] `File.read_info` was deprecated in favor of `File.stat`;
* [IO] `IO.print` was deprecated in favor of `IO.write`;
* [Kernel] Deprecated `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`;
* [Kernel] Deprecated `in_guard` in favor of `__CALLER__.in_guard?`;
* [Kernel] `refer` is deprecated in favor of `alias`;
* [ExUnit] Some assertions were deprecated in favor of simply using `assert()`;
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
* [File] `File.read_info` is deprecated in favor of `File.stat`
* [IO] `IO.print` is deprecated in favor of `IO.write`
* [Kernel] Deprecate `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`
* [Kernel] Deprecate `in_guard` in favor of `__CALLER__.in_guard?`
* [Kernel] `refer` is deprecated in favor of `alias`
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, { target, callback })`
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
* enhancements
* [OptionParser] Make OptionParser public, add support to flags and improved switch parsing;
* [Kernel] Operator `!` is now allowed in guard clauses;
* [IEx] IEx now provides autocomplete if the OS supports tty;
* [IEx] IEx now supports remsh;
* [Mix] First Mix public release;
* [Regex] Back references are now properly supported;
* [IEx] Elixir now defaults to compile with documentation and `d` can be used in IEx to print modules and functions documentation;
* [ExUnit] Support setup and teardown callbacks;
* [Kernel] Introduced operator `=~` for regular expression matches;
* [Kernel] Compiled docs now include the function signature;
* [Kernel] `defmodule` do not start a new variable scope, this improves meta-programming capabilities;
* [Range] Added a Range module with support to `in` operator (`x in 1..3`) and iterators;
* [Enum] Enhanced Enum protocol to support `Enum.count`;
* [Module] Added support to `@before_compile` and `@after_compile` callbacks. The first receives the module name while the latter receives the module name and its object code;
* [Kernel] quote special form now supports line and unquote as options;
* [Record] Allow `Record[_: value]` to set a default value to all records fields, as in Erlang;
* [IEx] Functions `c` and `m` are available in IEx to compile and print available module information. Functions `h` and `v` are available to show history and print previous commands values;
* [Enum] Optimized functions when a list is given as collection;
* [System] Added `System.find_executable`
* [Kernel] Document the macro `@` and allow attributes to be read inside functions;
* [IO/File] Many improvements to `File` and `IO` modules;
* [Macro] Added `Macro.expand`, useful for debugging what a macro expands to;
* [Enum] Added `find_index`;
* [Record] Records now provide a `to_keywords` function;
* [Kernel] Added support to the `%R` sigil. The same as `%r`, but without interpolation or escaping. Both implementations were also optimized to generate the regex at compilation time;
* [Kernel] Added `__ENV__` which returns a `Macro.Env` record with information about the compilation environment;
* [Kernel] Added `__CALLER__` inside macros which returns a `Macro.Env` record with information about the calling site;
* [Enum] Enhance Enum protocol to support `Enum.count`
* [Enum] Optimize functions when a list is given as collection
* [Enum] Add `find_index`, `nth!` and others
* [ExUnit] Support setup and teardown callbacks
* [IEx] IEx now provides autocomplete if the OS supports tty
* [IEx] IEx now supports remsh
* [IEx] Elixir now defaults to compile with documentation and `d` can be used in IEx to print modules and functions documentation
* [IEx] Functions `c` and `m` are available in IEx to compile and print available module information. Functions `h` and `v` are available to show history and print previous commands values
* [IO/File] Many improvements to `File` and `IO` modules
* [Kernel] Operator `!` is now allowed in guard clauses
* [Kernel] Introduce operator `=~` for regular expression matches
* [Kernel] Compiled docs now include the function signature
* [Kernel] `defmodule` do not start a new variable scope, this improves meta-programming capabilities
* [Kernel] quote special form now supports line and unquote as options
* [Kernel] Document the macro `@` and allow attributes to be read inside functions
* [Kernel] Add support to the `%R` sigil. The same as `%r`, but without interpolation or escaping. Both implementations were also optimized to generate the regex at compilation time
* [Kernel] Add `__ENV__` which returns a `Macro.Env` record with information about the compilation environment
* [Kernel] Add `__CALLER__` inside macros which returns a `Macro.Env` record with information about the calling site
* [Macro] Add `Macro.expand`, useful for debugging what a macro expands to
* [Mix] First Mix public release
* [Module] Add support to `@before_compile` and `@after_compile` callbacks. The first receives the module name while the latter receives the module name and its object code
* [OptionParser] Make OptionParser public, add support to flags and improved switch parsing
* [Range] Add a Range module with support to `in` operator (`x in 1..3`) and iterators
* [Record] Allow `Record[_: value]` to set a default value to all records fields, as in Erlang
* [Record] Records now provide a `to_keywords` function
* [Regex] Back references are now properly supported
* [System] Add `System.find_executable`
# v0.5.0 (2012-05-24)
+77 -36
View File
@@ -1,31 +1,37 @@
REBAR:=$(shell which rebar || 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
FULLFLAG:=.full
VERSION:=0.6.0
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
INSTALL_PATH := /usr/local
.PHONY: 1
.NOTPARALLEL: compile
#==> Templates
define TASK_TEMPLATE
define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
@ cd lib/$(1) && ../../bin/elixir ../../bin/mix compile.app
@ cd lib/$(1) && ../../bin/mix compile.app
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex) $$(FORCE)
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs"
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
#==> Compilation tasks
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
UNICODE:=lib/elixir/ebin/Elixir-String-Unicode.beam
default: compile
compile: lib/elixir/src/elixir.app.src erlang elixir
@@ -36,68 +42,103 @@ lib/elixir/src/elixir.app.src: src/elixir.app.src
erlang:
@ cd lib/elixir && $(REBAR) compile
# We need to compile only EEx (without the app)
# file so we can compile Mix
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
# file, then mix and then compile eex fully
elixir: kernel unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
kernel: $(KERNEL)
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
@ if [ -f $(KERNEL) ]; then \
echo "==> kernel (compile)"; \
$(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin; \
else \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
@ if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@ echo "==> kernel (compile)";
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
@ rm -rf lib/elixir/ebin/elixir.app
@ cd lib/elixir && $(REBAR) compile
$(eval $(call TASK_TEMPLATE,ex_unit,ExUnit))
$(eval $(call TASK_TEMPLATE,eex,EEx))
$(eval $(call TASK_TEMPLATE,mix,Mix))
unicode: $(UNICODE)
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
@ $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
$(eval $(call APP_TEMPLATE,mix,Mix))
$(eval $(call APP_TEMPLATE,iex,IEx))
install: compile
@ echo "==> elixir (install)"
for dir in lib/*; do \
install -m755 -d $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
install -m644 $$dir/ebin/* $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
done
install -m755 -d $(INSTALL_PATH)/lib/elixir/bin
install -m755 $(filter-out %.bat, $(wildcard bin/*)) $(INSTALL_PATH)/lib/elixir/bin
install -m755 -d $(INSTALL_PATH)/bin
ln -sf $(INSTALL_PATH)/lib/elixir/bin/* $(INSTALL_PATH)/bin
clean:
@ rm -rf .full
@ rm -rf lib/*/ebin
@ 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
#==> Release tasks
$(FULLFLAG): $(wildcard lib/*/ebin/*)
make ELIXIRC_OPTS="--debug-info" FORCE=1
touch $(FULLFLAG)
zip: $(FULLFLAG)
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
$(RELEASE_FLAG): compile
touch $(RELEASE_FLAG)
docs: $(FULLFLAG)
docs: $(RELEASE_FLAG)
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)
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
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: $(FULLFLAG)
release_erl: $(RELEASE_FLAG)
@ rm -rf rel/elixir
@ cd rel && ../rebar generate
#==> Tests tasks
test: test_erlang test_elixir
test_erlang: compile
@ echo "==> elixir (eunit)"
@ mkdir -p lib/elixir/test/ebin
@ $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
@ time $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt
@ time $(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_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 && time ../../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
@ rm -f .dialyzer_plt
@ cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
@ dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
@ echo "==> Dialyzing Elixir..."
@ dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
+4
View File
@@ -30,6 +30,10 @@ If you want to contribute, Elixir code is divided in applications inside the `li
* `ex_unit` - Simple test framework that ships with Elixir;
* `iex` — IEx, Elixir's interactive shell
* `mix` — Elixir's build tool
We usually keep a list of features and bugs [in the issue tracker][2].
# Important links
+12 -6
View File
@@ -4,22 +4,28 @@ This document simply outlines the release process:
1) Remove .dev extension from current versions
2) Run `make clean test` to ensure all tests pass from scratch
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
4) Commit changes above and tag new version on Git
4) Commit changes above and update stable branch
5) Release new docs, update elixir-lang.org
5) Create tag from master branch
6) Push new zip to Elixir's downloads page
6) Release new docs, update elixir-lang.org
7) After release, bump versions and add .dev back
7) Push new zip to Elixir's downloads page
8) Push package to expm
9) After release, bump versions and add .dev back
## 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
* CHANGELOG
* package.exs
+8 -2
View File
@@ -13,6 +13,7 @@ if [ $# -eq 0 ]; then
--name \"name\" Makes and assigns a name to the distributed node
--sname \"name\" Makes and assigns a short name to the distributed node
--remsh \"name\" Connects to a node using a remote shell (with iex)
--detached Starts the Erlang VM detached from console
--no-halt Does not halt the Erlang VM after execution
** Options marked with (*) can be given more than once
@@ -43,6 +44,9 @@ while [ $I -le $# ]; do
-e|-r|-pr|-pa|-pz|--remsh|-S)
S=2
;;
--detached)
ERL="$ERL `echo $PEEK | cut -c 2-`"
;;
--sname|--name)
I=$(expr $I + 1)
eval "VAL=\${$I}"
@@ -63,9 +67,11 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
then
"$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
else
erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
fi
+4 -4
View File
@@ -2,9 +2,9 @@
if [ $# -eq 0 ]; then
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
-o The directory to output compiled files
--no-docs Do not attach documentation with compiled code
--debug-info Attach debug info to compiled modules
-o The directory to output compiled files
--no-docs Do not attach documentation to compiled modules
--no-debug-info Do not attach debug info to compiled modules
--ignore-module-conflict
** Options given after -- are passed down to the executed code
@@ -24,4 +24,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
"$SCRIPT_PATH"/elixir --compile "$@"
exec "$SCRIPT_PATH"/elixir --compile "$@"
+4 -7
View File
@@ -5,14 +5,11 @@ if "%*" == "" (
goto run
)
:documentation
echo Usage: %~nx0 [switches] [.ex files]
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
echo.
echo -v Prints version and exit
echo -o The directory to output compiled files
echo -pa path Prepend the given path to Erlang code path (*)
echo -pz path Append the given path to Erlang code path (*)
echo --no-docs Do not attach documentation with compiled code
echo --debug-info Attach debug info to compiled modules
echo -o The directory to output compiled files
echo --no-docs Do not attach documentation to compiled modules
echo --no-debug-info Do not attach debug info to compiled modules
echo --ignore-module-conflict
echo.
echo ** Options marked with (*) can be given more than once
+1 -1
View File
@@ -11,4 +11,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
"$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" --no-halt -e "IEx.cli" %*
call "%~dp0\elixir.bat" --no-halt -e "IEx.start" %*
+17 -3
View File
@@ -1,3 +1,17 @@
#!/usr/bin/env elixir
Mix.start
Mix.CLI.run
#!/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" -- "$@"
+9 -1
View File
@@ -1,2 +1,10 @@
@echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %*
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" -- %*
+2
View File
@@ -41,6 +41,8 @@ defmodule EEx do
<% Elixir expression - inline with output %>
<%= Elixir expression - replace with result %>
<%% EEx quotation - returns the contents inside %>
<%# Comments - they are discarded from source %>
All expressions that output something to the template
**must** use the equals sign (`=`). Since everything in
+4 -4
View File
@@ -23,7 +23,7 @@ defmodule EEx.Compiler do
end
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
expr = maybe_block Erlang.elixir_translator.forms(chars, line, state.file)
expr = maybe_block :elixir_translator.forms!(chars, line, state.file, [])
buffer = state.engine.handle_expr(buffer, mark, expr)
generate_buffer(t, buffer, scope, state)
end
@@ -41,7 +41,7 @@ defmodule EEx.Compiler do
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
tuples = maybe_block Erlang.elixir_translator.forms(wrapped, state.start_line, state.file)
tuples = maybe_block :elixir_translator.forms!(wrapped, state.start_line, state.file, [])
buffer = insert_quotes(tuples, state.dict)
{ buffer, t }
end
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
else
key = length(state.dict)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
{ current ++ placeholder ++ new_lines ++ chars, state.prepend_dict([{key, buffer}]) }
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict([{key, buffer}|&1]) }
end
end
@@ -95,7 +95,7 @@ defmodule EEx.Compiler do
# Changes placeholder to real expression
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
{ ^key, value } = List.keyfind dict, key, 1
{ ^key, value } = List.keyfind dict, key, 0
value
end
+2 -3
View File
@@ -37,9 +37,8 @@ defmodule EEx.Engine do
"""
def handle_expr(buffer, '=', expr) do
quote do
tmp_1 = unquote(buffer)
tmp_2 = to_binary(unquote(expr))
tmp_1 <> tmp_2
tmp = unquote(buffer)
tmp <> to_binary(unquote(expr))
end
end
+22 -14
View File
@@ -16,16 +16,26 @@ defmodule EEx.Tokenizer do
end
def tokenize(list, line) do
List.reverse(tokenize(list, line, line, [], []))
Enum.reverse(tokenize(list, line, line, [], []))
end
defp tokenize([?<,?%|t], current_line, line, buffer, acc) do
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
{ buffer, new_line, rest } = tokenize_expr t, line, [?%,?<|buffer]
tokenize rest, current_line, new_line, [?>,?%|buffer], acc
end
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
{ _, new_line, rest } = tokenize_expr t, line, []
tokenize rest, current_line, new_line, buffer, acc
end
defp tokenize('<%' ++ t, current_line, line, buffer, acc) do
{ marker, t } = retrieve_marker(t)
{ expr, new_line, rest } = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(current_line, buffer, acc)
final = { token, line, marker, List.reverse(expr) }
final = { token, line, marker, Enum.reverse(expr) }
tokenize rest, new_line, new_line, [], [final | acc]
end
@@ -65,8 +75,6 @@ defmodule EEx.Tokenizer do
tokenize_expr t, line, [h|buffer]
end
# Raise an error if the expected token is not found
defp tokenize_expr([], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>"
end
@@ -87,16 +95,16 @@ defmodule EEx.Tokenizer do
end
defp token_name('>-' ++ rest) do
rest = List.reverse(rest)
rest = Enum.reverse(rest)
# Tokenize the remaining passing "__internal__" as file,
# which relax the tokenizer to not error on unmatched
# pairs. Then, we check if there is a "fn" token and,
# if so, it is not followed by an "end" token. If this
# is the case, we are on a start expr.
case :elixir_tokenizer.tokenize(rest, 1, "__internal__") do
# Tokenize the remaining passing check_terminators as
# false, which relax the tokenizer to not error on
# unmatched pairs. Then, we check if there is a "fn"
# token and, if so, it is not followed by an "end"
# token. If this is the case, we are on a start expr.
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
{ :ok, tokens } ->
tokens = List.reverse(tokens)
tokens = Enum.reverse(tokens)
fn_index = fn_index(tokens)
if fn_index && end_index(tokens) > fn_index do
@@ -146,6 +154,6 @@ defmodule EEx.Tokenizer do
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, list_to_binary(List.reverse(buffer)) } | acc]
[{ :text, line, list_to_binary(Enum.reverse(buffer)) } | acc]
end
end
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule EEx.SmartEngineTest do
use ExUnit.Case, async: true
+25 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule EEx.TokenizerTest do
use ExUnit.Case, async: true
@@ -41,6 +41,30 @@ baz %>
]
end
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
{ :text, 1, "foo <% true %>" }
]
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
{ :text, 1, "foo <% true do %>bar<% end %>" }
]
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
{ :text, 1, "foo " }
]
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
{ :text, 1, "foo bar" }
]
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
{ :text, 1, "foo " },
+2 -2
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
require EEx
@@ -33,7 +33,7 @@ defmodule EExText.Compiled do
defp fill_in_stacktrace do
try do
Erlang.erlang.error "failed"
:erlang.error "failed"
catch
:error, _, stack -> stack
end
-1
View File
@@ -1,2 +1 @@
# Configure ExUnit, no options supported yet.
ExUnit.start []
+10 -13
View File
@@ -14,23 +14,20 @@
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
macro=[], %% a stack with macros nesting
module=nil, %% the current module
function=nil, %% the current function
recur=nil, %% the current loop function to be recurred
vars=dict:new(), %% a dict of defined variables and their alias
temp_vars=dict:new(), %% a dict of all variables defined in a particular assign
clause_vars=dict:new(), %% a dict of all variables defined in a particular clause
quote_vars=dict:new(), %% a dict of all quoted variables
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=0, %% a counter for the variables defined
file=(<<"nofile">>), %% the current scope filename
counter=[], %% a counter for the variables defined
local=nil, %% the scope to evaluate local functions against
aliases=[], %% an orddict with aliases by new -> old names
requires=elixir_dispatch:default_requires(), %% a set with modules required
macros=elixir_dispatch:default_macros(), %% a list with macros imported by module
functions=elixir_dispatch:default_functions(), %% a list with functions imported by module
scheduled=[]}). %% scheduled modules to be loaded
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,
+8 -10
View File
@@ -5,11 +5,10 @@ defprotocol Access do
The Access protocol is the underlying protocol invoked
when the brackets syntax is used. For instance, `foo[bar]`
is translated to `access foo, bar` which, by default,
invokes `Access.access` protocol.
invokes the `Access.access` protocol.
This protocol is limited and is implemented only for the
following built-in types: keywords, tuples, atoms and
functions.
following built-in types: keywords, records and functions.
"""
@only [List, Function, Record, Atom]
@@ -17,12 +16,12 @@ defprotocol Access do
@doc """
Receives the element being accessed and the access item.
"""
def access(element, qualifier)
def access(container, key)
end
defimpl Access, for: List do
@doc """
Access the given key in a keywords list.
Access the given key in a keyword list.
## Examples
@@ -32,13 +31,9 @@ defimpl Access, for: List do
"""
def access(list, atom) when is_atom(atom) do
atom_access(list, atom)
Keyword.get(list, atom)
end
defp atom_access([{k, _}|_], key) when key < k, do: nil
defp atom_access([{k, _}|d], key) when key > k, do: atom_access(d, key)
defp atom_access([{_k, value}|_], _key), do: value
defp atom_access([], _), do: nil
end
defimpl Access, for: Atom do
@@ -47,6 +42,9 @@ defimpl Access, for: Atom do
at compilation time. If we reach this, we should raise
an exception.
"""
def access(nil, _) do
nil
end
def access(atom, _) do
raise "The access protocol can only be invoked for atoms at " <>
"compilation time, tried to invoke it for #{inspect atom}"
+121
View File
@@ -0,0 +1,121 @@
defmodule Behaviour do
@moduledoc """
A convenience module for defining behaviours.
Behaviours can be referenced by other modules
in order to ensure they implement the proper
callbacks.
For example, you can specify the `URI.Parser`
behaviour as follow:
defmodule URI.Parser do
use Behaviour
@doc "Parses the given URL"
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
@doc "Defines a default port"
defcallback default_port() :: integer
end
And then a specific module may use it as:
defmodule URI.HTTP do
@behaviour URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
In case the behaviour changes or URI.HTTP does
not implement one of the callbacks, a warning
will be raised.
## Implementation
Behaviours since Erlang R15 must be defined via
`@callback` attributes. `defcallback` is a simple
mechanism that defines the `@callback` attribute
according to the type specification and also allows
docs and defines a custom function signature.
The callbacks and their documentation can be retrieved
via the `__behaviour__` callback function.
"""
@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
defmacro defcallback(fun) do
do_defcallback(fun, quote(do: term), __CALLER__)
end
defp do_defcallback(fun, return, caller) do
case Macro.extract_args(fun) do
{ name, args } -> :ok
:error ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_binary(fun)}"
end
arity = length(args)
Enum.each args, fn
{ :::, _, [left, right] } ->
ensure_not_default(left)
ensure_not_default(right)
left
other ->
ensure_not_default(other)
other
end
quote do
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(name), unquote(arity)
end
end
defp ensure_not_default({ ://, _, [_, _] }) do
raise ArgumentError, message: "default arguments // not supported in defcallback"
end
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :__behaviour_docs, { { name, arity }, line, doc }
end
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :__behaviour_docs, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
end
end
@doc false
defmacro __before_compile__(_) do
quote do
@doc false
def __behaviour__(:callbacks) do
__MODULE__.behaviour_info(:callbacks)
end
def __behaviour__(:docs) do
@__behaviour_docs
end
end
end
end
-218
View File
@@ -1,218 +0,0 @@
defmodule Binary do
@moduledoc """
Functions for working with binaries.
"""
@doc %B"""
Receives a char list and escapes all special chars (like \n)
and interpolation markers. A last argument is given and wraps
the whole char list given.
## Examples
Binary.escape "foo", ?'
#=> "'foo'"
"""
def escape(other, char) do
<<char>> <> do_escape(other, char)
end
@doc """
Check if a binary is printable considering it is encoded
as UTF-8. Returns true if so, false otherwise.
## Examples
Binary.printable?("abc") #=> true
"""
# 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
printable?(t)
end
def printable?(<<?\n, t|:binary>>), do: printable?(t)
def printable?(<<?\r, t|:binary>>), do: printable?(t)
def printable?(<<?\t, t|:binary>>), do: printable?(t)
def printable?(<<?\v, t|:binary>>), do: printable?(t)
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?(<<>>), do: true
def printable?(_), do: false
@doc %B"""
Unescape the given chars. The unescaping is driven by the same
rules as single- and double-quoted strings. Check `unescape/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.
## Examples
Binary.unescape "example\\n"
#=> "example\n"
In the example above, we pass a string with `\n` escaped
and we return a version with it unescaped.
"""
def unescape(chars) do
Erlang.elixir_interpolation.unescape_chars(chars)
end
@doc %B"""
Unescape the given chars according to the map given.
Check `unescape/1` if you want to use the same map as Elixir
single- and double-quoted strings.
## Map
The map must be a function. The function receives an integer
representing the number of the characters it wants to unescape.
Here is the default mapping function implemented by Elixir:
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
def unescape_map(?e), do: ?\e
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(e), do: e
If the `unescape_map` function returns false. The char is
not escaped and `\` is kept in the char list.
## Octals
Octals will by default be escaped unless the map function
returns false for ?0.
## Examples
Using the unescape_map defined above is easy:
Binary.unescape "example\\n", unescape_map(&1)
"""
def unescape(chars, map) do
Erlang.elixir_interpolation.unescape_chars(chars, map)
end
@doc """
Unescape the given tokens according to the default map.
Check `unescape/1` and `unescape/2` for more information
about unescaping. Only tokens that are char lists are
unescaped, all others are ignored. This method is useful
when implementing your own sigils. Check the implementation
of `Kernel.__b__` for examples.
"""
def unescape_tokens(tokens) do
Erlang.elixir_interpolation.unescape_tokens(tokens)
end
@doc """
Unescape the given tokens according to the given map.
Check `unescape_tokens/1` and `unescaped/2` for more information.
"""
def unescape_tokens(tokens, map) do
Erlang.elixir_interpolation.unescape_tokens(tokens, map)
end
## Helpers
defp do_escape(<<char, t|:binary>>, char) do
<<?\\, char, do_escape(t, char)|:binary>>
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
<<?\\, escape_map(h), do_escape(t, char)|:binary>>
end
defp do_escape(<<h, t|:binary>>, char) do
<<h, do_escape(t,char)|:binary>>
end
defp do_escape(<<>>, char) do
<<char>>
end
defp escape_map(?#), do: ?#
defp escape_map(?\b), do: ?b
defp escape_map(?\d), do: ?d
defp escape_map(?\e), do: ?e
defp escape_map(?\f), do: ?f
defp escape_map(?\n), do: ?n
defp escape_map(?\r), do: ?r
defp escape_map(?\\), do: ?\\
defp escape_map(?\t), do: ?t
defp escape_map(?\v), do: ?v
end
+88
View File
@@ -0,0 +1,88 @@
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
+232 -101
View File
@@ -3,22 +3,103 @@ import Kernel, except: [inspect: 1]
defprotocol Binary.Inspect do
@moduledoc """
The `Binary.Inspect` protocol is responsible for
converting any structure to a Binary for textual
representation. All basic data structures (tuple,
list, function, pid, etc) implement the inspect
protocol. Other structures are adviced to implement
the protocol in order to provide pretty printing.
converting any structure to a binary for textual
representation. All basic data structures
(tuple, list, function, pid, etc) implement the
inspect protocol. Other structures are advised to
implement the protocol in order to provide pretty
printing.
"""
@only [BitString, List, Record, Tuple, Atom, Number, Any]
@only [BitString, List, Tuple, Atom, Number, Any]
def inspect(thing)
def inspect(thing, opts)
end
defmodule Binary.Inspect.Utils do
@moduledoc false
## container_join
def container_join(tuple, first, last, opts) when is_tuple(tuple) do
container_join(tuple_to_list(tuple), first, last, opts)
end
def container_join(list, first, last, opts) do
first <> do_container_join(list, opts, Keyword.get(opts, :limit, :infinity)) <> last
end
defp do_container_join(_, _opts, 0) do
"..."
end
defp do_container_join([h], opts, _counter) do
Binary.Inspect.inspect(h, opts)
end
defp do_container_join([h|t], opts, counter) when is_list(t) do
Binary.Inspect.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
end
defp do_container_join([h|t], opts, _counter) do
Binary.Inspect.inspect(h, opts) <> "|" <> Binary.Inspect.inspect(t, opts)
end
defp do_container_join([], _opts, _counter) do
""
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
## escape
# It is considerably faster to loop the binary
# and convert it to a list as we go compared
# to looping the binary and creating a binary
# as we go.
def escape(other, char) do
list_to_binary [char|do_escape(other, char)]
end
defp do_escape(<<char, t :: binary>>, char) do
[?\\, char | do_escape(t, char)]
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
[?\\, escape_map(h) | do_escape(t, char)]
end
defp do_escape(<<h, t :: binary>>, char) do
[h | do_escape(t,char)]
end
defp do_escape(<<>>, char) do
[char]
end
defp escape_map(?#), do: ?#
defp escape_map(?\b), do: ?b
defp escape_map(?\d), do: ?d
defp escape_map(?\e), do: ?e
defp escape_map(?\f), do: ?f
defp escape_map(?\n), do: ?n
defp escape_map(?\r), do: ?r
defp escape_map(?\\), do: ?\\
defp escape_map(?\t), do: ?t
defp escape_map(?\v), do: ?v
end
defimpl Binary.Inspect, for: Atom do
require Macro
import Binary.Inspect.Utils
@doc """
@moduledoc """
Represents the atom as an Elixir term. The atoms false, true
and nil are simply quoted. Modules are properly represented
as modules using the dot notation.
@@ -33,40 +114,37 @@ defimpl Binary.Inspect, for: Atom do
inspect(Foo.Bar) #=> "Foo.Bar"
"""
def inspect(false), do: "false"
def inspect(true), do: "true"
def inspect(nil), do: "nil"
def inspect(:""), do: ":\"\""
def inspect(Elixir), do: "Elixir"
def inspect(atom) do
def inspect(false, _), do: "false"
def inspect(true, _), do: "true"
def inspect(nil, _), do: "nil"
def inspect(:"", _), do: ":\"\""
def inspect(Elixir, _), do: "Elixir"
def inspect(atom, _) do
binary = atom_to_binary(atom)
cond do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
"Elixir-" <> rest = binary
bc <<r>> inbits rest, do: <<to_dot(r)>>
Module.to_binary(atom)
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
":" <> Binary.escape(binary, ?")
":" <> escape(binary, ?")
end
end
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
defp to_dot(?-), do: ?.
defp to_dot(l), do: l
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?-, h, t|:binary>>) when h in ?A..?Z do
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
@@ -75,7 +153,7 @@ defimpl Binary.Inspect, for: Atom do
# Detect if atom
defp valid_atom_identifier?(<<h, t|:binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
@@ -86,7 +164,7 @@ defimpl Binary.Inspect, for: Atom do
defp valid_atom_identifier?(_), do: false
defp valid_identifier?(<<h, t|:binary>>)
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
@@ -98,7 +176,9 @@ defimpl Binary.Inspect, for: Atom do
end
defimpl Binary.Inspect, for: BitString do
@doc %B"""
import Binary.Inspect.Utils
@moduledoc %B"""
Represents the string as itself escaping
all necessary characters.
@@ -108,34 +188,55 @@ defimpl Binary.Inspect, for: BitString do
inspect("f\"oo") #=> "f\"oo"
"""
def inspect(thing) when is_binary(thing) do
if Binary.printable?(thing) do
Binary.escape(thing, ?")
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
escape(thing, ?")
else
as_bitstring(thing)
as_bitstring(thing, opts)
end
end
def inspect(thing) do
as_bitstring(thing)
def inspect(thing, opts) do
as_bitstring(thing, opts)
end
## Helpers
## Bitstrings
defp as_bitstring(thing) do
erlang = Erlang.io_lib.format('~p', [thing])
list_to_binary List.reverse(replace(erlang, []))
defp as_bitstring(bitstring, opts) do
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
end
defp replace([?:|t], acc), do: replace(t, [?||acc])
defp replace([h|t], acc) when is_list(h), do: replace(t, replace(h, acc))
defp replace([h|t], acc), do: replace(t, [h|acc])
defp replace([], acc), do: acc
defp each_bit(_, 0) do
"..."
end
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
end
defp each_bit(<<h :: size(8)>>, _counter) do
integer_to_binary(h)
end
defp each_bit(<<>>, _counter) do
<<>>
end
defp each_bit(bitstring, _counter) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Binary.Inspect, for: List do
import Binary.Inspect.Utils
@doc %B"""
@moduledoc %B"""
Represents a list checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used.
@@ -154,33 +255,30 @@ defimpl Binary.Inspect, for: List do
"""
def inspect([]), do: "[]"
def inspect([], _), do: "[]"
def inspect(thing) do
if printable?(thing) do
Binary.escape(list_to_binary(thing), ?')
else
container_join(thing, "[", "]")
def inspect(thing, opts) do
cond do
printable?(thing) ->
escape(list_to_binary(thing), ?')
Keyword.keyword?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
container_join(thing, "[", "]", opts)
end
end
## Helpers
def container_join([h], acc, last) do
acc <> Binary.Inspect.inspect(h) <> last
defp join_keywords(thing, opts) do
Enum.join(lc {key, value} inlist thing do
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
end, ", ")
end
def container_join([h|t], acc, last) when is_list(t) do
acc = acc <> Binary.Inspect.inspect(h) <> ","
container_join(t, acc, last)
end
def container_join([h|t], acc, last) do
acc <> Binary.Inspect.inspect(h) <> "|" <> Binary.Inspect.inspect(t) <> last
end
def container_join([], acc, last) do
acc <> last
defp key_to_binary(key, opts) do
case Binary.Inspect.Atom.inspect(key, opts) do
":" <> right -> right
other -> other
end
end
## printable?
@@ -198,7 +296,9 @@ defimpl Binary.Inspect, for: List do
end
defimpl Binary.Inspect, for: Tuple do
@doc """
import Binary.Inspect.Utils
@moduledoc """
Inspect tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
@@ -208,53 +308,77 @@ defimpl Binary.Inspect, for: Tuple do
inspect(ArgumentError.new) #=> ArgumentError[message: "argument error"]
"""
def inspect({}), do: "{}"
def inspect(exception) when is_exception(exception) do
[name,_|tail] = tuple_to_list(exception)
[_|fields] = lc { field, _ } inlist name.__record__(:fields), do: field
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
end
def inspect({}, _), do: "{}"
def inspect(thing) do
list = tuple_to_list(thing)
[name|tail] = list
if is_record?(name) do
fields = lc { field, _ } inlist name.__record__(:fields), do: field
if length(fields) != size(thing) - 1 do
Binary.Inspect.List.container_join(list, "{", "}")
else
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
end
else
Binary.Inspect.List.container_join(list, "{", "}")
end
def inspect(tuple, opts) do
unless opts[:raw] do
record_protocol(tuple, opts)
end || container_join(tuple, "{", "}", opts)
end
## Helpers
defp is_record?(name) do
is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name, :utf8)) and
function_exported?(name, :__record__, 1)
defp record_protocol(tuple, opts) do
name = elem(tuple, 0)
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
unless name in [BitString, List, Tuple, Atom, Number, Any] do
try do
target = Module.concat(Binary.Inspect, name)
target.inspect(tuple, opts)
rescue
UndefinedFunctionError ->
record_inspect(tuple, opts)
end
end
end
end
defp records_join([f], [v], acc, last) do
acc <> atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v) <> last
defp record_inspect(record, opts) do
list = tuple_to_list(record)
[name|tail] = list
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if Enum.first(tail) == :__exception__ do
record_join(name, tl(fields), tl(tail), opts)
else
record_join(name, fields, tail, opts)
end
end
end
defp records_join([fh|ft], [vh|vt], acc, last) do
acc = acc <> atom_to_binary(fh, :utf8) <> ": " <> Binary.Inspect.inspect(vh) <> ", "
records_join(ft, vt, acc, last)
defp record_fields(name) do
try do
name.__record__(:fields)
rescue
_ -> nil
end
end
defp records_join([], [], acc, last) do
acc <> last
defp record_join(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
record_join(fields, tail, opts) <> "]"
end
defp record_join([f], [v], opts) do
atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v, opts)
end
defp record_join([fh|ft], [vh|vt], opts) do
atom_to_binary(fh, :utf8) <> ": " <>
Binary.Inspect.inspect(vh, opts) <> ", " <>
record_join(ft, vt, opts)
end
defp record_join([], [], _opts) do
""
end
end
defimpl Binary.Inspect, for: Number do
@doc """
@moduledoc """
Represents the number as a binary.
## Examples
@@ -262,17 +386,18 @@ defimpl Binary.Inspect, for: Number do
inspect(1) #=> "1"
"""
def inspect(thing) when is_integer(thing) do
def inspect(thing, _) when is_integer(thing) do
list_to_binary integer_to_list(thing)
end
def inspect(thing) do
list_to_binary float_to_list(thing)
def inspect(thing, _) do
list_to_binary :io_lib.format("~p", [thing])
end
end
defimpl Binary.Inspect, for: Regex do
@doc %B"""
@moduledoc %B"""
Represents the Regex using the `%r""` syntax.
## Examples
@@ -280,13 +405,18 @@ defimpl Binary.Inspect, for: Regex do
inspect(%r/foo/m) #=> "%r\"foo\"m"
"""
def inspect(thing) do
"%r" <> Binary.Inspect.inspect(Regex.source(thing)) <> Regex.opts(thing)
def inspect(regex, _opts) when size(regex) == 5 do
"%r" <> Binary.Inspect.inspect(Regex.source(regex), []) <> Regex.opts(regex)
end
def inspect(other, opts) do
Binary.Inspect.inspect other, Keyword.put(opts, :raw, true)
end
end
defimpl Binary.Inspect, for: Any do
@doc """
@moduledoc """
For all other terms not implemented, we use the default
Erlang representation.
@@ -295,7 +425,8 @@ defimpl Binary.Inspect, for: Any do
inspect Process.self #=> "<0.35.0>"
"""
def inspect(thing) do
iolist_to_binary Erlang.io_lib.format('~p', [thing])
def inspect(thing, _) do
iolist_to_binary :io_lib.format('~p', [thing])
end
end
+2 -2
View File
@@ -29,9 +29,9 @@ defmodule Bitwise do
defmacro __using__(options) do
except = cond do
Keyword.get(options, :only_operators) ->
[:bnot, :band, :bor, :bxor, :bsl, :bsr]
[bnot: 1, band: 2, bor: 2, bxor: 2, bsl: 2, bsr: 2]
Keyword.get(options, :skip_operators) ->
[:~~~, :&&&, :|||, :^^^, :<<<, :>>>]
[~~~: 1, &&&: 2, |||: 2, ^^^: 2, <<<: 2, >>>: 2]
true -> []
end
+123 -64
View File
@@ -17,12 +17,22 @@ defmodule Code do
server_call :loaded
end
@doc """
Removes the given files from the loaded files list.
The modules defined in the file are not removed,
calling this function only removes it from the list,
allowing it to be required again.
"""
def unload_files(files) do
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.
"""
def append_path(path) do
Erlang.code.add_pathz(File.expand_path to_char_list(path))
:code.add_pathz(File.expand_path to_char_list(path))
end
@doc """
@@ -30,49 +40,63 @@ defmodule Code do
The path is expanded with `File.expand_path` before added.
"""
def prepend_path(path) do
Erlang.code.add_patha(File.expand_path to_char_list(path))
:code.add_patha(File.expand_path to_char_list(path))
end
@doc """
Evalutes the contents given by string. The second argument is the binding
(which should be a Keyword) followed by a keyword list of options. The
options can be:
Deletes a path from Erlang VM code path.
The path is expanded with `File.expand_path` before deleted.
"""
def delete_path(path) do
:code.del_path(File.expand_path to_char_list(path))
end
@doc """
Evalutes the contents given by string. The second argument is the
binding (which should be a keyword) followed by a keyword list of
environment options. Those options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line the script starts
* `: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,
otherwise functions are evaluated inside Erlang's default scope.
the default is to not delegate
## Examples
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
When passing the __ENV__'s file and line, we could simply get
the location which already returns both fields as a keywords lists:
For convenience, you can my pass `__ENV__` as argument and
all imports, requires and aliases will be automatically carried
over:
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
Code.eval "a + b", [a: 1, b: 2], __ENV__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval(string, binding // [], opts // []) do
def eval(string, binding // [], opts // [])
def eval(string, binding, Macro.Env[] = env) do
eval(string, binding, env.to_keywords)
end
def eval(string, binding, opts) do
{ value, binding, _scope } =
Erlang.elixir.eval :unicode.characters_to_list(string), binding, opts
:elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding }
end
@doc """
Evalutes the quoted contents.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
This function accepts a list of environment options.
Check `Code.eval` for more information.
## Examples
@@ -81,16 +105,22 @@ defmodule Code do
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
When passing the __ENV__'s file and line, we could simply get
the location which already returns both fields as a keywords lists:
For convenience, you can my pass `__ENV__` as argument and
all options will be automatically extracted from the environment:
Code.eval_quoted contents, [a: 1, b: 2], __ENV__.location
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval_quoted(quoted, binding // [], opts // []) do
def eval_quoted(quoted, binding // [], opts // [])
def eval_quoted(quoted, binding, Macro.Env[] = env) do
eval_quoted(quoted, binding, env.to_keywords)
end
def eval_quoted(quoted, binding, opts) do
{ value, binding, _scope } =
Erlang.elixir.eval_quoted [quoted], binding, opts
:elixir.eval_quoted [quoted], binding, opts
{ value, binding }
end
@@ -105,11 +135,15 @@ defmodule Code do
* `:line` - The line reported in the __ENV__ variable.
* `:existing_atoms_only` - When true, raises an error
when non-existing atoms are found by the tokenizer.
"""
def string_to_ast(string, opts // []) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.raw_forms(:unicode.characters_to_list(string), line, file)
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
case res do
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
_ -> res
@@ -122,18 +156,12 @@ defmodule Code do
in case a token is missing (usually because the expression is incomplete),
SyntaxError otherwise.
## Options
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
Check `Code.string_to_ast/2` for options information.
"""
def string_to_ast!(string, opts // []) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file)
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
unpack_ast(line, res)
end
@@ -142,34 +170,50 @@ defmodule Code do
defp unpack_ast(line, forms), do: { :__block__, line, forms }
@doc """
Loads the given `file`. Accepts `relative_to` as an argument to tell
where the file is located. If the file was already required/loaded,
loads it again. It returns the full path of the loaded file.
Loads the given `file`. Accepts `relative_to` as an argument
to tell where the file is located. If the file was already
required/loaded, loads it again. It returns all the modules
defined in the file.
When loading a file, you may skip passing .exs as extension as Elixir
automatically adds it for you.
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
in many times. I.e. if `load_file` is called N times with
a given file, the given file will be loaded N times. Check
`require_file` if you don't want a file to be loaded concurrently.
"""
def load_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
server_call { :loaded, file }
Erlang.elixir_compiler.file file
file
server_call { :acquire, file }
loaded = :elixir_compiler.file file
server_cast { :loaded, file }
loaded
end
@doc """
Requires the given `file`. Accepts `relative_to` as an argument to tell
where the file is located. If the file was already required/loaded,
returns nil, otherwise the full path of the loaded file.
Requires the given `file`. Accepts `relative_to` as an argument
to tell where the file is located. If the file was already
required/loaded, loads it again. It returns all the modules
defined in the file.
When requiring a file, you may skip passing .exs as extension as
Elixir automatically adds it for you.
Notice that if `require_file` is invoked by different processes
concurrently, the first process to invoke `require_file` acquires
a lock and the remaining ones will block until the file is
available. I.e. if `require_file` is called N times with a given
file, the given file will be loaded only once. Check `load_file`
if you want a file to be loaded concurrently.
"""
def require_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
case server_call({ :loaded, file }) do
:ok -> Erlang.elixir_compiler.file file
:duplicated -> []
case 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 }
loaded
end
end
@@ -208,7 +252,7 @@ defmodule Code do
For compiling many files at once, check `Kernel.ParallelCompiler`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
Erlang.elixir_compiler.string :unicode.characters_to_list(string), to_binary(file)
:elixir_compiler.string :unicode.characters_to_list(string), to_binary(file)
end
@doc """
@@ -230,8 +274,8 @@ defmodule Code do
Therefore, this function is useful to check if a module is loaded
before using it and react accordingly. For example, the `URI` module
uses this function to check if a specific parser exists and is for the
given URI scheme.
uses this function to check if a specific parser exists for a given
URI scheme.
## Code.ensure_compiled
@@ -250,7 +294,15 @@ defmodule Code do
that needs to invoke a module for callback information.
"""
def ensure_loaded(module) when is_atom(module) do
Erlang.code.ensure_loaded(module)
:code.ensure_loaded(module)
end
@doc """
Similar to `ensure_loaded/1`, but returns a boolean in case
it could be ensured or not.
"""
def ensure_loaded?(module) do
match?({ :module, ^module }, ensure_loaded(module))
end
@doc """
@@ -267,13 +319,13 @@ defmodule Code do
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
"""
def ensure_compiled(module) when is_atom(module) do
case Erlang.code.ensure_loaded(module) do
case :code.ensure_loaded(module) do
{ :error, :nofile } = error ->
case :erlang.get(:elixir_compiler_pid) do
case :erlang.get(:elixir_ensure_compiled) do
:undefined -> error
_ ->
try do
module.__info__(:self)
module.__info__(:module)
{ :module, module }
rescue
UndefinedFunctionError -> error
@@ -283,6 +335,14 @@ defmodule Code do
end
end
@doc """
Similar to `ensure_compiled/1`, but returns a boolean in case
it could be ensured or not.
"""
def ensure_compiled?(module) do
match?({ :module, ^module }, ensure_compiled(module))
end
## Helpers
# Finds the file given the relative_to path.
@@ -299,16 +359,15 @@ defmodule Code do
if File.regular?(file) do
file
else
prefix = "#{file}.exs"
if File.regular?(prefix) do
prefix
else
raise ArgumentError, message: "could not load #{file}"
end
raise ArgumentError, message: "could not load #{file}"
end
end
defp server_call(args) do
Erlang.gen_server.call(:elixir_code_server, args)
:gen_server.call(:elixir_code_server, args)
end
defp server_cast(args) do
:gen_server.cast(:elixir_code_server, args)
end
end
+148 -39
View File
@@ -1,12 +1,49 @@
defprotocol Dict do
@only [Record]
defmodule Dict do
@moduledoc """
This module provides the Dict protocol
with the goal of being a common API
to work with dictionaries.
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.
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
`new` is used, it implies one of the module-specific
calls like the two above. Likewise, when the result of
a function invocation is shown in the form `[a: 1, b: 2]`,
it implies that the returned value is actually of the
same dict type as the input one.
"""
use Behaviour
@type key :: any
@type value :: any
@type t :: tuple
defcallback delete(t, key) :: t
defcallback empty(t) :: t
defcallback get(t, key, value) :: value
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback merge(t, t, (key, value, value -> value)) :: t
defcallback put(t, key, value) :: t
defcallback size(t) :: non_neg_integer()
defcallback to_list(t) :: list()
defcallback update(t, key, (value -> value)) :: t
defcallback values(t) :: list(value)
@doc """
Returns a list containing all dict's keys.
The keys are not guaranteed to be sorted, unless
@@ -14,41 +51,57 @@ defprotocol Dict do
## Examples
Dict.keys [a: 1, b: 2] #=> [:a,:b]
d = new [a: 1, b: 2]
Dict.keys d #=> [:a,:b]
"""
def keys(dict)
@spec keys(t) :: [key]
def keys(dict) do
elem(dict, 0).keys(dict)
end
@doc """
Returns a list containing all dict's values.
## Examples
Dict.values [a: 1, b: 2] #=> [1,2]
d = new [a: 1, b: 2]
Dict.values d #=> [1,2]
"""
def values(dict)
@spec values(t) :: [value]
def values(dict) do
elem(dict, 0).values(dict)
end
@doc """
Returns the number of elements in `dict`.
## Examples
Dict.size [a: 1, b: 2] #=> 2
d = new [a: 1, b: 2]
Dict.size d #=> 2
"""
def size(dict)
@spec size(t) :: non_neg_integer
def size(dict) do
elem(dict, 0).size(dict)
end
@doc """
Returns whether the given key exists in the given dict.
## Examples
Dict.has_key?([a: 1], :a) #=> true
Dict.has_key?([a: 1], :b) #=> false
d = new [a: 1]
Dict.has_key?(d, :a) #=> true
Dict.has_key?(d, :b) #=> false
"""
def has_key?(dict, key)
@spec has_key?(t, key) :: boolean
def has_key?(dict, key) do
elem(dict, 0).has_key?(dict, key)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
@@ -56,13 +109,33 @@ defprotocol Dict do
## Examples
Dict.get [a: 1], :a #=> 1
Dict.get [a: 1], :b #=> nil
Dict.get [a: 1], :b, 3 #=> 3
d = new [a: 1]
Dict.get d, :a #=> 1
Dict.get d, :b #=> nil
Dict.get d, :b, 3 #=> 3
"""
def get(dict, key)
def get(dict, key, default)
@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)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, it raises `KeyError`.
## Examples
d = new [a: 1]
Dict.get d, :a #=> 1
Dict.get d, :b #=> raises KeyError[key: :b]
"""
@spec get!(t, key) :: value | no_return
def get!(dict, key) do
elem(dict, 0).get!(dict, key)
end
@doc """
Stores the given `value` under `key` in `dict`.
@@ -70,11 +143,15 @@ defprotocol Dict do
## Examples
Dict.put [a: 1, b: 2], :a, 3
d = new [a: 1, b: 2]
Dict.put d, :a, 3
#=> [a: 3, b: 2]
"""
def put(dict, key, val)
@spec put(t, key, value) :: t
def put(dict, key, val) do
elem(dict, 0).put(dict, key, val)
end
@doc """
Removes the entry stored under the given key from `dict`.
@@ -82,23 +159,36 @@ defprotocol Dict do
## Examples
Dict.delete [a: 1, b: 2], :a #=> [b: 2]
Dict.delete [b: 2], :a #=> [b: 2]
d = new [a: 1, b: 2]
Dict.delete d, :a #=> [b: 2]
d = new [b: 2]
Dict.delete d, :a #=> [b: 2]
"""
def delete(dict, key)
@spec delete(t, key) :: t
def delete(dict, key) do
elem(dict, 0).delete(dict, key)
end
@doc """
Merges two dicts into one. If the dicts have duplicated entries, the one
given as second argument wins.
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.
## Examples
Dict.merge [a: 1, b: 2], [a: 3, d: 4]
#=> [a:3, b:2, d: 4]
d1 = new [a: 1, b: 2]
d2 = new [a: 3, d: 4]
Dict.merge d1, d2
#=> [a: 3, b: 2, d: 4]
"""
def merge(dict1, dict2)
@spec merge(t, t) :: t
def merge(dict1, dict2) do
merge(dict1, dict2, fn(_k, _v1, v2) -> v2 end)
end
@doc """
Merges two dicts into one. If the dicts have duplicated entries, the given
@@ -106,13 +196,18 @@ defprotocol Dict do
## Examples
Dict.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
d1 = new [a: 1, b: 2]
d2 = new [a: 3, d: 4]
Dict.merge d1, d2, fn _k, v1, v2 ->
v1 + v2
end
#=> [a: 4, b: 2, d: 4]
"""
def merge(dict1, dict2, fun)
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(dict1, dict2, fun) do
elem(dict1, 0).merge(dict1, dict2, fun)
end
@doc """
Update a value in `dict` by calling `fun` on the value to get a new
@@ -120,11 +215,15 @@ defprotocol Dict do
## Examples
Dict.update [a: 1, b: 2], :a, fn val -> -val end
d = new [a: 1, b: 2]
Dict.update d, :a, fn val -> -val end
#=> [a: -1, b: 2]
"""
def update(dict, key, fun)
@spec update(t, key, (value -> value)) :: t
def update(dict, key, fun) do
elem(dict, 0).update(dict, key, fun)
end
@doc """
Update a value in `dict` by calling `fun` on the value to get a new value. If
@@ -133,20 +232,30 @@ defprotocol Dict do
## Examples
Dict.update [a: 1, b: 2], :c, 3, fn val -> -val end
d = new [a: 1, b: 2]
Dict.update d, :c, 3, fn val -> -val end
#=> [a: 1, b: 2, c: 3]
"""
def update(dict, key, initial, fun)
@spec update(t, key, value, (value -> value)) :: t
def update(dict, key, initial, fun) do
elem(dict, 0).update(dict, key, initial, fun)
end
@doc """
Returns an empty dict of the same type as `dict`.
"""
def empty(dict)
@spec empty(t) :: t
def empty(dict) do
elem(dict, 0).empty(dict)
end
@doc """
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
"""
def to_list(dict)
end
@spec to_list(t) :: list
def to_list(dict) do
elem(dict, 0).to_list(dict)
end
end
+6 -4
View File
@@ -1,13 +1,15 @@
defmodule Dict.Common do
@moduledoc false
defmacro __using__(ref) do
defmacro __using__(_) do
quote do
@behavior Dict
@doc """
Creates a new empty dict.
"""
def new do
unquote(ref).empty(nil)
empty(nil)
end
@doc """
@@ -21,7 +23,7 @@ defmodule Dict.Common do
"""
def new(pairs) do
Enum.reduce pairs, new, fn { k, v }, dict ->
unquote(ref).put(dict, k, v)
put(dict, k, v)
end
end
@@ -37,7 +39,7 @@ defmodule Dict.Common do
def new(list, transform) when is_function(transform) do
Enum.reduce list, new(), fn i, dict ->
{ k, v } = transform.(i)
unquote(ref).put(dict, k, v)
put(dict, k, v)
end
end
end
+655 -286
View File
File diff suppressed because it is too large Load Diff
+38 -6
View File
@@ -56,11 +56,11 @@ defmodule Exception do
ErlangError.new original: other
end
# Check the given module is a valid record.
# Check the given module is a valid exception record.
@doc false
def check!(module) do
unless :erlang.function_exported(module, :message, 1) do
raise "Expected #{inspect module} to implement message/1"
raise "Expected #{inspect module} to implement message/1"
end
end
@@ -70,7 +70,10 @@ defmodule Exception do
of arguments. It follows the same syntax as in stacktraces.
"""
def format_module_fun_arity(module, fun, arity) do
<< ?:, fun | :binary >> = inspect(fun)
case inspect(fun) do
<< ?:, fun :: binary >> -> :ok
fun -> :ok
end
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
@@ -80,6 +83,35 @@ 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
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.
"""
@@ -89,7 +121,7 @@ defmodule Exception do
@doc """
Formats file and line information present in stacktraces.
Expect them to be given in a keywords list.
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))
@@ -204,10 +236,10 @@ defexception ErlangError, [original: nil] do
end
end
defexception Keyword.KeyError, key: nil do
defexception KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.OutOfBoundsError, message: "out of bounds error"
+147 -78
View File
@@ -1,33 +1,35 @@
defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.hrl"), moduledoc: """
A record responsible to hold file information. Its fields are:
defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.hrl") do
@moduledoc """
A record responsible to hold file information. Its fields are:
* `size` - Size of file in bytes.
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
the file.
* `atime` - The last time the file was read.
* `mtime` - The last time the file was written.
* `ctime` - The interpretation of this time field depends on the operating
system. On Unix, it is the last time the file or the inode was
changed. In Windows, it is the create time.
* `mode` - The file permissions.
* `links` - The number of links to this file. This is always 1 for file
systems which have no concept of links.
* `major_device` - Identifies the file system where the file is located.
In windows, the number indicates a drive as follows:
0 means A:, 1 means B:, and so on.
* `minor_device` - Only valid for character devices on Unix. In all other
cases, this field is zero.
* `inode` - Gives the inode number. On non-Unix file systems, this field
will be zero.
* `uid` - Indicates the owner of the file.
* `gid` - Gives the group that the owner of the file belongs to. Will be
zero for non-Unix file systems.
* `size` - Size of file in bytes.
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
the file.
* `atime` - The last time the file was read.
* `mtime` - The last time the file was written.
* `ctime` - The interpretation of this time field depends on the operating
system. On Unix, it is the last time the file or the inode was
changed. In Windows, it is the create time.
* `mode` - The file permissions.
* `links` - The number of links to this file. This is always 1 for file
systems which have no concept of links.
* `major_device` - Identifies the file system where the file is located.
In windows, the number indicates a drive as follows:
0 means A:, 1 means B:, and so on.
* `minor_device` - Only valid for character devices on Unix. In all other
cases, this field is zero.
* `inode` - Gives the inode number. On non-Unix file systems, this field
will be zero.
* `uid` - Indicates the owner of the file.
* `gid` - Gives the group that the owner of the file belongs to. Will be
zero for non-Unix file systems.
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
time type set in options. `{:time, type}` where type can be `:local`,
`:universal`, or `:posix`. Default is `:local`.
"""
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
time type set in options. `{:time, type}` where type can be `:local`,
`:universal`, or `:posix`. Default is `:local`.
"""
end
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
@@ -57,8 +59,16 @@ defmodule File do
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`. If you want to read or write to a file
in chunks, check the IO module.
`File.stat`.
In order to write and read files, one must use the
functions in the IO module. By default, a file is
opened on binary mode which requires the functions
`IO.binread`, `IO.binwrite` and `IO.binreadline` to
interact with the file. A developer may pass `:utf8`
as an option when opening the file and then all other
functions from IO are available, since they work directly
with Unicode data.
Most of the functions in this module return `:ok`
or `{ :ok, result }` in case of success, `{ :error, reason }`
@@ -85,11 +95,16 @@ defmodule File do
should be used when the developer expects his software
to fail in case the file cannot be read (i.e. it is
literally an exception).
Finally, the functions in this module accept either
a char lists or a binary. When manipulating paths, a char
list is returned if one is given as argument. However,
when reading files, binaries are always returned.
"""
alias Erlang.file, as: F
alias Erlang.filename, as: FN
alias Erlang.filelib, as: FL
alias :file, as: F
alias :filename, as: FN
alias :filelib, as: FL
@doc """
Expands the path by returning its absolute name and expanding
@@ -385,7 +400,7 @@ defmodule File do
On some platforms, `:enoent` is returned instead.
* :enomem - There is not enough memory for the contents of the file.
You can use `Erlang.file.format_error(reason)` to get a descriptive string of the error.
You can use `:file.format_error(reason)` to get a descriptive string of the error.
"""
def read(path) do
F.read_file(path)
@@ -456,12 +471,12 @@ defmodule File do
"""
def wildcard(glob) when is_binary(glob) do
paths = Erlang.elixir_glob.wildcard binary_to_list(glob)
paths = :elixir_glob.wildcard binary_to_list(glob)
Enum.map paths, list_to_binary(&1)
end
def wildcard(glob) when is_list(glob) do
Erlang.elixir_glob.wildcard(glob)
:elixir_glob.wildcard(glob)
end
@doc """
@@ -487,16 +502,6 @@ defmodule File do
end
end
def read_info(path, opts // []) do
IO.puts "File.read_info is deprecated in favor of File.stat"
stat(path, opts)
end
def read_info!(path, opts // []) do
IO.puts "File.read_info! is deprecated in favor of File.stat"
stat!(path, opts)
end
@doc """
Same as `stat` but returns the `File.Stat` directly and
throws `File.Error` if an error is returned.
@@ -514,7 +519,7 @@ defmodule File do
path. Returns `:ok` or `{ :error, reason }`.
"""
def write_stat(path, File.Stat[] = stat, opts // []) do
F.write_file_info(path, setelem(stat, 1, :file_info), opts)
F.write_file_info(path, setelem(stat, 0, :file_info), opts)
end
@doc """
@@ -738,16 +743,25 @@ defmodule File do
acc
end
defp copy_file_mode!(src, dest) do
src_stat = File.stat!(src)
dest_stat = File.stat!(dest)
File.write_stat!(dest, File.Stat.mode(File.Stat.mode(src_stat), dest_stat))
end
# Both src and dest are files.
defp do_cp_file(src, dest, callback, acc) do
case copy(src, { dest, [:exclusive] }) do
{ :ok, _ } ->
copy_file_mode!(src, dest)
[dest|acc]
{ :error, :eexist } ->
if callback.(src, dest) do
rm(dest)
case copy(src, dest) do
{ :ok, _ } -> [dest|acc]
{ :ok, _ } ->
copy_file_mode!(src, dest)
[dest|acc]
reason -> reason
end
else
@@ -968,6 +982,12 @@ defmodule File do
Note that the file size obtained with `stat/1` will most probably not
match the number of bytes that can be read from a compressed file.
* `:utf8` - This option denotes how data is actually stored in the disk file and
makes the file perform automatic translation of characters to and from utf-8.
If data is sent to a file in a format that cannot be converted to the utf-8
or if data is read by a function that returns data in a format that cannot cope
with the character range of the data, an error occurs and the file will be closed.
If a function is given to modes (instead of a list), it dispatches to `open/3`.
Check `http://www.erlang.org/doc/man/file.html#open-2` for more information about
@@ -994,7 +1014,7 @@ defmodule File do
def open(path, modes // [])
def open(path, modes) when is_list(modes) do
F.open(path, open_defaults(modes, true, true))
F.open(path, open_defaults(modes, true))
end
def open(path, function) when is_function(function) do
@@ -1017,9 +1037,9 @@ defmodule File do
## Examples
File.open!("foo.txt", [:read, :write], fn(file) ->
IO.readline(file)
end)
File.open!("foo.txt", [:read, :write], fn(file) ->
IO.readline(file)
end)
"""
def open(path, modes, function) do
@@ -1132,10 +1152,12 @@ defmodule File do
end
@doc """
Convert the file device into an iterator that can be
Converts the file device into an iterator that can be
passed into `Enum`. The device is iterated line
by line lazily, at the end of iteration the file is
closed.
by line, at the end of iteration the file is closed.
This reads the file as utf-8. CHeck out `File.biniterator`
to handle the file as a raw binary.
## Examples
@@ -1146,7 +1168,7 @@ defmodule File do
source = File.iterator("README.md")
File.open "NEWREADME.md", [:write], fn(target) ->
Enum.each source, fn(line) ->
IO.write target, Regex.replace_all(%r/"/, line, "'")
IO.write target, Regex.replace(%r/"/, line, "'")
end
end
@@ -1158,16 +1180,19 @@ defmodule File do
end
def iterator(device) do
fn(_) ->
case :io.get_line(device, "") do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
{ data, :ok }
fn ->
function = fn(_) ->
case :io.get_line(device, '') do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
data ->
{ data, :ok }
end
end
{ function, function.(:start) }
end
end
@@ -1191,6 +1216,54 @@ defmodule File do
open!(file, mode) /> iterator
end
@doc """
Converts the file device into an iterator that can
be passed into `Enum` to iterate line by line as a
binary. Check `iterator/1` for more information.
"""
def biniterator(device)
def biniterator(file) when is_binary(file) or is_list(file) do
biniterator(file, [])
end
def biniterator(device) do
fn ->
function = fn(_) ->
case :file.read_line(device) do
:eof ->
close(device)
:stop
{ :error, reason } ->
raise File.IteratorError, reason: reason
{ :ok, data } ->
{ data, :ok }
end
end
{ function, function.(:start) }
end
end
@doc """
Opens the given `file` with the given `mode` and
returns its biniterator. Fails for the same reasons
as `File.open`.
"""
def biniterator(file, mode) do
case open(file, mode) do
{ :ok, device } -> { :ok, biniterator(device) }
error -> error
end
end
@doc """
Same as `biniterator/2` but raises if the file
cannot be opened.
"""
def biniterator!(file, mode // []) do
open!(file, mode) /> biniterator
end
## Helpers
# Normalize the given path by removing "..".
@@ -1209,25 +1282,21 @@ defmodule File do
end
defp normalize([], acc) do
join List.reverse(acc)
join Enum.reverse(acc)
end
defp open_defaults([:charlist|t], add_encoding, _add_binary) do
open_defaults(t, add_encoding, false)
defp open_defaults([:charlist|t], _add_binary) do
open_defaults(t, false)
end
defp open_defaults([{:encoding, _} = h|t], _add_encoding, add_binary) do
[h|open_defaults(t, false, add_binary)]
defp open_defaults([:utf8|t], add_binary) do
open_defaults([{ :encoding, :utf8 }|t], add_binary)
end
defp open_defaults([h|t], add_encoding, add_binary) do
[h|open_defaults(t, add_encoding, add_binary)]
defp open_defaults([h|t], add_binary) do
[h|open_defaults(t, add_binary)]
end
defp open_defaults([], add_encoding, add_binary) do
options = []
if add_encoding, do: options = [{:encoding, :unicode}|options]
if add_binary, do: options = [:binary|options]
options
end
defp open_defaults([], true), do: [:binary]
defp open_defaults([], false), do: []
end
-48
View File
@@ -1,48 +0,0 @@
defmodule GenServer.Behavior do
@doc """
By using this module, you get default GenServer callbacks
for `handle_call`, `handle_info`, `handle_cast`, `terminate`
and `code_change`. `init` still needs to be implemented by the
developer. Since these functions are defined as overridable,
they can be partially customized and have a global clause
that simply invokes `super`. See `ExUnit.Server` for some
code examples.
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
"""
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_server
def handle_call(_request, _from, state) do
{ :reply, :undef, state }
end
def handle_info(_msg, state) do
{ :noreply, state }
end
def handle_cast(_msg, state) do
{ :noreply, state }
end
def terminate(reason, state) do
:error_logger.error_report('#{inspect __MODULE__} crashed:\n#{inspect reason}')
:error_logger.error_report('#{inspect __MODULE__} snapshot:\n#{inspect state}')
:ok
end
def code_change(_old, state, _extra) do
{ :ok, state }
end
defoverridable [handle_call: 3, handle_info: 2, handle_cast: 2, terminate: 2, code_change: 3]
end
end
end
+77
View File
@@ -0,0 +1,77 @@
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 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
## Example
defmodule MyServer do
use GenServer.Behaviour
# Callbacks
def handle_call(:peek, _from, [h|_] = state) do
{ :reply, h, state }
end
# Default behaviour
def handle_call(request, from, config) do
super(request, from, config)
end
def handle_cast({ :push, item }, state) do
{ :noreply, [item|state] }
end
# Default cast behaviour
def handle_cast(request, config) do
super(request, config)
end
end
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_server
def init(args) do
{ :ok, args }
end
def handle_call(_request, _from, state) do
{ :reply, :undef, state }
end
def handle_info(_msg, state) do
{ :noreply, state }
end
def handle_cast(_msg, state) do
{ :noreply, state }
end
def terminate(reason, state) do
:ok
end
def code_change(_old, state, _extra) do
{ :ok, state }
end
defoverridable [init: 1, handle_call: 3, handle_info: 2,
handle_cast: 2, terminate: 2, code_change: 3]
end
end
end
+45 -21
View File
@@ -1,81 +1,105 @@
defimpl Dict, for: HashDict do
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.
Check the `Dict` module for examples and documentation.
"""
use Dict.Common
defmacrop dict(data) do
quote do
{ HashDict, unquote(data) }
end
end
@doc false
def keys(dict(data)) do
:dict.fetch_keys data
end
@doc false
def values(dict(data)) do
:dict.fold fn _key, value, acc ->
[value|acc]
end, [], data
end
@doc false
def size(dict(data)) do
:dict.size data
end
@doc false
def has_key?(dict(data), key) do
:dict.is_key key, data
end
def get(dict(data), key, default // nil) do
@doc false
def get(dict(data), key, default) do
case :dict.find(key, data) do
{:ok, value} ->
value
:error ->
default
{:ok, value} -> value
:error -> default
end
end
@doc false
def get!(dict(data), key) do
case :dict.find(key, data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
@doc false
def put(dict(data), key, value) do
dict(:dict.store key, value, data)
end
@doc false
def delete(dict(data), key) do
dict(:dict.erase key, data)
end
def merge(dict(d1), dict(d2)) do
dict(:dict.merge fn _k, _v1, v2 -> v2 end, d1, d2)
end
@doc false
def merge(dict(d1), dict(d2), fun) do
dict(:dict.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(:dict.update key, fun, data)
end
@doc false
def update(dict(data), key, initial, fun) do
dict(:dict.update key, fun, initial, data)
end
@doc false
def empty(_) do
dict(:dict.new)
end
@doc false
def to_list(dict(data)) do
:dict.to_list data
end
end
defimpl Enum.Iterator, for: HashDict do
def iterator({ HashDict, data }), do: :dict.to_list(data)
def count({ HashDict, data }), do: :dict.size(data)
def iterator(dict), do: HashDict.to_list(dict)
def count(dict), do: HashDict.size(dict)
end
defmodule HashDict do
@moduledoc """
This module implements a dictionary 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` protocol.
"""
use Dict.Common, Dict.HashDict
end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
end
-212
View File
@@ -1,212 +0,0 @@
defrecord IEx.Config, io: nil, binding: nil, cache: '', counter: 1, scope: nil, result: nil
defmodule IEx do
@moduledoc """
This module implements interactive Elixir. It provides a main
function, `start` which will either delegate to `tty` or `simple`.
The former is meant for systems where tty is available and relies
on it in order to work properly. This makes all control commands
available in tty available to the developer.
In case `tty` is not available (for example, Windows), a
developer may invoke `simple` which starts a stripped
down version.
"""
import Exception, only: [format_stacktrace: 1]
@doc """
Interface to start IEx from CLI.
"""
def cli do
run([remsh: get_remsh(:init.get_plain_arguments)])
end
defp get_remsh(['--remsh',h|_]), do: list_to_binary(h)
defp get_remsh([_|t]), do: get_remsh(t)
defp get_remsh([]), do: nil
@doc """
Runs IEx checking if tty is available or not.
If so, invoke tty, otherwise go with the simple iex.
"""
def run(opts // []) when is_list(opts) do
case :os.type do
{ :unix, _ } -> tty(opts)
_ -> simple(opts)
end
end
@doc """
Starts IEx using a tty server.
"""
def tty(opts // []) when is_list(opts) do
config = boot_config(opts)
remote =
if remsh = opts[:remsh] do
if node() == :nonode@nohost do
raise ArgumentError, message: "In order to use --remsh, you need to name the node"
end
if is_atom(remsh), do: remsh, else: binary_to_atom(remsh)
end
function = fn ->
# We are inside the new tty and in a new process,
# reattach it the error logger.
attach_error_logger
start config
end
# Dettach the error logger because we are going to unregister
# the user process and start a new tty which will get control
# over the standardio. Dettaching it here allows us to get rid
# of warnings. We reattach it again when we get the new tty.
dettach_error_logger
# Unregister the user process, user_drv command below
# will register the new one.
unregister_user_process
# Close the default io port, user_drv start command below
# will take control over the io.
close_io_port
args =
if remote do
{ remote, :erlang, :apply, [function, []] }
else
{ :erlang, :apply, [function, []] }
end
Erlang.user_drv.start([:"tty_sl -c -e", args])
end
@doc """
Starts IEx simply using the current stdio.
"""
def simple(opts // []) when is_list(opts) do
start boot_config(opts)
end
# This is a callback invoked by Erlang shell utilities.
@doc false
def start(config // nil) do
spawn fn ->
config = config || boot_config([])
gl = :erlang.group_leader
glnode = node gl
if glnode != node do
ensure_module_exists glnode, IEx.Remsh
expand_fun = IEx.Remsh.expand node
else
expand_fun = IEx.Autocomplete.expand &1
end
:io.setopts gl, [expand_fun: expand_fun]
start_loop(config)
end
end
## Boot Helpers
defp boot_config(opts) do
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit"
scope = Erlang.elixir.scope_for_eval(
file: 'iex',
delegate_locals_to: IEx.Helpers
)
IEx.Config[
io: opts[:io] || IEx.UnicodeIO,
binding: opts[:binding] || [],
scope: scope
]
end
defp dettach_error_logger do
:error_logger.delete_report_handler(:error_logger_tty_h)
end
defp attach_error_logger do
:error_logger.add_report_handler(:error_logger_tty_h)
end
defp unregister_user_process do
if is_pid(Process.whereis(:user)), do: Process.unregister :user
end
defp close_io_port do
if port = Enum.find(Port.list, io_port?(&1)) do
Port.close(port)
end
end
defp io_port?(port) do
Port.info(port, :name) == {:name,'0/1'} && port
end
## Loop helpers
defp start_loop(config) do
Process.put :iex_history, []
{ _, _, scope } = Erlang.elixir.eval('import IEx.Helpers', [], 0, config.scope)
do_loop(config.scope(scope))
end
defp do_loop(config) do
io = config.io
counter = config.counter
cache = config.cache
code = cache ++ io.get(config)
new_config =
try do
{ result, new_binding, scope } =
Erlang.elixir.eval(code, config.binding, counter, config.scope)
io.put result
config = config.result(result)
update_history(config.cache(code).scope(nil))
config.increment_counter.cache('').binding(new_binding).scope(scope)
rescue
TokenMissingError ->
config.cache(code)
exception ->
stacktrace = System.stacktrace
io.error "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace io, stacktrace
config.cache('')
catch
kind, error ->
stacktrace = System.stacktrace
io.error "** (#{kind}) #{inspect(error)}"
print_stacktrace io, stacktrace
config.cache('')
end
do_loop(new_config)
end
defp update_history(config) do
current = Process.get :iex_history
Process.put :iex_history, [config|current]
end
defp print_stacktrace(io, stacktrace) do
Enum.each stacktrace, fn s -> io.error " #{format_stacktrace(s)}" end
end
## Code injection helper
defp ensure_module_exists(node, mod) do
unless :rpc.call node, :code, :is_loaded, [mod] do
{m,b,f} = :code.get_object_code mod
{:module, mod} = :rpc.call node, :code, :load_binary, [m,f,b]
end
end
end
-244
View File
@@ -1,244 +0,0 @@
defmodule IEx.Helpers do
@moduledoc """
A bunch of helpers available in IEx.
* `c` - compiles a file in the given path
* `d` - prints documentation
* `h` - prints history
* `m` - prints loaded modules
* `r` - recompiles and reloads the given module's source file
* `v` - retrieves nth value from console
Documentation for functions in this module can be consulted
directly from the command line, as an example, try:
d(:c, 1)
"""
@doc """
Expects a list of files to compile and a path
to write their object code to. It returns the name
of the compiled modules.
## Examples
c ["foo.ex"], "ebin"
#=> Foo
"""
def c(files, path // ".") do
tuples = Kernel.ParallelCompiler.files_to_path List.wrap(files), path
Enum.map tuples, elem(&1, 1)
end
@doc """
Returns the name and module of all modules loaded.
"""
def m do
all = Enum.map :code.all_loaded, fn { mod, file } -> { inspect(mod), file } end
sorted = List.sort(all)
size = Enum.reduce sorted, 0, fn({ mod, _ }, acc) -> max(byte_size(mod), acc) end
format = "~-#{size}s ~s~n"
Enum.each sorted, fn({ mod, file }) ->
:io.format(format, [mod, file])
end
end
@doc """
Prints commands history and their result.
"""
def h do
history = List.reverse(Process.get(:iex_history))
Enum.each(history, print_history(&1))
end
defp print_history(config) do
IO.puts "#{config.counter}: #{config.cache}#=> #{inspect config.result}\n"
end
@doc """
Shows the documentation for IEx.Helpers.
"""
def d() do
d(IEx.Helpers, :all)
end
@doc """
Shows the documentation for the given module
or for the given function/arity pair.
## Examples
d(Enum)
#=> Prints documentation for Enum
It also accepts functions in the format `fun/arity`
and `module.fun/arity`, for example:
d receive/1
d Enum.all?/2
"""
defmacro d({ :/, _, [{ fun, _, nil }, arity] }) do
quote do
d(unquote(fun), unquote(arity))
end
end
defmacro d({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
quote do
d(unquote(mod), unquote(fun), unquote(arity))
end
end
defmacro d(other) do
quote do
d(unquote(other), :all)
end
end
@doc """
Prints the documentation for the given function and arity.
The function may either be a function defined inside `IEx.Helpers`
or in `Kernel`. To see functions from other module, use
`d/3` instead.
## Examples
d(:d, 2)
#=> Prints documentation for this function
"""
def d(:d, 1) do
d(__MODULE__, :d, 1)
end
def d(function, arity) when is_atom(function) and is_integer(arity) do
if function_exported?(__MODULE__, function, arity) do
d(__MODULE__, function, arity)
else
d(Kernel, function, arity)
end
end
def d(module, :all) when is_atom(module) do
case Code.ensure_loaded(module) do
{ :module, _ } ->
case module.__info__(:moduledoc) do
{ _, binary } when is_binary(binary) ->
IO.puts "# #{inspect module}\n"
IO.write binary
{ _, _ } ->
IO.puts "No docs for #{inspect module}"
_ ->
IO.puts "#{inspect module} was not compiled with docs"
end
{ :error, reason } ->
IO.puts "Could not load module #{inspect module}: #{reason}"
end
end
@doc """
Shows the documentation for the `function/arity` in `module`.
"""
def d(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
if docs = module.__info__(:docs) do
doc =
if tuple = List.keyfind(docs, { function, arity }, 1) do
print_signature(tuple)
end
if doc do
IO.write "\n" <> doc
else
IO.puts "No docs for #{function}/#{arity}"
end
else
IO.puts "#{inspect module} was not compiled with docs"
end
end
# Get the full signature from a function.
defp print_signature({ _info, _line, _kind, _args, false }) do
false
end
defp print_signature({ { name, _arity }, _line, kind, args, docs }) do
args = Enum.map_join(args, ", ", signature_arg(&1))
IO.puts "* #{kind} #{name}(#{args})"
docs
end
defp signature_arg({ ://, _, [left, right] }) do
signature_arg(left) <> " // " <> Macro.to_binary(right)
end
defp signature_arg({ var, _, _ }) do
atom_to_binary(var)
end
@doc """
Retrieves nth query's value from the history. Use negative
values to lookup query's value from latest to earliest.
For instance, v(-1) returns the latest result.
"""
def v(n) when n < 0 do
history = Process.get(:iex_history)
Enum.nth!(history, abs(n)).result
end
def v(n) do
history = Process.get(:iex_history) /> List.reverse
Enum.nth!(history, n).result
end
@doc """
Reloads all modules that were already reloaded
at some point with `r/1`.
"""
def r do
Enum.map iex_reloaded, r(&1)
end
@doc """
Recompiles and reloads the specified module's source file.
Please note that all the modules defined in the specified
files are recompiled and reloaded.
"""
def r(module) do
if source = source(module) do
Process.put(:iex_reloaded, :ordsets.add_element(module, iex_reloaded))
{ module, Code.load_file source }
else
:nosource
end
end
defp iex_reloaded do
Process.get(:iex_reloaded) || :ordsets.new
end
defp source(module) do
compile = module.module_info(:compile)
# Get the source of the compiled module. Due to a bug in Erlang
# R15 and before, we need to look for the source first in the
# options and then into the real source.
options =
case List.keyfind(compile, :options, 1) do
{ :options, opts } -> opts
_ -> []
end
source = List.keyfind(options, :source, 1) || List.keyfind(compile, :source, 1)
case source do
{ :source, source } -> list_to_binary(source)
_ -> nil
end
end
end
-45
View File
@@ -1,45 +0,0 @@
defmodule IEx.UnicodeIO do
@moduledoc """
This module implements the API used by IEX to
interact with the console. This API may change
in the future without warnings.
"""
@doc """
Implements the get IO API used by IEx. It receives the
code cache, the instructions counter and needs to
return a list with the new characters inserted.
"""
def get(config) do
prefix = case config.cache do
[] -> "iex"
_ -> "..."
end
prompt = case node do
:nonode@nohost ->
"#{prefix}(#{config.counter})> "
n ->
"#{prefix}(#{n})#{config.counter}> "
end
case IO.gets(prompt) do
{ :error, _ } -> ''
data -> :unicode.characters_to_list(data)
end
end
@doc """
Implements the put IO API used by IEx. It receives the
result and prints it.
"""
def put(result) do
IO.inspect result
end
@doc """
Implements the error IO API used by IEx. It prints error
messages.
"""
def error(result) do
IO.puts result
end
end
+55 -16
View File
@@ -1,15 +1,23 @@
defmodule IO do
@moduledoc """
Module responsible for doing IO. The function in this
module expects an iodata as argument encoded in UTF-8.
An iodata can be:
Module responsible for doing IO. Many functions in this
module expects an IO device and an io data encoded in UTF-8.
An IO device must be a pid or an atom representing a process.
For convenience, Elixir provides `:stdio` and `:stderr` as
shortcut to Erlang's `:standard_io` and `:standard_error`.
An io data can be:
* A list of integers representing a string. Any unicode
character must be represented with one entry in the list,
this entry being an integer with the codepoint value;
* A binary in which unicode characters are represented
with many bytes (Elixir's default representation);
* A list of binaries or a list of char lists (as described above);
* If none of the above, `to_binary` is invoked in the
given argument;
@@ -27,11 +35,24 @@ defmodule IO do
NFS file system.
"""
def read(device // :stdio, count) do
Erlang.io.get_chars(map_dev(device), "", count)
:io.get_chars(map_dev(device), "", count)
end
@doc """
Read a line from the IO device. It returns:
Reads `count` bytes from the IO device as binary,
no unicode conversion happens.
Check `read/2` for more information.
"""
def binread(device // :stdio, count) do
case :file.read(map_dev(device), count) do
{ :ok, data } -> data
other -> other
end
end
@doc """
Reads a line from the IO device. It returns:
* `data` - The input characters.
@@ -45,7 +66,20 @@ defmodule IO do
except the prompt is not required as argument.
"""
def readline(device // :stdio) do
Erlang.io.get_line(map_dev(device), "")
:io.get_line(map_dev(device), "")
end
@doc """
Reads a line from the IO device as binary,
no unicode conversion happens.
Check `readline/1` for more information.
"""
def binreadline(device // :stdio) do
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
other -> other
end
end
@doc """
@@ -66,12 +100,17 @@ defmodule IO do
"""
def write(device // :stdio, item) do
Erlang.io.put_chars map_dev(device), to_iodata(item)
:io.put_chars map_dev(device), to_iodata(item)
end
def print(device // :stdio, item) do
IO.puts "IO.print is deprecated in favor of IO.write"
Erlang.io.put_chars map_dev(device), to_iodata(item)
@doc """
Writes the given argument to the given device
as a binary, no unicode conversion happens.
Check `write/2` for more information.
"""
def binwrite(device // :stdio, item) do
:file.write map_dev(device), to_iodata(item)
end
@doc """
@@ -81,16 +120,16 @@ defmodule IO do
"""
def puts(device // :stdio, item) do
erl_dev = map_dev(device)
Erlang.io.put_chars erl_dev, to_iodata(item)
Erlang.io.nl(erl_dev)
:io.put_chars erl_dev, to_iodata(item)
:io.nl(erl_dev)
end
@doc """
Inspects and writes the given argument to the device
followed by a new line. Returns the item given.
"""
def inspect(device // :stdio, item) do
puts device, Kernel.inspect(item)
def inspect(device // :stdio, item, opts // []) do
puts device, Binary.Inspect.inspect(item, opts)
item
end
@@ -106,7 +145,7 @@ defmodule IO do
NFS file system.
"""
def getb(device // :stdio, prompt, count // 1) do
Erlang.io.get_chars(map_dev(device), to_iodata(prompt), count)
:io.get_chars(map_dev(device), to_iodata(prompt), count)
end
@doc """
@@ -122,7 +161,7 @@ defmodule IO do
NFS file system.
"""
def gets(device // :stdio, prompt) do
Erlang.io.get_line(map_dev(device), to_iodata(prompt))
:io.get_line(map_dev(device), to_iodata(prompt))
end
# Map the Elixir names for standard io and error to Erlang names
+775 -382
View File
File diff suppressed because it is too large Load Diff
+82 -52
View File
@@ -1,72 +1,105 @@
defrecord Kernel.CLI.Config, commands: [], output: ".",
compile: [], halt: true, compiler_options: []
defmodule Kernel.CLI do
@moduledoc false
@moduledoc """
Module responsible for controlling Elixir's CLI
"""
import Exception, only: [format_stacktrace: 1]
defrecord Config, commands: [], output: ".",
compile: [], halt: true, compiler_options: []
# Invoked directly from erlang boot process. It parses all argv
# options and execute them in the order they are specified.
def process_argv(options) do
{ config, argv } = process_options(options, Kernel.CLI.Config.new)
# This is the API invoked by Elixir boot process.
@doc false
def main(options) do
{ config, argv } = process_argv(options, Kernel.CLI.Config.new)
argv = lc arg inlist argv, do: list_to_binary(arg)
Erlang.gen_server.call(:elixir_code_server, { :argv, argv })
:gen_server.call(:elixir_code_server, { :argv, argv })
all_commands = List.reverse(config.commands)
run fn ->
Enum.map Enum.reverse(config.commands), process_command(&1, config)
:gen_server.cast(:elixir_code_server, :finished)
end, config.halt
end
@doc """
Wait until the CLI finishes procesing options.
"""
def wait_until_finished do
case :gen_server.call(:elixir_code_server, { :wait_until_finished, self }) do
:wait ->
receive do
{ :elixir_code_server, :finished } -> :ok
end
:ok -> :ok
end
end
@doc """
Runs the given function by catching any failure
and printing them to stdout. `at_exit` hooks are
also invoked before exiting.
This function is used by Elixir's CLI and also
by escripts generated by Elixir.
"""
def run(fun, halt // true) do
try do
Enum.map all_commands, process_command(&1, config)
if config.halt do
fun.()
if halt do
at_exit(0)
halt(0)
System.halt(0)
end
rescue
exception ->
at_exit(1)
stacktrace = System.stacktrace
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace(stacktrace)
halt(1)
IO.puts Exception.formatted_stacktrace(trace)
System.halt(1)
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
halt(reason)
System.halt(reason)
:exit, :normal ->
at_exit(0)
halt(0)
System.halt(0)
kind, reason ->
at_exit(1)
stacktrace = System.stacktrace
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
print_stacktrace(stacktrace)
halt(1)
IO.puts Exception.formatted_stacktrace(trace)
System.halt(1)
end
end
## Private
defp at_exit(status) do
hooks = Erlang.gen_server.call(:elixir_code_server, :at_exit)
hooks = :gen_server.call(:elixir_code_server, :flush_at_exit)
lc hook inlist hooks do
try do
hook.(status)
rescue
exception ->
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace(System.stacktrace)
IO.puts Exception.formatted_stacktrace(trace)
catch
kind, reason ->
trace = System.stacktrace
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
print_stacktrace(System.stacktrace)
IO.puts Exception.formatted_stacktrace(trace)
end
end
# If an at_exit callback adds a
# new hook we need to invoke it.
unless hooks == [], do: at_exit(status)
end
defp invalid_option(option) do
IO.puts(:stderr, "Unknown option #{list_to_binary(option)}")
halt(1)
System.halt(1)
end
defp shared_option?(list, config, callback) do
@@ -78,10 +111,6 @@ defmodule Kernel.CLI do
end
end
defp print_stacktrace(stacktrace) do
Enum.each stacktrace, fn s -> IO.puts :stderr, " #{format_stacktrace(s)}" end
end
# Process shared options
defp process_shared(['-v'|t], config) do
@@ -94,7 +123,7 @@ defmodule Kernel.CLI do
end
defp process_shared(['-e',h|t], config) do
process_shared t, config.prepend_commands [eval: h]
process_shared t, config.update_commands [{:eval,h}|&1]
end
defp process_shared(['-pa',h|t], config) do
@@ -110,14 +139,14 @@ defmodule Kernel.CLI do
defp process_shared(['-r',h|t], config) do
h = list_to_binary(h)
config = Enum.reduce File.wildcard(h), config, fn path, config ->
config.prepend_commands [require: path]
config.update_commands [{:require,path}|&1]
end
process_shared t, config
end
defp process_shared(['-pr',h|t], config) do
h = list_to_binary(h)
process_shared t, config.prepend_commands [parallel_require: h]
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
@@ -130,35 +159,36 @@ defmodule Kernel.CLI do
# Process init options
def process_options(['--'|t], config) do
defp process_argv(['--'|t], config) do
{ config, t }
end
def process_options(['--compile'|t], config) do
defp process_argv(['--compile'|t], config) do
process_compiler t, config
end
def process_options(['-S',h|t], config) do
defp process_argv(['-S',h|t], config) do
exec = System.find_executable(h)
if exec do
{ config.prepend_commands([require: list_to_binary(exec)]), t }
bin = list_to_binary(exec)
{ config.update_commands([{:require,bin}|&1]), t }
else
IO.puts(:stderr, "Could not find executable #{h}")
halt(1)
System.halt(1)
end
end
def process_options([h|t] = list, config) do
defp process_argv([h|t] = list, config) do
case h do
'-' ++ _ ->
shared_option? list, config, process_options(&1, &2)
shared_option? list, config, process_argv(&1, &2)
_ ->
h = list_to_binary(h)
{ config.prepend_commands([require: h]), t }
bin = list_to_binary(h)
{ config.update_commands([{:require,bin}|&1]), t }
end
end
def process_options([], config) do
defp process_argv([], config) do
{ config, [] }
end
@@ -173,15 +203,15 @@ defmodule Kernel.CLI do
end
defp process_compiler(['--no-docs'|t], config) do
process_compiler t, config.merge_compiler_options(docs: false)
process_compiler t, config.update_compiler_options([{:docs,false}|&1])
end
defp process_compiler(['--debug-info'|t], config) do
process_compiler t, config.merge_compiler_options(debug_info: true)
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
process_compiler t, config.merge_compiler_options(ignore_module_conflict: true)
process_compiler t, config.update_compiler_options([{:ignore_module_conflict,true}|&1])
end
defp process_compiler([h|t] = list, config) do
@@ -191,18 +221,18 @@ defmodule Kernel.CLI do
_ ->
h = list_to_binary(h)
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.prepend_compile [pattern]
process_compiler t, config.update_compile [pattern|&1]
end
end
defp process_compiler([], config) do
{ config.prepend_commands([compile: config.compile]), [] }
{ config.update_commands([{:compile,config.compile}|&1]), [] }
end
# Process commands
defp process_command({:eval, expr}, _config) when is_list(expr) do
Erlang.elixir.eval(expr, [])
:elixir.eval(expr, [])
end
defp process_command({:require, file}, _config) when is_binary(file) do
@@ -211,7 +241,7 @@ defmodule Kernel.CLI do
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = File.wildcard(pattern)
files = List.uniq(files)
files = Enum.uniq(files)
files = Enum.filter files, File.regular?(&1)
Kernel.ParallelRequire.files(files)
end
@@ -220,7 +250,7 @@ defmodule Kernel.CLI do
File.mkdir_p(config.output)
files = Enum.map patterns, File.wildcard(&1)
files = List.uniq(List.concat(files))
files = Enum.uniq(List.concat(files))
files = Enum.filter files, File.regular?(&1)
Code.compiler_options(config.compiler_options)
+2 -2
View File
@@ -5,12 +5,12 @@ defmodule Kernel.ErrorHandler do
def undefined_function(module, fun, args) do
ensure_loaded(module)
Erlang.error_handler.undefined_function(module, fun, args)
:error_handler.undefined_function(module, fun, args)
end
def undefined_lambda(module, fun, args) do
ensure_loaded(module)
Erlang.error_handler.undefined_lambda(module, fun, args)
:error_handler.undefined_lambda(module, fun, args)
end
defp ensure_loaded(module) do
+17 -12
View File
@@ -1,5 +1,5 @@
defmodule Kernel.ParallelCompiler do
alias Erlang.orddict, as: Orddict
alias :orddict, as: OrdDict
@moduledoc """
A module responsible for compiling files in parallel.
@@ -19,7 +19,7 @@ defmodule Kernel.ParallelCompiler do
with its name can be optionally given as argument.
"""
def files(files, callback // default_callback) do
files_to_path(files, nil, callback)
spawn_compilers(files, nil, callback)
end
@doc """
@@ -27,6 +27,10 @@ defmodule Kernel.ParallelCompiler do
Read files/2 for more information.
"""
def files_to_path(files, path, callback // default_callback) when is_binary(path) do
spawn_compilers(files, path, callback)
end
defp spawn_compilers(files, path, callback) do
Code.ensure_loaded(Kernel.ErrorHandler)
spawn_compilers(files, path, callback, [], [], [])
end
@@ -42,14 +46,15 @@ defmodule Kernel.ParallelCompiler do
parent = self()
child = spawn_link fn ->
Process.put(:elixir_compiler_pid, parent)
Process.flag(:error_handler, Kernel.ErrorHandler)
:erlang.put(:elixir_compiler_pid, parent)
:erlang.put(:elixir_ensure_compiled, true)
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
try do
if output do
Erlang.elixir_compiler.file_to_path(h, output)
:elixir_compiler.file_to_path(h, output)
else
Erlang.elixir_compiler.file(h)
:elixir_compiler.file(h)
end
parent <- { :compiled, self(), h }
catch
@@ -80,26 +85,26 @@ defmodule Kernel.ParallelCompiler do
receive do
{ :compiled, child, file } ->
callback.(file)
new_queued = List.keydelete(queued, child, 1)
new_queued = List.keydelete(queued, child, 0)
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, child, 1)
new_waiting = List.keydelete(waiting, child, 0)
spawn_compilers(files, output, callback, new_waiting, new_queued, 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)
{ :waiting, child, on } ->
new_waiting = Orddict.store(child, on, waiting)
new_waiting = OrdDict.store(child, on, waiting)
spawn_compilers(files, output, callback, new_waiting, queued, result)
{ :failure, child, kind, reason, stacktrace } ->
extra = if match?({^child, module}, List.keyfind(waiting, child, 1)) do
extra = if match?({^child, module}, List.keyfind(waiting, child, 0)) do
" (undefined module #{inspect module})"
end
{^child, file} = List.keyfind(queued, child, 1)
{^child, file} = List.keyfind(queued, child, 0)
IO.puts "== Compilation error on file #{file}#{extra} =="
Erlang.erlang.raise(kind, reason, stacktrace)
:erlang.raise(kind, reason, stacktrace)
end
end
+5 -6
View File
@@ -27,10 +27,9 @@ defmodule Kernel.ParallelRequire do
child = spawn_link fn ->
try do
new = Code.require_file(h)
result = new ++ result
new = Code.require_file(h)
callback.(h)
parent <- { :required, self }
parent <- { :required, self, new }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
@@ -42,10 +41,10 @@ defmodule Kernel.ParallelRequire do
defp wait_for_messages(files, waiting, callback, result) do
receive do
{ :required, child } ->
spawn_requires(files, List.delete(waiting, child), callback, result)
{ :required, child, new } ->
spawn_requires(files, List.delete(waiting, child), callback, (new || []) ++ result)
{ :failure, _child, kind, reason, stacktrace } ->
Erlang.erlang.raise(kind, reason, stacktrace)
:erlang.raise(kind, reason, stacktrace)
end
end
end
+374
View File
@@ -0,0 +1,374 @@
# This is an optimization Elixir runs on function clauses.
# Whenever a variables matches against a record (a tagged
# tuple), this information is stored in order to optimize
# record calls.
defmodule Kernel.RecordRewriter do
@moduledoc false
def optimize_clause(clause) do
optimize_clause(clause, :orddict.new)
end
## Clause
defp optimize_clause({ :clause, line, args, guards, body }, dict) do
{ args, dict } = optimize_args(args, dict)
{ body, dict, res } = optimize_body(body, dict, [])
{ { :clause, line, args, guards, body }, dict, res }
end
defp optimize_args(args, dict) do
Enum.map_reduce args, dict, fn(arg, acc) ->
{ new_arg, new_acc, _res } = optimize_expr(arg, acc)
{ new_arg, new_acc }
end
end
defp optimize_body([], dict, _acc) do
{ [], dict, nil }
end
defp optimize_body([h], dict, acc) do
{ new_expr, new_dict, new_res } = optimize_expr(h, dict)
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
end
defp optimize_body([h|t], dict, acc) do
{ new_expr, new_dict, _ } = optimize_expr(h, dict)
optimize_body(t, new_dict, [new_expr|acc])
end
## Record helpers
defp record_fields(record) do
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
try do
fields = lc { k, _ } inlist record.__record__(:fields), do: k
optimizable = record.__record__(:optimizable)
{ fields, optimizable }
rescue
[UndefinedFunctionError, FunctionClauseError] -> nil
end
end
end
defp record_field_info(function) do
case atom_to_list(function) do
'update_' ++ field -> { :update, list_to_atom(field) }
_ -> { :accessor, function }
end
end
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)
end
end
nil -> nil
end
end
defp optimize_call(_line, _res, _left, _right, _args) do
nil
end
defp optimize_call(line, _res, :accessor, _field, index, left, []) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
[{ :integer, 0, index + 2 }, left]
}
{ call, nil }
end
defp optimize_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]
}
{ call, res }
end
defp optimize_call(line, res, :update, field, _index, left, args) do
call = { :call, line,
{ :remote, line, left, { :atom, 0, :"update_#{field}" } },
args
}
{ call, res }
end
## Expr
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, dict) do
{ left, dict, res } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ args, dict } = optimize_args(args, dict)
case optimize_call(call_line, res, left, right, args) do
{ call, call_res } ->
{ call, dict, call_res }
nil ->
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
end
end
defp optimize_expr({ :call, line, expr, args }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
{ args, dict } = optimize_args(args, dict)
{ { :call, line, expr, args }, dict, nil }
end
defp optimize_expr({ :match, line, left, right }, dict) do
{ left, dict, left_res } = optimize_expr(left, dict)
{ right, dict, right_res } = optimize_expr(right, dict)
match = { :match, line, left, right }
if left_res do
dict = assign_vars(extract_vars(right, []), dict, left_res)
end
if right_res do
dict = assign_vars(extract_vars(left, []), dict, right_res)
end
{ match, dict, right_res || left_res }
end
defp optimize_expr({ :op, line, op, left, right }, dict) do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ { :op, line, op, left, right }, dict, nil }
end
defp optimize_expr({ :op, line, op, expr }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
{ { :op, line, op, expr }, dict, nil }
end
defp optimize_expr({ :bin, line, elements }, dict) do
{ elements, dict } = optimize_args(elements, dict)
{ { :bin, line, elements }, dict, nil }
end
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
end
defp optimize_expr({ :cons, line, left, right }, dict) do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ { :cons, line, left, right }, dict, nil }
end
defp optimize_expr({ :block, line, args }, dict) do
{ args, dict, res } = optimize_body(args, dict, [])
{ { :block, line, args }, dict, res }
end
defp optimize_expr({ :tuple, line, args }, dict) do
{ args, dict, args_res } = optimize_tuple_args(args, dict)
args_res = if Enum.any?(args_res), do: args_res, else: nil
res =
case args do
[{ :atom, _, atom }|t] -> atom
_ -> nil
end
{ { :tuple, line, args }, dict, { res, args_res } }
end
defp optimize_expr({ :var, _, name } = var, dict) do
case :orddict.find(name, dict) do
{ :ok, res } -> { var, dict, res }
:error -> { var, dict, nil }
end
end
defp optimize_expr({ :case, line, expr, clauses }, dict) do
{ expr, dict, _ } = optimize_expr(expr, dict)
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :case, line, expr, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses }, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :receive, line, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
{ after_key, dict, _ } = optimize_expr(after_key, dict)
{ after_value, dict, res } = optimize_body(after_value, dict, [])
dict = join_dict(tuples, dict)
res = join_result(tuples, res)
{ { :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
{ 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 }
end
defp optimize_expr({ :fun, line, { :function, module, name, arity } }, dict) do
{ module, dict, _ } = optimize_expr(module, dict)
{ name, dict, _ } = optimize_expr(name, dict)
{ arity, dict, _ } = optimize_expr(arity, dict)
{ { :fun, line, { :function, module, name, arity } }, dict, nil }
end
defp optimize_expr({ :fun, line, { :clauses, clauses } }, dict) do
clauses = lc clause inlist clauses do
{ clause, _, _ } = optimize_clause(clause, dict)
clause
end
{ { :fun, line, { :clauses, clauses } }, dict, nil }
end
defp optimize_expr({ comprehension, line, expr, args }, dict) when comprehension in [:lc, :bc] do
{ args, new_dict } = optimize_args(args, dict)
{ expr, _, _ } = optimize_expr(expr, new_dict)
{ { comprehension, line, expr, args }, dict, nil }
end
defp optimize_expr({ generate, line, left, right }, dict) when generate in [:generate, :b_generate] do
{ left, dict, _ } = optimize_expr(left, dict)
{ right, dict, _ } = optimize_expr(right, dict)
{ { generate, line, left, right }, dict, nil }
end
defp optimize_expr(other, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
{ other, dict, nil }
end
## Helpers
defp optimize_tuple_args(args, dict) do
{ final_args, { final_dict, final_acc } } =
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
{ new_arg, new_acc, res } = optimize_expr(arg, acc_dict)
{ new_arg, { new_acc, [res|acc_res] } }
end
{ final_args, final_dict, Enum.reverse(final_acc) }
end
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
assign_vars t, assign_nested_vars(key, dict, value), res
end
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
dict =
case :orddict.find(key, dict) do
{ :ok, ^res } ->
dict
{ :ok, { ^value, _ } } ->
:orddict.store(key, { value, nil }, dict)
{ :ok, _ } ->
# We are overriding a type of an existing variable,
# which means the source code is invalid.
:orddict.store(key, nil, dict)
:error ->
:orddict.store(key, res, dict)
end
assign_vars t, dict, res
end
defp assign_vars([_|t], dict, res) do
assign_vars t, dict, res
end
defp assign_vars([], dict, _res) do
dict
end
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
end
defp assign_nested_vars([], dict, []) do
dict
end
defp extract_vars({ :match, _, left, right }, vars) do
vars = extract_vars(right, vars)
extract_vars(left, vars)
end
defp extract_vars({ :var, _, name }, vars) do
[name|vars]
end
defp extract_vars({ :tuple, _, args }, vars) do
[Enum.map(args, extract_vars(&1, []))|vars]
end
defp extract_vars(_, vars) do
vars
end
defp join_dict([{ _, dict, _ }|t]) do
join_dict(t, dict)
end
defp join_dict([{ _, dict, _ }|t], other) do
other = Enum.reduce other, other, fn
{ key, { value, _ } = res }, acc ->
case :orddict.find(key, dict) do
{ :ok, ^res } -> acc
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
{ :ok, _ } -> :orddict.store(key, nil, acc)
:error -> :orddict.erase(key, acc)
end
end
join_dict(t, other)
end
defp join_dict([], other) do
other
end
defp join_result([{ _, _, res }|t]) do
join_result(t, res)
end
defp join_result([{ _, _, res }|t], res) do
join_result(t, res)
end
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
join_result(t, { res, nil })
end
defp join_result([{ _, _, _ }|_], _res) do
nil
end
defp join_result([], res) do
res
end
end
+67 -31
View File
@@ -56,7 +56,7 @@ defmodule Kernel.SpecialForms do
alias MyKeyword, as: Keyword
end
In the example above, we have set up `MyOrdict` to be alias
In the example above, we have set up `MyKeyword` to be alias
as `Keyword`. So now, any reference to `Keyword` will be
automatically replaced by `MyKeyword`.
@@ -118,30 +118,38 @@ defmodule Kernel.SpecialForms do
## Examples
If you want to use the `values` function from `Keyword` several times
in your module and you don't want to always type `Keyword.values`,
you can simply import it:
If you are using several functions from a given module, you can
import those functions and reference them as local functions,
for example:
defmodule Math do
import Keyword, only: [values: 1]
import List
flatten([1,[2],3]) #=> [1,2,3]
def some_function do
# call values(orddict)
end
end
## Selector
In this case, we are importing only the function `values` (with arity 1)
from `Keyword`. Although `only` is optional, its usage is recommended.
`except` could also be given as an option. If no option is given, all
functions and macros are imported.
By default, Elixir imports functions and macros from the given
module, except the ones starting with underscore (which are
usually callbacks):
In case you want to import only functions or macros, you can pass a
first argument selecting the scope:
import List
import :macros, MyMacros
A developer can change this behavior to include all macros and
functions, regardless if it starts with underscore, by passing
`:all` as first argument:
And you can then use `only` or `except` to filter the macros being
included.
import :all, List
It can also be customized to import only all functions or
all macros:
import :functions, List
import :macros, List
Alternatively, Elixir allows a developer to specify `:only`
or `:except` as a fine grained control on what to import (or
not):
import List, only: [flatten: 1]
## Lexical scope
@@ -161,10 +169,10 @@ defmodule Kernel.SpecialForms do
end
end
In the example above, we imported macros from `MyMacros`, replacing
the original `if/2` implementation by our own during that
specific function. All other functions in that module will still
be able to use the original one.
In the example above, we imported macros from `MyMacros`,
replacing the original `if/2` implementation by our own
during that specific function. All other functions in that
module will still be able to use the original one.
## Alias/Require shortcut
@@ -268,7 +276,7 @@ defmodule Kernel.SpecialForms do
a = 10
NoHygiene.interference
a #=> 11
a #=> 1
Notice that aliases are not hygienic in Elixir, ambiguity
must be solved by prepending Elixir:
@@ -298,13 +306,13 @@ defmodule Kernel.SpecialForms do
is defining new functions. Consider this example:
defmodule MyServer do
use GenServer.Behavior
use GenServer.Behaviour
end
`GenServer.Behavior` defines new functions in our `MyServer` module.
`GenServer.Behaviour` defines new functions in our `MyServer` module.
However, if there is an exception in any of these functions, we want
the stacktrace to point to the `GenServer.Behavior` and not the line
that calls `use GenServer.Behavior`. For this reason, there is an
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:
@@ -315,9 +323,9 @@ defmodule Kernel.SpecialForms do
end
It is important to warn though that `location: :keep` evaluates the
code as if it was defined inside `GenServer.Behavior` file, in
code as if it was defined inside `GenServer.Behaviour` file, in
particular, the macro `__FILE__` will always point to
`GenServer.Behavior` file.
`GenServer.Behaviour` file.
"""
defmacro quote(opts, do: contents)
@@ -440,11 +448,39 @@ defmodule Kernel.SpecialForms do
@doc """
This is the special form used to hold aliases information.
At compilation time, it is usually compiled to an atom:
It is usually compiled to an atom:
quote do: Foo.Bar
{ :__aliases__, 0, [: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,[]}
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.
On the other hand, aliases holds some properties:
1) The head element of aliases can be any term;
2) The tail elements of aliases are guaranteed to always be atoms;
3) When the head element of aliases is the atom :Elixir, no expansion happen;
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]}
Since `some_var` is not available at compilation time, the compiler
expands such expression to:
Module.concat [some_var, Foo]
"""
defmacro __aliases__(args)
end
+541 -103
View File
@@ -1,54 +1,518 @@
defmodule Kernel.Typespec do
@moduledoc """
This is the module that converts Elixir typespecs
to Erlang typespecs syntax. Everytime @spec, @type
and @typep are used they proxy to the functions
in this module.
Holds macros and functions for working with typespecs.
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
`@callback` available in modules are handled by the equivalent
macros defined by this module.
## Defining a type
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
For more details, see documentation for deftype, deftypep and defopaque in
Kernel.Typespec
## Defining a specification
@spec function_name(type, type) :: type
@callback function_name(type, type) :: type
For more details, see documentation for defspec and defcallback in
Kernel.Typespec
## Types
The type syntax provided by Elixir is fairly similar to the one
in Erlang.
Most of the built-in types provided in Erlang (for example, `pid()`)
are expressed the same way: `pid()` or simply `pid`. Parametrized types
are also supported: `list(integer())` and so are remote types: `Enum.t`.
Certain data type shortcuts ([...], <<>> and {...}) are supported as well.
Main differences lie in how bit strings and functions are defined:
### Bit Strings
Bit string with a base size of 3:
<<_ :: 3>>
Bit string with a unit size of 8:
<<_ :: _ * 8>>
### Functions
Any function:
(fun(...) -> any)
or
((...) -> any)
or
(... -> any)
Function with arity of zero:
(fun() -> type)
or
(() -> type)
Function with some arity:
(fun(type, type) -> type)
or
((type, type) -> type)
or
(type, type -> type)
## 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
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.
If you want to refer to the "string" type (the one operated by functions in the
String module), use `String.t()` type instead.
See http://www.erlang.org/doc/reference_manual/typespec.html
for more information.
"""
defmacro deftype(name, options // []) do
_deftype(name, true, __CALLER__, options)
end
@doc """
Defines a type.
This macro is the one responsible to handle the attribute @type.
defmacro deftypep(name) do
_deftype(name, false, __CALLER__, [])
end
## Examples
defmacro defspec(spec, block) do
_defspec(:spec, __CALLER__, spec, block)
end
@type my_type :: atom
defmacro defcallback(spec, block) do
_defspec(:callback, __CALLER__, spec, block)
"""
defmacro deftype(type) do
quote do
Kernel.Typespec.deftype(:type, (quote line: :keep, do: unquote(type)), __ENV__)
end
end
@doc """
Get the types defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
Defines an opaque type.
This macro is the one responsible to handle the attribute @opaque.
## Examples
@opaque my_type :: atom
"""
def get_types(module) do
Module.read_attribute(module, :type) ++ Module.read_attribute(module, :opaque)
defmacro defopaque(type) do
quote do
Kernel.Typespec.deftype(:opaque, (quote line: :keep, do: unquote(type)), __ENV__)
end
end
@doc """
Get the specs defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
Defines a private type.
This macro is the one responsible to handle the attribute @typep.
## Examples
@typep my_type :: atom
"""
def get_specs(module) do
specs = :ets.tab2list(spec_table_for(module))
keys = :lists.ukeysort(1, specs)
lc { k, _ } inlist keys, do: { k, :proplists.append_values(k, specs) }
defmacro deftypep(type) do
quote do
Kernel.Typespec.deftype(:typep, (quote line: :keep, do: unquote(type)), __ENV__)
end
end
## Typespec conversion
@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__)
end
end
@doc """
Defines a spec.
This macro is the one responsible to handle the attribute @spec.
## Examples
@spec add(number, number) :: number
"""
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__)
end
end
@doc """
Defines a callback.
This macro is the one responsible to handle the attribute @callback.
## Examples
@callback add(number, number) :: number
"""
defmacro defcallback(spec) do
quote do
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote(spec)), __ENV__)
end
end
## Helpers
@doc """
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
"""
def define_type(module, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
{ kind, export } =
case kind do
:type -> { :type, true }
:typep -> { :type, false }
:opaque -> { :opaque, true }
end
Module.compile_typespec module, kind, type
if export, do:
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
type
end
@doc """
Defines a `spec` by receiving Erlang's typespec.
"""
def define_spec(module, tuple, definition) do
Module.compile_typespec module, :spec, { tuple, definition }
end
@doc """
Defines a `callback` by receiving Erlang's typespec.
"""
def define_callback(module, tuple, definition) do
Module.compile_typespec module, :callback, { tuple, definition }
end
@doc """
Returns true if the current module defines a given type
(private, opaque or not). This function is only available
for modules being compiled.
"""
def defines_type?(module, name, arity) do
finder = match?({ ^name, _, vars } when length(vars) == arity, &1)
:lists.any(finder, Module.get_attribute(module, :type)) or
:lists.any(finder, Module.get_attribute(module, :opaque))
end
@doc """
Returns true if the current module defines a given spec.
This function is only available for modules being compiled.
"""
def defines_spec?(module, name, arity) do
tuple = { name, arity }
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :spec))
end
@doc """
Returns true if the current module defines a callback.
This function is only available for modules being compiled.
"""
def defines_callback?(module, name, arity) do
tuple = { name, arity }
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :callback))
end
@doc """
Converts a spec clause back to Elixir AST.
"""
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)] }
end
def spec_to_ast(name, { :type, line, :fun, [] }) do
{ :::, line, [{ name, 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)] }
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)] }
end
@doc """
Converts a type clause back to Elixir AST.
"""
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] }
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
end
def type_to_ast({ name, type, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
end
@doc """
Returns all types available from the beam.
It is returned as a list of tuples where the first
element is the type (`:typep`, `:type` and `:opaque`).
The module has to have a corresponding beam file
on the file system.
"""
def beam_types(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
exported_types = List.flatten(exported_types)
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
cond do
kind == :opaque -> { :opaque, type }
List.member?(exported_types, { name, length(args) }) -> { :type, type }
true -> { :typep, type }
end
end
_ ->
[]
end
end
@doc """
Returns all specs available from the beam.
It is returned as a list of tuples where the first
element is spec name and arity and the second is the spec.
The module has to have a corresponding beam file
on the file system.
"""
def beam_specs(module) do
from_abstract_code(module, :spec)
end
@doc """
Returns all callbacks available from the beam.
It is returned as a list of tuples where the first
element is spec name and arity and the second is the spec.
The module has to have a corresponding beam file
on the file system.
"""
def beam_callbacks(module) do
from_abstract_code(module, :callback)
end
defp from_abstract_code(module, kind) do
case abstract_code(module) do
{ :ok, abstract_code } ->
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
_ ->
[]
end
end
defp abstract_code(module) do
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
{:ok, { _, [{ :abstract_code, { raw_abstract_v1, abstract_code } }] } } ->
{ :ok, abstract_code }
_ ->
[]
end
end
defp abstract_code_beam(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
end
end
defp abstract_code_beam(binary) when is_binary(binary) do
binary
end
## Macro callbacks
@doc false
def deftype(kind, { :::, _, [type, definition] }, caller) do
do_deftype(kind, type, definition, caller)
end
def deftype(kind, type, caller) do
do_deftype(kind, type, { :term, caller.line, nil }, caller)
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
args =
if is_atom(args) do
[]
else
lc(arg inlist args, do: variable(arg))
end
vars = lc { :var, _, var } inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc { :var, _, _ } = var inlist args, do: var
type = { name, spec, vars }
define_type(caller.module, kind, type)
end
@doc false
def defspec(type, {:::, _, [{ :when, _, [{ name, line, 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)] }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
def defspec(type, {:::, _, [{ name, line, args }, return]}, caller) do
if is_atom(args), do: args = []
spec = { :type, line, :fun, fn_args(line, 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
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
[{ name, { :type, line, :constraint, contraints } }]
end
defp guard_to_constraints({ :and, _, [left, right] }, caller) do
guard_to_constraints(left, caller) ++ guard_to_constraints(right, caller)
end
## To AST conversion
defp typespec_to_ast({ :type, line, :tuple, :any }) do
typespec_to_ast({:type, line, :tuple, []})
end
defp typespec_to_ast({ :type, line, :tuple, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :{}, line, args }
end
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
cond do
arg2 == 0 ->
quote line: line, do: <<_ :: unquote(arg1)>>
arg1 == 0 ->
quote line: line, do: <<_ :: _ * unquote(arg2)>>
true ->
quote line: line, do: <<_ :: unquote(arg1) * unquote(arg2)>>
end
end
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
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)}] }
end
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
{ :"->", 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, name, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ name, line, args }
end
defp typespec_to_ast({ :var, line, var }) do
var =
case atom_to_binary(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
end
{ var, line, nil }
end
# special shortcut(s)
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
typespec_to_ast({:type, line, :char_list, []})
end
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 }
end
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
{ :::, line, [typespec_to_ast(var), typespec_to_ast(type)] }
end
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] })
end
defp typespec_to_ast({:type, _, :any}) do
quote do: ...
end
defp typespec_to_ast({:paren_type, _, [type]}) do
typespec_to_ast(type)
end
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
atom
end
defp typespec_to_ast(other), do: other
## From AST conversion
# Handle unions
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
exprs = List.reverse(collect_union(exprs))
exprs = :lists.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line, :union, union }
end
@@ -58,11 +522,11 @@ defmodule Kernel.Typespec do
{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}
end
defp typespec({:<<>>, line, [{:|, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
defp typespec({:<<>>, line, [{:::, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, 0}, {:integer, line2, unit}]}
end
defp typespec({:<<>>, line, [{:|, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
defp typespec({:<<>>, line, [{:::, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, base}, {:integer, line2, 0}]}
end
@@ -71,15 +535,32 @@ defmodule Kernel.Typespec do
typespec({:range, line, args}, vars, caller)
end
# Handle aliases
# Handle special forms
defp typespec({:__MODULE__, _, atom}, vars, caller) when is_atom(atom) do
typespec(caller.module, vars, caller)
end
defp typespec({:__aliases__, _, _} = alias, vars, caller) do
atom = Macro.expand alias, caller
typespec(atom, vars, caller)
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)
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 }
end
# Handle type operator
defp typespec({:"::", line, [var, expr] }, vars, caller) do
left = typespec(var, [elem(var,1)|vars], caller)
left = typespec(var, [elem(var, 0)|vars], caller)
right = typespec(expr, vars, caller)
{ :ann_type, line, [left, right] }
end
@@ -89,10 +570,17 @@ defmodule Kernel.Typespec do
{ :op, line, op, {:integer, line, integer} }
end
# Handle access macro
defp typespec({{:., line, [Kernel, :access]}, line1, [target, args]}, vars, caller) do
access = {{:., line, [Kernel, :access]}, line1,
[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
remote = Macro.expand remote, caller
unless is_atom(remote), do: raise(ArgumentError, message: "Invalid remote in typespec")
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)
end
@@ -110,28 +598,27 @@ defmodule Kernel.Typespec do
{ :type, line, :tuple, args }
end
# Handle funs
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
args =
case List.reverse(arguments) do
[[{:do,h}]|t] -> fn_args(line, List.reverse(t), h, vars, caller)
[] -> []
_ -> [fn_args(line, arguments, vars, caller)]
end
{ :type, line, :fun, args }
end
# Handle variables or local calls
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
{ :var, line, name }
{ :var, line, name }
else
typespec({name, line, []}, vars, caller)
end
end
# Handle local calls
defp typespec({:string, line, 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)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, :string, arguments }
end
defp typespec({:char_list, _line, arguments}, vars, caller) do
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
end
defp typespec({name, line, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, name, arguments }
@@ -155,7 +642,7 @@ defmodule Kernel.Typespec do
end
defp typespec(l, _, _) when is_list(l) do
raise(ArgumentError, message: "Unexpected list #{inspect l}")
raise ArgumentError, message: "Unexpected list #{inspect l}"
end
defp typespec(t, vars, caller) when is_tuple(t) do
@@ -174,7 +661,10 @@ defmodule Kernel.Typespec do
defp collect_union(v), do: [v]
defp fn_args(line, args, return, vars, caller) do
[fn_args(line, args, vars, caller), typespec(return, vars, caller)]
case [fn_args(line, args, vars, caller), typespec(return, vars, caller)] do
[{:type,_,:any},{:type,_,:any,[]}] -> []
x -> x
end
end
defp fn_args(line, [{:"...", _, _}], _vars, _caller) do
@@ -186,58 +676,6 @@ defmodule Kernel.Typespec do
{ :type, line, :product, args }
end
def _deftype({:"::", _, [name, definition]}, export, caller, opts) do
_deftype(name, definition, export, caller, opts)
end
def _deftype(name, export, caller, opts) do
_deftype(name, { :term, caller.line, nil }, export, caller, opts)
end
defp _deftype({name, _, args}, definition, export, caller, options) do
args = if is_atom(args), do: [], else: lc(arg inlist args, do: variable(arg))
vars = lc {:var, _, var} inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc ({:var, _, _} = var) inlist args, do: var
attr = if options[:opaque], do: :opaque, else: :type
export = if export do
quote do: Module.compile_type(__MODULE__, :export_type, [{name, length(vars)}])
else
nil
end
quote do
name = unquote(name)
spec = unquote(Macro.escape(spec))
vars = unquote(Macro.escape(vars))
type = { name, spec, vars }
Module.compile_type __MODULE__, unquote(attr), type
unquote(export)
{ unquote(attr), type }
end
end
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
code = Macro.escape { {type, { name, length(args) }}, [spec] }
table = spec_table_for(caller.module)
quote do
code = unquote(code)
:ets.insert(unquote(table), code)
code
end
end
defp spec_table_for(module) do
table = list_to_atom Erlang.lists.concat([:s, module])
unless table == :ets.info(table, :name), do:
raise(ArgumentError, message: "cannot manage specs for #{inspect module} because it was already compiled")
table
end
defp variable({name, line, _}) do
{:var, line, name}
end
+152 -62
View File
@@ -2,8 +2,7 @@ defmodule Keyword do
@moduledoc """
A keyword is a list of tuples where the first element
of the tuple is an atom and the second element can be
any value. The list is sorted by the first element of
each tuple.
any value.
A keyword may have duplicated keys, so it is not strictly
a dictionary. However most of the functions in this module
@@ -12,23 +11,44 @@ defmodule Keyword do
key, regardless if duplicated entries exist. Similarly,
`Keyword.put` and `Keyword.delete` ensure all duplicated
entries for a given key are removed when invoked.
This module uses `==` as operator to check if two keys
are equal or not.
"""
@type key :: atom
@type value :: any
@type t :: [{key, value}]
@doc """
Creates a Keyword from enum. Differently from `Keyword.new`
that behaves as a dict, `Keyword.from_enum` do not remove
which behaves as a dict, `Keyword.from_enum` do not remove
duplicated entries.
"""
@spec from_enum(Enum.t) :: t
def from_enum(enum) when is_list(enum) do
enum
end
def from_enum(enum) do
Enum.qsort(enum)
Enum.map(enum, fn(x) -> x end)
end
@doc """
Returns an empty keywords list, i.e. an empty list.
Checks if the given argument is a keywords list or not
"""
@spec keyword?(term) :: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir-' ++ _ -> false
_ -> keyword?(rest)
end
end
def keyword?([]), do: true
def keyword?(_other), do: false
@doc """
Returns an empty keyword list, i.e. an empty list.
"""
@spec new :: t
def new do
[]
end
@@ -43,8 +63,9 @@ defmodule Keyword do
#=> [a: 2, b: 1]
"""
@spec new(Enum.t) :: t
def new(pairs) do
Enum.reduce pairs, [], fn {k, v}, keywords ->
Enum.reduce pairs, [], fn { k, v }, keywords ->
put(keywords, k, v)
end
end
@@ -58,7 +79,9 @@ defmodule Keyword do
Keyword.new [:a, :b], fn x -> {x,x} end
#=> [a: :a, b: :b]
"""
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
def new(pairs, transform) do
Enum.reduce pairs, [], fn i, keywords ->
{ k, v } = transform.(i)
@@ -80,12 +103,16 @@ defmodule Keyword do
Keyword.get [a: 1], :a #=> 1
Keyword.get [a: 1], :b #=> nil
Keyword.get [a: 1], :b, 3 #=> 3
"""
def get(keywords, key, default // nil)
def get([{k, _}|_], key, default) when key < k, do: default
def get([{k, _}|d], key, default) when key > k, do: get(d, key, default)
def get([{_, value}|_], _key, _default), do: value
def get([], _, default), do: default
@spec get(t, key) :: value
@spec get(t, key, value) :: value
def get(keywords, key, default // nil) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> default
end
end
@doc """
Gets the value for specific key. If key does not exist,
@@ -95,11 +122,15 @@ defmodule Keyword do
Keyword.get! [a: 1], :a #=> 1
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
"""
def get!([{k, _}|_], key) when key < k, do: raise(Keyword.KeyError, key: key)
def get!([{k, _}|d], key) when key > k, do: get!(d, key)
def get!([{_, value}|_], _key), do: value
def get!([], key), do: raise(Keyword.KeyError, key: key)
@spec get!(t, key) :: value | no_return
def get!(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Gets all values for a specific key.
@@ -110,13 +141,13 @@ defmodule Keyword do
#=> [1,2]
"""
def get_values([{k, _}|_], key) when key < k, do: []
def get_values([{k, _}|d], key) when key > k, do: get_values(d, key)
def get_values([{_, value}|d], key), do: [value|get_values(d, key)]
def get_values([], _), do: []
@spec get_values(t, key) :: [value]
def get_values(keywords, key) when is_atom(key) do
lc { k, v } inlist keywords, key == k, do: v
end
@doc """
Returns all keys from the keywords list. Duplicated
Returns all keys from the keyword list. Duplicated
keys appear duplicated in the final list of keys.
## Examples
@@ -124,6 +155,7 @@ defmodule Keyword do
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
"""
@spec keys(t) :: [key]
def keys(keywords) do
lc { key, _ } inlist keywords, do: key
end
@@ -134,14 +166,16 @@ defmodule Keyword do
## Examples
Keyword.values [a: 1, b: 2] #=> [1,2]
"""
@spec values(t) :: [value]
def values(keywords) do
lc { _, value } inlist keywords, do: value
end
@doc """
Deletes all entries in the keywords list for a specific key.
If the key does not exist, returns the keywords list unchanged.
Deletes all entries in the keyword list for a specific key.
If the key does not exist, returns the keyword list unchanged.
Use `delete_first` to delete just the first entry in case of
duplicated keys.
@@ -149,11 +183,12 @@ defmodule Keyword do
Keyword.delete [a: 1, b: 2], :a #=> [b: 2]
Keyword.delete [b: 2], :a #=> [b: 2]
"""
def delete([{k, _}|_] = keywords, key) when key < k, do: keywords
def delete([{k, _} = e|tail], key) when key > k, do: [e|delete(tail, key)]
def delete([{_, _}|tail], key), do: delete(tail, key)
def delete([], _), do: []
@spec delete(t, key) :: t
def delete(keywords, key) when is_atom(key) do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
end
@doc """
Sets the given `value` under `key`.
@@ -161,45 +196,49 @@ defmodule Keyword do
If a previous value is already stored, all entries are
removed and the value is overriden.
Use `put_other/3` to add a new value for an existing key
without removing previous ones.
## Examples
Keyword.put [a: 1, b: 2], :a, 3
#=> [a: 3, b: 2]
"""
def put([{k, _} = e|keywords], key, value) when key < k and is_atom(key) do
[{key, value},e|keywords]
end
def put([{k, _} = e|keywords], key, value) when key > k do
[e|put(keywords, key, value)]
end
def put([{key, _}|keywords], key, value) when is_atom(key) do
@spec put(t, key, value) :: t
def put(keywords, key, value) when is_atom(key) do
[{key, value}|delete(keywords, key)]
end
def put([], key, value) when is_atom(key) do
[{key, value}]
@doc """
Checks if two keywords are equal. I.e. they contain
the same keys and those keys contain the same values.
## Examples
Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
#=> true
"""
@spec equal?(t, t) :: boolean
def equal?(left, right) do
:lists.sort(left) == :lists.sort(right)
end
@doc """
Merges two keywords lists into one. If they have duplicated
Merges two keyword lists into one. If they have duplicated
entries, the one given as second argument wins.
## Examples
Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
#=> [a:3, b:2, d: 4]
"""
@spec merge(t, t) :: t
def merge(d1, d2) do
merge(d1, d2, fn _k, _v1, v2 -> v2 end)
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
end
@doc """
Merges two keywords lists into one. If they have duplicated
Merges two keyword lists into one. If they have duplicated
entries, the given function is invoked to solve conflicts.
## Examples
@@ -208,33 +247,84 @@ defmodule Keyword do
v1 + v2
end
#=> [a:4, b:2, d: 4]
"""
def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2], fun) when k1 < k2 and is_atom(k1) do
[e1|merge(d1, [e2|d2], fun)]
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(d1, d2, fun) do
do_merge(d2, d1, fun)
end
def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2], fun) when k1 > k2 and is_atom(k2) do
[e2|merge([e1|d1], d2, fun)]
defp do_merge([{ k, v2 }|t], acc, fun) do
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
end
def merge([{k1, v1}|d1], [{k1, v2}|d2], fun) do
[{k1, fun.(k1, v1, v2)}|merge(d1, d2, fun)]
defp do_merge([], acc, _fun) do
acc
end
def merge([], d2, _fun), do: d2
def merge(d1, [], _fun), do: d1
@doc """
Returns whether a given key exists in the given keywords.
### Examples
Keyword.key?([a: 1], :a)
## Examples
Keyword.has_key?([a: 1], :a)
#=> true
Keyword.key?([a: 1], :b)
Keyword.has_key?([a: 1], :b)
#=> false
"""
def key?([{k, _}|_], key) when key < k, do: false
def key?([{k, _}|d], key) when key > k, do: key?(d, key)
def key?([{_, _}|_], _key), do: true
def key?([], _), do: false
@spec has_key?(t, key) :: boolean
def has_key?(keywords, key) when is_atom(key) do
:lists.keymember(key, 1, keywords)
end
@doc """
Updates the key with the given function. If the key does
not exist, raises `KeyError`.
## Examples
Keyword.update([a: 1], :a, &1 * 2)
#=> [a: 2]
Keyword.update([a: 1], :b, &1 * 2)
#=> KeyError
"""
@spec update(t, key, (value -> value)) :: t | no_return
def update([{key, value}|keywords], key, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
def update([{_, _} = e|keywords], key, fun) do
[e|update(keywords, key, fun)]
end
def update([], key, _fun) when is_atom(key) do
raise(KeyError, key: key)
end
@doc """
Updates the key with the given function. If the key does
not exist, inserts the given `initial` value.
## Examples
Keyword.update([a: 1], :a, 13, &1 * 2)
#=> [a: 2]
Keyword.update([a: 1], :b, 11, &1 * 2)
#=> [a: 1, b: 11]
"""
@spec update(t, key, value, (value -> value)) :: t
def update([{key, value}|keywords], key, _initial, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
def update([{_, _} = e|keywords], key, initial, fun) do
[e|update(keywords, key, initial, fun)]
end
def update([], key, initial, _fun) when is_atom(key) do
[{key, initial}]
end
end
+87 -137
View File
@@ -20,7 +20,7 @@ defmodule List do
"""
def concat(list) when is_list(list) do
Erlang.lists.append(list)
:lists.append(list)
end
@doc """
@@ -52,7 +52,22 @@ defmodule List do
"""
def delete(list, item) do
Erlang.lists.delete(item, list)
:lists.delete(item, list)
end
@doc """
Duplicates the given element n times in a list.
## Examples
List.duplicate "hello", 3
#=> ["hello","hello","hello"]
List.duplicate [1,2], 2
#=> [[1,2],[1,2]]
"""
def duplicate(elem, n) do
:lists.duplicate(n, elem)
end
@doc """
@@ -70,11 +85,11 @@ defmodule List do
"""
def flatten(list) do
Erlang.lists.flatten(list)
:lists.flatten(list)
end
def flatten(list, tail) do
Erlang.lists.flatten(list, tail)
:lists.flatten(list, tail)
end
@doc """
@@ -91,7 +106,7 @@ defmodule List do
"""
def foldl(list, acc, function) when is_list(list) and is_function(function) do
Erlang.lists.foldl(function, acc, list)
:lists.foldl(function, acc, list)
end
@doc """
@@ -105,21 +120,7 @@ defmodule List do
"""
def foldr(list, acc, function) when is_list(list) and is_function(function) do
Erlang.lists.foldr(function, acc, list)
end
@doc """
Reverses the given list. This function simply delegates
to `lists:reverse` which is implemented in C for performance.
## Examples
List.reverse [1,2,3]
#=> [3,2,1]
"""
def reverse(list) do
:lists.reverse(list)
:lists.foldr(function, acc, list)
end
@doc """
@@ -156,7 +157,7 @@ defmodule List do
"""
def member?(list, term) do
Erlang.lists.member(term, list)
:lists.member(term, list)
end
@doc """
@@ -166,18 +167,18 @@ defmodule List do
## Examples
List.keyfind([a: 1, b: 2], :a, 1)
List.keyfind([a: 1, b: 2], :a, 0)
#=> { :a, 1 }
List.keyfind([a: 1, b: 2], 2, 2)
List.keyfind([a: 1, b: 2], 2, 1)
#=> { :b, 2 }
List.keyfind([a: 1, b: 2], :c, 1)
List.keyfind([a: 1, b: 2], :c, 0)
#=> nil
"""
def keyfind(list, item, position, default // nil) do
Erlang.lists.keyfind(item, position, list) || default
def keyfind(list, key, position, default // nil) do
:lists.keyfind(key, position + 1, list) || default
end
@doc """
@@ -187,18 +188,47 @@ defmodule List do
## Examples
List.keymember?([a: 1, b: 2], :a, 1)
List.keymember?([a: 1, b: 2], :a, 0)
#=> true
List.keymember?([a: 1, b: 2], 2, 2)
List.keymember?([a: 1, b: 2], 2, 1)
#=> true
List.keymember?([a: 1, b: 2], :c, 1)
List.keymember?([a: 1, b: 2], :c, 0)
#=> false
"""
def keymember?(list, item, position) do
Erlang.lists.keymember(item, position, list)
def keymember?(list, key, position) do
:lists.keymember(key, position + 1, list)
end
@doc """
Receives a list of tuples and replaces the item
identified by `key` at position `pos` if it exists.
## Examples
List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
#=> [a: 3, b: 2]
"""
def keyreplace(list, key, position, new_tuple) do
:lists.keyreplace(key, position + 1, list, new_tuple)
end
@doc """
Receives a list of tuples and replaces the item
identified by `key` at position `pos`. If the item
does not exist, it is added to the end of the list.
## Examples
List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
#=> [a: 3, b: 2]
"""
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
end
@doc """
@@ -208,116 +238,65 @@ defmodule List do
## Examples
List.keydelete([a: 1, b: 2], :a, 1)
List.keydelete([a: 1, b: 2], :a, 0)
#=> [{ :b, 2 }]
List.keydelete([a: 1, b: 2], 2, 2)
List.keydelete([a: 1, b: 2], 2, 1)
#=> [{ :a, 1 }]
List.keydelete([a: 1, b: 2], :c, 1)
List.keydelete([a: 1, b: 2], :c, 0)
#=> [{ :a, 1 }, { :b, 2 }]
"""
def keydelete(list, item, position) do
Erlang.lists.keydelete(item, position, list)
def keydelete(list, key, position) do
:lists.keydelete(key, position + 1, list)
end
@doc """
Returns a list of integers in the given range (both ends included when
possible). An optional step can be provided as well (defaults to 1).
If first > last and no step is provided, the numbers will be in descending
order.
## Examples
List.range 1, 3 #=> [1,2,3]
List.range 1, 8, 2 #=> [1,3,5,7]
List.range 1, 0 #=> []
List.range 3, 1 #=> [3,2,1]
List.range 5, 1, -2 #=> [5, 3, 1]
"""
@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
step = case step do
IO.write "[WARNING] List.range is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
case step do
nil ->
Erlang.lists.seq(first, last, 1)
:lists.seq(first, last, 1)
x when x < 0 ->
[]
_ ->
Erlang.lists.seq(first, last, step)
:lists.seq(first, last, step)
end
end
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
step = case step do
IO.write "[WARNING] List.range is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
case step do
nil ->
Erlang.lists.seq(first, last, -1)
:lists.seq(first, last, -1)
x when x > 0 ->
[]
_ ->
Erlang.lists.seq(first, last, step)
:lists.seq(first, last, step)
end
end
@doc """
Sorts the list by comparing each term. For an alternative
sorting algorithm, check `Enum.qsort`.
## Examples
List.sort [3, 4, 2, 1, 7]
#=> [1, 2, 3, 4, 7]
"""
@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 """
Sorts the list according to an ordering function. fun(a, b) should
return true if `a` compares less than or equal to `b`, `false` otherwise.
## Examples
List.sort [3, 4, 2, 1, 7], fn a, b -> b <= a end
#=> [7, 4, 3, 2, 1]
"""
@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 """
Returns a list without duplicated items.
## Examples
List.uniq [1,2,3,2,1]
#=> [1,2,3]
"""
@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 """
Duplicates the given element n times in a list.
## Examples
List.duplicate "hello", 3
#=> ["hello","hello","hello"]
List.duplicate [1,2], 2
#=> [[1,2],[1,2]]
"""
def duplicate(elem, n) do
Erlang.lists.duplicate(n, elem)
end
@doc """
Wraps the argument in a list.
If the argument is already a list, returns the list.
@@ -340,24 +319,6 @@ defmodule List do
[other]
end
@doc """
Zips corresponding elements from two lists (or tuples) into one list of tuples. The
number of elements in the resulting list is equal to the length of the
shortest list among the given ones.
## Examples
List.zip [1, 2, 3], [4, 5, 6]
#=> [{1, 4}, {2, 5}, {3, 6}]
List.zip [1, 2], [4, 5, 6]
#=> [{1, 4}, {2, 5}]
"""
def zip(item1, item2) do
do_zip(to_list(item1), to_list(item2), [])
end
@doc """
Zips corresponding elements from each list in `list_of_lists`.
@@ -370,6 +331,7 @@ defmodule List do
#=> [{1, 3, 5}]
"""
def zip([]), do: []
def zip(list_of_lists) when is_list(list_of_lists) do
do_zip(list_of_lists, [])
end
@@ -396,11 +358,9 @@ defmodule List do
# uniq
defp do_uniq([h|t], acc) do
case Erlang.lists.member(h, acc) do
true ->
do_uniq(t, acc)
false ->
[h|do_uniq(t, [h|acc])]
case :lists.member(h, acc) do
true -> do_uniq(t, acc)
false -> [h|do_uniq(t, [h|acc])]
end
end
@@ -410,23 +370,13 @@ defmodule List do
# zip
defp do_zip([h1|t1], [h2|t2], acc) do
do_zip t1, t2, [{h1, h2}|acc]
end
defp do_zip(_, _, acc) do
reverse acc
end
defp do_zip(list, acc) do
converter = fn x, acc -> do_zip_each(to_list(x), acc) end
{mlist, heads} = :lists.mapfoldl converter, [], list
case heads do
nil ->
:lists.reverse acc
_ ->
do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
nil -> :lists.reverse acc
_ -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
end
end
+202 -19
View File
@@ -29,6 +29,28 @@ defmodule Macro do
[:!, :@, :^, :not, :+, :-]
end
@doc """
Receives an expresion representing a possible definition
and extracts its arguments. It returns a tuple with the
function name and the arguments list or `:error` if not
a valid call syntax.
This is useful for macros that want to provide the same
arguments syntax available in def/defp/defmacro and friends.
## Examples
extract_args(quote do: foo) == { :foo, [] }
extract_args(quote do: foo()) == { :foo, [] }
extract_args(quote do: :foo.()) == { :foo, [] }
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
extract_args(quote do: 1.(1,2,3)) == :error
"""
def extract_args(expr) do
:elixir_clauses.extract_args(expr)
end
@doc """
Recursively escapes the given value so it can be inserted
into a syntax tree. Structures that are valid syntax nodes
@@ -56,6 +78,97 @@ defmodule Macro do
def escape(other), do: other
@doc %B"""
Unescape the given chars. This is the unescaping behavior
used by default in Elixir single- and double-quoted strings.
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.
This function is commonly used on sigil implementations
(like `%r`, `%b` and others).
## Examples
Macro.unescape_binary "example\\n"
#=> "example\n"
In the example above, we pass a string with `\n` escaped
and we return a version with it unescaped.
"""
def unescape_binary(chars) do
:elixir_interpolation.unescape_chars(chars)
end
@doc %B"""
Unescape the given chars according to the map given.
Check `unescape/1` if you want to use the same map as
Elixir single- and double-quoted strings.
## Map
The map must be a function. The function receives an integer
representing the number of the characters it wants to unescape.
Here is the default mapping function implemented by Elixir:
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
def unescape_map(?e), do: ?\e
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(e), do: e
If the `unescape_map` function returns false. The char is
not escaped and `\` is kept in the char list.
## Octals
Octals will by default be escaped unless the map function
returns false for ?0.
## Hex
Octals will by default be escaped unless the map function
returns false for ?x.
## Examples
Using the unescape_map defined above is easy:
Macro.unescape_binary "example\\n", unescape_map(&1)
"""
def unescape_binary(chars, map) do
:elixir_interpolation.unescape_chars(chars, map)
end
@doc """
Unescape the given tokens according to the default map.
Check `unescape/1` and `unescape/2` for more information
about unescaping. Only tokens that are binaries are
unescaped, all others are ignored. This function is useful
when implementing your own sigils. Check the implementation
of `Kernel.__b__` for examples.
"""
def unescape_tokens(tokens) do
:elixir_interpolation.unescape_tokens(tokens)
end
@doc """
Unescape the given tokens according to the given map.
Check `unescape_tokens/1` and `unescaped/2` for more information.
"""
def unescape_tokens(tokens, map) do
:elixir_interpolation.unescape_tokens(tokens, map)
end
@doc """
Converts the given expression to a binary.
@@ -103,28 +216,47 @@ defmodule Macro do
end
# Fn keyword
def to_binary({ :fn, _, [[do: block]] }) do
def to_binary({ :fn, _, [[do: { :->, _, [{_,tuple}] } = arrow]] })
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
"fn " <> arrow_to_binary(arrow) <> " end"
end
def to_binary({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
"fn " <> block_to_binary(block) <> "\nend"
end
def to_binary({ :fn, _, [[do: block]] }) do
block = adjust_new_lines block_to_binary(block), "\n "
"fn\n " <> block <> "\nend"
end
# Partial call
def to_binary({ :&, _, [num] }) do
"&#{num}"
end
# left -> right
def to_binary({ :->, _, _ } = arrow) do
"(" <> arrow_to_binary(arrow, true) <> ")"
end
# Binary ops
def to_binary({ op, _, [left, right] }) when op in binary_ops do
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
end
# Unary ops
def to_binary({ :not, _, [arg] }) do
"not " <> to_binary(arg)
end
def to_binary({ op, _, [arg] }) when op in unary_ops do
atom_to_binary(op, :utf8) <> to_binary(arg)
end
# All other calls
def to_binary({ target, _, args }) when is_list(args) do
{ list, last } = Erlang.elixir_tree_helpers.split_last(args)
{ list, last } = :elixir_tree_helpers.split_last(args)
case is_kw_blocks?(last) do
true -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
false -> call_to_binary_with_args(target, args)
@@ -142,17 +274,22 @@ defmodule Macro do
end
# All other structures
def to_binary(other), do: Binary.Inspect.inspect(other)
def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)
# Block keywords
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
defp is_kw_blocks?([_|_] = kw), do: Enum.all?(kw, fn({x,_}) -> x in kw_keywords end)
defp is_kw_blocks?(_), do: false
defp is_kw_blocks?([_|_] = kw) do
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
end
defp is_kw_blocks?(_), do: false
defp module_to_binary(atom) when is_atom(atom), do: Binary.Inspect.inspect(atom, raw: true)
defp module_to_binary(other), do: call_to_binary(other)
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_binary({ :., _, [arg] }), do: call_to_binary(arg) <> "."
defp call_to_binary({ :., _, [left, right] }), do: call_to_binary(left) <> "." <> call_to_binary(right)
defp call_to_binary({ :., _, [arg] }), do: module_to_binary(arg) <> "."
defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
defp call_to_binary(other), do: to_binary(other)
defp call_to_binary_with_args(target, args) do
@@ -162,7 +299,7 @@ defmodule Macro do
defp kw_blocks_to_binary(kw) do
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
case Keyword.key?(kw, x) do
case Keyword.has_key?(kw, x) do
true -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
false -> acc
end
@@ -176,8 +313,8 @@ defmodule Macro do
defp block_to_binary({ :->, _, exprs }) do
Enum.map_join(exprs, "\n", fn({ left, right }) ->
left = Enum.map_join(left, ", ", to_binary(&1))
left <> " ->\n " <> adjust_new_lines block_to_binary(right), "\n "
left = comma_join_or_empty_paren(left, false)
left <> "->\n " <> adjust_new_lines block_to_binary(right), "\n "
end)
end
@@ -193,12 +330,26 @@ defmodule Macro do
defp op_to_binary(expr), do: to_binary(expr)
defp arrow_to_binary({ :->, _, pairs }, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, right }) ->
left = comma_join_or_empty_paren(left, paren)
left <> "-> " <> to_binary(right)
end)
end
defp comma_join_or_empty_paren([], true), do: "() "
defp comma_join_or_empty_paren([], false), do: ""
defp comma_join_or_empty_paren(left, _) do
Enum.map_join(left, ", ", to_binary(&1)) <> " "
end
defp adjust_new_lines(block, replacement) do
bc <<x>> inbits block do
<< case x == ?\n do
true -> replacement
false -> <<x>>
end | :binary >>
end :: binary >>
end
end
@@ -209,6 +360,7 @@ defmodule Macro do
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (__FILE__, __MODULE__, etc);
* Module attributes reader (@foo);
In case the expression cannot be expanded, it returns the expression itself.
@@ -300,19 +452,27 @@ defmodule Macro do
# 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] }, env) do
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)
:lists.all(is_atom(&1), aliases) && Erlang.elixir_aliases.concat(aliases)
case :lists.all(is_atom(&1), aliases) do
true -> :elixir_aliases.concat(aliases)
false -> original
end
end
# Expand Erlang.foo calls
def expand({ { :., _, [{ :__aliases__, _, [:Erlang] }, atom] }, _, args }, _env) when
is_atom(atom) and (is_atom(args) or args == []), do: atom
# Expand @ calls
def expand({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
case (module = env.module) && Module.open?(module) do
true -> Module.get_attribute(module, name)
false -> original
end
end
# Expand pseudo-variables
def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
@@ -329,7 +489,7 @@ defmodule Macro do
case not is_partial?(args) do
false -> original
true ->
expand = Erlang.elixir_dispatch.expand_import(line, { atom, length(args) }, args,
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
env.module, env.function, env.requires, env.macros, env)
case expand do
{ :ok, _, expanded } -> expanded
@@ -345,7 +505,7 @@ defmodule Macro do
case is_atom(receiver) and not is_partial?(args) do
false -> original
true ->
expand = Erlang.elixir_dispatch.expand_require(line, receiver, { right, length(args) },
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
args, env.module, env.function, env.requires, env)
case expand do
{ :ok, expanded } -> expanded
@@ -365,6 +525,29 @@ defmodule Macro do
defp expand_alias(h, env) do
atom = list_to_atom('Elixir-' ++ atom_to_list(h))
Erlang.elixir_aliases.lookup(atom, env.aliases)
: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
evaluate code) and returns `:ok` unless a given term is unsafe,
which is returned as `{ :unsafe, term }`.
"""
def safe_term(terms) do
do_safe_term(terms) || :ok
end
def do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
do_safe_term(terms)
end
def do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
do_safe_term(term)
end
def do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
def do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
def do_safe_term(_), do: nil
end
+49 -45
View File
@@ -1,90 +1,94 @@
# We generate this record using a raw module due to
# bootstrap constraints. Notice the fields are not
# represented by a keywords list because we want
# to keep control over the order.
defmodule Macro.Env do
@doc """
@type name_arity :: { atom, non_neg_integer }
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{ module, module }]
@type context :: :assign | :guard | nil
@type requires :: [module]
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
fields = [:module, :file, :line, :function,
:aliases, :context, :requires, :functions, :macros]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros]
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__.
"""
def __access__(caller, args) do
Record.access(caller, __MODULE__, __record__(:fields), args)
end
def __record__(kind, _), do: __record__(kind)
def __record__(:name), do: Macro.Env
# When adding removing new fields,
# src/elixir_tree_helpers.erl needs to be changed as well.
def __record__(:fields) do
[
{:module,nil},
{:file,nil},
{:line,nil},
{:function,nil},
{:aliases,nil},
{:context,nil},
{:requires,nil},
{:macros,nil}
]
end
@doc """
Returns the current module name.
"""
def module(record), do: elem(record, 2)
def module(record)
@doc """
Returns the current file name as a binary.
"""
def file(record), do: elem(record, 3)
def file(record)
@doc """
Returns the current line as an integer.
"""
def line(record), do: elem(record, 4)
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), do: elem(record, 5)
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), do: elem(record, 6)
def aliases(record)
@doc """
Returns wether the compilation environment is currently
inside a guard.
Returns the context of the environment. It can be nil
(default context), inside a guard or inside an assign.
"""
def in_guard?(record), do: elem(record, 7) == :guard
@doc """
Returns wether the compilation environment is currently
inside a match clause.
"""
def in_match?(record), do: elem(record, 7) == :assign
def context(record)
@doc """
Returns the list of required modules.
"""
def requires(record), do: elem(record, 8)
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), do: elem(record, 9)
def macros(record)
@doc """
Returns a keywords list containing the file and line
Returns a keyword list containing the file and line
information as keys.
"""
def location(record) do
[file: file(record), line: line(record)]
end
end
@doc """
Returns wether the compilation environment is currently
inside a guard.
"""
def in_guard?(record), do: context(record) == :guard
@doc """
Returns wether the compilation environment is currently
inside a match clause.
"""
def in_match?(record), do: context(record) == :assign
end
+158 -99
View File
@@ -1,5 +1,11 @@
defmodule Module do
require Erlang.ets, as: ETS
require :ets, as: ETS
defmacrop is_env(env) do
quote do
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
end
end
@moduledoc """
This module provides many functions to deal with modules during
@@ -12,57 +18,96 @@ defmodule Module do
via the `__info__(attr)` function attached to each compiled module.
"""
@doc """
Check if a module is open, i.e. it is currently being defined
and its attributes and functions can be modified.
"""
def open?(module) do
table = data_table_for(module)
table == ETS.info(table, :name)
end
@doc """
Evalutes the quotes contents in the given module context.
A list of environment options can also be given as argument.
Check `Code.eval` for more information.
Raises an error if the module was already compiled.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
## Examples
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __MODULE__, contents, []
Module.eval_quoted __MODULE__, contents
end
Foo.sum(1, 2) #=> 3
This function also accepts a `Macro.Env` as first argument. This
is useful to evalute the quoted contents inside an existing environment:
For convenience, you can my pass `__ENV__` as argument and
all options will be automatically extracted from the environment:
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __ENV__, contents, []
Module.eval_quoted __MODULE__, contents, [], __ENV__
end
Foo.sum(1, 2) #=> 3
"""
def eval_quoted(env, quoted, binding // [], opts // [])
def eval_quoted(module, quoted, binding // [], opts // [])
def eval_quoted(Macro.Env[module: module] = env, quoted, binding, opts) do
eval_quoted(module, quoted, binding, Keyword.merge(env.location, opts))
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
end
def eval_quoted(module, quoted, binding, env) when is_env(env) do
eval_quoted(module, quoted, binding, env.to_keywords)
end
def eval_quoted(module, quoted, binding, opts) do
assert_not_compiled!(:eval_quoted, module)
:elixir_module.eval_quoted(module, quoted, binding, opts)
end
binding = Erlang.elixir_module.binding_for_eval(module, binding)
scope = Erlang.elixir_module.scope_for_eval(module, opts)
@doc """
Creates a module with the given name and given by
the given quoted expressions. The line where the module
is defined and its file can be given as options.
Erlang.elixir_def.reset_last(module)
## Examples
line = Keyword.get opts, :line, 1
{ value, binding, _scope } = Erlang.elixir.eval_quoted([quoted], binding, line, scope)
{ value, binding }
contents =
quote do
def world, do: true
end
Module.create(Hello, contents, __ENV__.location)
Hello.world #=> true
## Differences with `defmodule`
`Module.create` works similarly to `defmodule` and
return the same results. While one could also use
`defmodule` to define modules dynamically, this
function is preferred when the module body is given
by a quoted expression.
Another important distinction is that `Module.create`
allows you to control the environment variables used
when defining the module, while `defmodule` automatically
shares the same environment.
"""
def create(module, quoted, opts // [])
def create(module, quoted, env) when is_env(env) do
create(module, quoted, env.to_keywords)
end
def create(module, quoted, opts) when is_atom(module) do
line = Keyword.get(opts, :line, 1)
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
end
@doc """
@@ -77,7 +122,7 @@ defmodule Module do
"""
def concat(list) when is_list(list) do
Erlang.elixir_aliases.concat(list)
:elixir_aliases.concat(list)
end
@doc """
@@ -92,7 +137,7 @@ defmodule Module do
"""
def concat(left, right) do
Erlang.elixir_aliases.concat([left, right])
:elixir_aliases.concat([left, right])
end
@doc """
@@ -111,7 +156,7 @@ defmodule Module do
"""
def safe_concat(list) when is_list(list) do
Erlang.elixir_aliases.safe_concat(list)
:elixir_aliases.safe_concat(list)
end
@doc """
@@ -130,7 +175,7 @@ defmodule Module do
"""
def safe_concat(left, right) do
Erlang.elixir_aliases.safe_concat([left, right])
:elixir_aliases.safe_concat([left, right])
end
@doc """
@@ -153,7 +198,7 @@ defmodule Module do
end
def add_doc(module, line, kind, tuple, signature, doc) when
is_binary(doc) or is_boolean(doc) or doc == nil do
kind in [:def, :defmacro] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
@@ -161,12 +206,18 @@ defmodule Module do
{ simplify_signature(x, line, acc), acc + 1 }
end
case { ETS.lookup(table, tuple), doc } do
{ [], _ } ->
case ETS.lookup(table, tuple) do
[] ->
ETS.insert(table, { tuple, line, kind, signature, doc })
:ok
{ [{ tuple, line, kind, old, doc }], nil } ->
ETS.insert(table, { tuple, line, kind, merge_signatures(old, signature, 1), doc })
[{ tuple, line, old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
ETS.insert(table, {
tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
doc || old_doc
})
:ok
_ ->
{ :error, :existing_doc }
@@ -198,7 +249,7 @@ defmodule Module do
defp simplify_signature(other, line, i) when is_binary(other), do: { :"binary#{i}", line, :guess }
defp simplify_signature(_, line, i), do: { :"arg#{i}", line, :guess }
# Merge signatures
# Merge
defp merge_signatures([h1|t1], [h2|t2], i) do
[merge_signature(h1, h2, i)|merge_signatures(t1, t2, i + 1)]
@@ -268,30 +319,6 @@ defmodule Module do
end
end
@doc false
def function_defined?(module, tuple) do
IO.puts "Module.function_defined? is deprecated in favor of Module.defines?"
defines?(module, tuple)
end
@doc false
def function_defined?(module, tuple, kind) do
IO.puts "Module.function_defined? is deprecated in favor of Module.defines?"
defines?(module, tuple, kind)
end
@doc false
def defined_functions(module) do
IO.puts "Module.defined_functions is deprecated in favor of Module.definitions_in"
definitions_in(module)
end
@doc false
def defined_functions(module, kind) do
IO.puts "Module.defined_functions is deprecated in favor of Module.definitions_in"
definitions_in(module, kind)
end
@doc """
Return all functions defined in the given module.
@@ -341,11 +368,11 @@ defmodule Module do
[clause] ->
ETS.delete(table, tuple)
old = Module.read_attribute(module, :__overridable)
new = [ { tuple, { 1, [clause] } } ]
merged = :orddict.merge(fn(_k, { count, v1 }, _v2) -> { count + 1, [clause|v1] } end, old, new)
old = get_attribute(module, :__overridable)
new = [ { tuple, { 1, clause, false } } ]
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
Module.add_attribute(module, :__overridable, merged)
put_attribute(module, :__overridable, merged)
_ ->
{ name, arity } = tuple
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
@@ -353,27 +380,27 @@ defmodule Module do
end
end
def add_compile_callback(module, target, fun // :__compiling__) do
IO.puts "Module.add_compile_callback(module, target, fun) is deprecated in favor of " <>
"Module.add_attribute(module, :before_compile, { target, fun })"
assert_not_compiled!(:add_compile_callback, module)
add_attribute(module, :before_compile, { target, fun })
@doc """
Returns true if the given tuple in module is marked as overridable.
"""
def overridable?(module, tuple) do
!! List.keyfind(get_attribute(module, :__overridable), tuple, 0)
end
@doc """
Adds an Erlang attribute to the given module with the given
key and value. The semantics of adding the attribute depends
Puts an Erlang attribute to the given module with the given
key and value. The semantics of putting the attribute depends
if the attribute was registered or not via `register_attribute/2`.
## Examples
defmodule MyModule do
Module.add_attribute __MODULE__, :custom_threshold_for_lib, 10
Module.put_attribute __MODULE__, :custom_threshold_for_lib, 10
end
"""
def add_attribute(module, key, value) when is_atom(key) do
assert_not_compiled!(:add_attribute, module)
def put_attribute(module, key, value) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = normalize_attribute(key, value)
acc = ETS.lookup_element(table, :__acc_attributes, 2)
@@ -392,24 +419,24 @@ defmodule Module do
end
@doc """
Reads the given attribute from a module. If the attribute
Gets the given attribute from a module. If the attribute
was marked as accumulate with `Module.register_attribute`,
a list is always returned.
## Examples
defmodule Foo do
Module.add_attribute __MODULE__, :value, 1
Module.read_attribute __MODULE__, :value #=> 1
Module.put_attribute __MODULE__, :value, 1
Module.get_attribute __MODULE__, :value #=> 1
Module.register_attribute __MODULE__, :value, accumulate: true
Module.add_attribute __MODULE__, :value, 1
Module.read_attribute __MODULE__, :value #=> [1]
Module.put_attribute __MODULE__, :value, 1
Module.get_attribute __MODULE__, :value #=> [1]
end
"""
def read_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:read_attribute, module)
def get_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case ETS.lookup(table, key) do
@@ -426,7 +453,7 @@ defmodule Module do
## Examples
defmodule MyModule do
Module.add_attribute __MODULE__, :custom_threshold_for_lib, 10
Module.put_attribute __MODULE__, :custom_threshold_for_lib, 10
Module.delete_attribute __MODULE__, :custom_threshold_for_lib
end
@@ -454,7 +481,7 @@ defmodule Module do
By default, both options are true. Which means that registering
an attribute without passing any options will revert the attribute
behavior to exactly the same expected in Erlang.
behavior to exactly the same expected in :
## Examples
@@ -484,21 +511,57 @@ defmodule Module do
end
end
@doc """
Split the given module name into binary parts.
## Examples
Module.split Very.Long.Module.Name.And.Even.Longer
#=> ["Very", "Long", "Module", "Name", "And", "Even", "Longer"]
"""
def split(module) do
tl(String.split(Binary.Chars.to_binary(module), "-"))
end
@doc """
Convert a module name to binary without the Elixir prefix.
"""
def to_binary(module) do
"Elixir-" <> rest = Binary.Chars.to_binary(module)
bc <<r>> inbits rest, do: <<to_dot(r)>>
end
defp to_dot(?-), do: ?.
defp to_dot(l), do: l
@doc false
# Used internally to compile documentation. This function
# is private and must be used only internally.
def compile_doc(module, line, kind, pair, signature) do
doc = read_attribute(module, :doc)
result = add_doc(module, line, kind, pair, signature, doc)
def compile_doc(env, kind, name, args, _guards, _body) do
module = env.module
line = env.line
arity = length(args)
pair = { name, arity }
doc = get_attribute(module, :doc)
case add_doc(module, line, kind, pair, args, doc) do
:ok ->
:ok
{ :error, :private_doc } ->
IO.puts "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
{ :error, :existing_doc } ->
IO.puts "#{env.file}:#{line} @doc's for function #{name}/#{arity} have been given more than once, the first version is being kept"
end
delete_attribute(module, :doc)
result
end
@doc false
# Used internally to compile types. This function
# is private and must be used only internally.
def compile_type(module, key, value) when is_atom(key) do
assert_not_compiled!(:add_attribute, module)
def compile_typespec(module, key, value) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
new =
@@ -521,12 +584,13 @@ defmodule Module do
atom
end
defp normalize_attribute(:file, Macro.Env[file: file, line: line]), do: { binary_to_list(file), 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 })
defp normalize_attribute(key, atom) when key in [:before_compile, :after_compile] and is_atom(atom) do
{ atom, key }
defp normalize_attribute(key, atom) when is_atom(atom) and
key in [:before_compile, :after_compile, :on_definition] do
{ atom, :"__#{key}__" }
end
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
@@ -538,25 +602,20 @@ defmodule Module do
value
end
defp compiled?(module) do
table = data_table_for(module)
table == ETS.info(table, :name)
end
defp data_table_for(module) do
list_to_atom Erlang.lists.concat([:d, module])
module
end
defp function_table_for(module) do
list_to_atom Erlang.lists.concat([:f, module])
list_to_atom :lists.concat([:f, module])
end
defp docs_table_for(module) do
list_to_atom Erlang.lists.concat([:o, module])
list_to_atom :lists.concat([:o, module])
end
defp assert_not_compiled!(fun, module) do
compiled?(module) ||
open?(module) ||
raise ArgumentError,
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
end
+30 -7
View File
@@ -73,23 +73,46 @@ defmodule Node do
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
the list of available options.
"""
def spawn(node, fun, opts // []) do
def spawn(node, fun) do
:erlang.spawn(node, fun)
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
the list of available options.
"""
def spawn(node, fun, opts) do
:erlang.spawn_opt(node, fun, opts)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists, a useless
pid is returned.
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
the list of available options.
"""
def spawn(node, module, fun, args, opts // []) do
:erlang.spawn(node, module, fun, args, opts)
def spawn(node, module, fun, args) do
:erlang.spawn(node, module, fun, args)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
the list of available options.
"""
def spawn(node, module, fun, args, opts) do
:erlang.spawn_opt(node, module, fun, args, opts)
end
@doc """
+17 -10
View File
@@ -44,8 +44,7 @@ defmodule OptionParser do
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
dict = Keyword.new(flags, fn(k) -> { k, false } end)
parse(argv, aliases, flags, dict, [], true)
parse(argv, aliases, flags, true)
end
@doc """
@@ -63,12 +62,15 @@ defmodule OptionParser do
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
dict = Keyword.new(flags, fn(k) -> { k, false } end)
parse(argv, aliases, flags, dict, [], false)
parse(argv, aliases, flags, false)
end
## Helpers
defp parse(argv, aliases, flags, all) do
parse(argv, aliases, flags, [], [], all)
end
defp parse(["-" <> option|t], aliases, flags, dict, args, all) do
{ option, value } = normalize_option(option, aliases)
@@ -84,16 +86,16 @@ defmodule OptionParser do
parse(t, aliases, flags, dict, args, all)
end
defp parse([], _, _, dict, args, true) do
{ dict, List.reverse(args) }
defp parse([], _, flags, dict, args, true) do
{ reverse_dict(dict, flags), Enum.reverse(args) }
end
defp parse([h|t], aliases, flags, dict, args, true) do
parse(t, aliases, flags, dict, [h|args], true)
end
defp parse(value, _, _, dict, _args, false) do
{ dict, value }
defp parse(value, _, flags, dict, _args, false) do
{ reverse_dict(dict, flags), value }
end
defp flag_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
@@ -108,10 +110,15 @@ defmodule OptionParser do
end
defp store_option(dict, option, value) do
Keyword.put dict, option, value
[{ option, value }|dict]
end
defp normalize_option(<<?-, option|:binary>>, aliases) do
defp reverse_dict(dict, flags) do
flags = lc k inlist flags, not Keyword.has_key?(dict, k), do: { k, false }
Enum.reverse flags ++ dict
end
defp normalize_option(<<?-, option :: binary>>, aliases) do
normalize_option(option, aliases)
end
@@ -1,84 +1,103 @@
defimpl Dict, for: Orddict do
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) }
{ 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
def get(dict(data), key, default // nil) do
@doc false
def get(dict(data), key, default) do
case :orddict.find(key, data) do
{:ok, value} ->
value
:error ->
default
{: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
def merge(dict(d1), dict(d2)) do
dict(:orddict.merge fn _k, _v1, v2 -> v2 end, d1, d2)
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)
defimpl Enum.Iterator, for: OrdDict do
def iterator({ OrdDict, data }), do: data
def count({ OrdDict, data }), do: length(data)
end
defimpl Enum.OrdIterator, for: Orddict do
def iterator({ Orddict, data }), do: data
def to_list({ h, next }, _), do: [h|next]
defimpl Access, for: OrdDict do
def access(dict, key), do: OrdDict.get(dict, key, nil)
end
defmodule Orddict do
@moduledoc """
This module implements a dictionary based 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` protocol.
"""
use Dict.Common, Dict.Orddict
end
+53 -11
View File
@@ -28,7 +28,7 @@ defmodule Process do
@doc """
Returns all key-values in the dictionary
with no specific ordering (i.e. they are
not a keywords list).
not a keyword list).
"""
def get do
:erlang.get()
@@ -103,12 +103,19 @@ defmodule Process do
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as the `Kernel.spawn/1` function except
it also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
It behaves exactly the same as `Kernel.spawn/1`.
"""
def spawn(fun, opts // []) do
def spawn(fun) do
:erlang.spawn(fun)
end
@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
"""
def spawn(fun, opts) do
:erlang.spawn_opt(fun, opts)
end
@@ -117,12 +124,22 @@ defmodule Process do
`module.function(args)`. The new process created will be placed in the system
scheduler queue and be run some time later.
It behaves exactly the same as the `Kernel.spawn/3` function except
it also accepts extra options, for the list of available options
It behaves exactly the same as the `Kernel.spawn/3` function.
"""
def spawn(mod, fun, args) do
:erlang.spawn(mod, fun, args)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)`. The new process created will be placed in the system
scheduler queue and be run some time later.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
"""
def spawn(mod, fun, args, opts // []) do
def spawn(mod, fun, args, opts) do
:erlang.spawn_opt(mod, fun, args, opts)
end
@@ -224,7 +241,7 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
"""
def register(name, pid) do
def register(pid, name) do
:erlang.register(name, pid)
end
@@ -244,7 +261,22 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
def whereis(name) do
:erlang.whereis(name)
nillify :erlang.whereis(name)
end
@doc """
Returns the pid of the group leader for the process which evaluates the function.
"""
def group_leader do
:erlang.group_leader
end
@doc """
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`.
"""
def group_leader(leader, pid) do
:erlang.group_leader(leader, pid)
end
@doc """
@@ -275,6 +307,16 @@ defmodule Process do
:erlang.process_flag(pid, flag, value)
end
@doc """
Returns information about the process identified by pid.
Use this only for debugging information.
See http://www.erlang.org/doc/man/erlang.html#process_info-1 for more info.
"""
def info(pid) do
:erlang.process_info(pid)
end
@doc """
Returns information about the process identified by pid
or undefined if the process is not alive.
+237 -146
View File
@@ -1,15 +1,15 @@
defmodule Protocol do
@moduledoc false
# We need to use Erlang.lists because Enum is not available yet
require Erlang.lists, as: L
# We need to use :lists because Enum is not available yet
require :lists, as: L
@doc """
Handle `defprotocol`. It will define a function for each
protocol plus two extra functions:
* `__protocol__/1` - returns the protocol name when :name is given,
and a keywords list with the protocol functions
and a keyword list with the protocol functions
when :functions is given;
* `__impl_for__/1` - receives one argument and returns a module
@@ -21,16 +21,16 @@ defmodule Protocol do
def defprotocol(name, [do: block]) do
quote do
defmodule unquote(name) do
# Remove "harmful" macros
# We don't want to allow function definition inside protocols
# We don't allow function definition inside protocols
import Kernel, except: [
defmacrop: 1, defmacrop: 2, defmacrop: 4,
defmacro: 1, defmacro: 2, defmacro: 4,
defp: 1, defp: 2, defp: 4,
def: 1, def: 2, def: 4
]
# Import the new dsl that holds the new def
import Protocol.DSL
import :macros, Protocol.DSL
# Set up a clear slate to store defined functions
@functions []
@@ -39,9 +39,9 @@ defmodule Protocol do
unquote(block)
# Define callbacks and meta information
{ conversions, fallback } = Protocol.conversions_for(__MODULE__, @only, @except)
Protocol.impl_for(__ENV__, conversions)
Protocol.meta(__ENV__, @functions, fallback)
{ conversions, fallback, returns_nil } = Protocol.conversions_for(__MODULE__, @only, @except)
Protocol.impl_for(conversions, fallback, returns_nil, __ENV__)
Protocol.meta(@functions, conversions, fallback, returns_nil, __ENV__)
end
end
end
@@ -51,7 +51,15 @@ defmodule Protocol do
It also defines a `__impl__` function which
returns the protocol being implemented.
"""
def defimpl(protocol, [do: block, for: for]) do
def defimpl(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts))
end
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
lc f inlist for, do: do_defimpl(protocol, [do: block, for: f])
end
defp do_defimpl(protocol, [do: block, for: for]) do
quote do
protocol = unquote(protocol)
for = unquote(for)
@@ -60,20 +68,18 @@ defmodule Protocol do
Protocol.assert_protocol(protocol)
defmodule name do
def __impl__, do: unquote(protocol)
@behaviour unquote(protocol)
unquote(block)
def __impl__, do: unquote(protocol)
end
Protocol.assert_impl(name, protocol)
end
end
@doc """
Check if the given module is a protocol. Raises an error
if not loaded or not a protocol.
"""
# Check if the given module is a protocol. Raises an error
# if not loaded or not a protocol.
@doc false
def assert_protocol(module) do
case :code.ensure_loaded(module) do
case Code.ensure_compiled(module) do
{ :module, ^module } -> nil
_ -> raise ArgumentError, message: "#{module} is not loaded"
end
@@ -86,69 +92,16 @@ defmodule Protocol do
end
end
@doc """
Check if the given `impl` is a valid impl for `protocol`.
Raises an error if not.
"""
def assert_impl(impl, protocol) do
remaining = protocol.__protocol__(:functions) -- impl.__info__(:functions)
if remaining != [] do
pp = Enum.map_join remaining, ", ", fn {x,y} -> "#{x}/#{y}" end
raise ArgumentError,
message: "#{inspect impl} did not implement #{inspect protocol}, missing: #{pp}"
end
end
@doc """
Defines meta information about the protocol and internal callbacks.
"""
def meta(env, functions, fallback) do
contents = quote do
def __protocol__(:name), do: __MODULE__
def __protocol__(:functions), do: unquote(:lists.sort(functions))
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
rescue
UndefinedFunctionError -> __fallback__
end
other ->
other
end
end
def __impl_for__!(arg) do
if module = __impl_for__(arg) do
module
else
raise Protocol.UndefinedError, protocol: __MODULE__, structure: arg
end
end
defp __fallback__, do: unquote(fallback)
# Implements the function that detects the protocol and returns
# the module to dispatch to. Returns module.Record for records
# which should be properly handled by the dispatching function.
@doc false
def impl_for(conversions, fallback, returns_nil, env) do
contents = lc kind inlist conversions do
each_impl_for(kind, conversions, fallback)
end
Module.eval_quoted env, contents
end
@doc """
Implements the function that detects the protocol and returns
the module to dispatch to. Returns module.Record for records
which should be properly handled by the dispatching function.
"""
def impl_for(env, conversions) do
contents = lc kind inlist conversions, do: each_impl_for(kind, conversions)
# If we don't implement all protocols and any is not in the
# list, we need to add a final clause that returns nil.
if !L.member({ Any, :is_any }, conversions) && length(conversions) < 10 do
if returns_nil do
contents = contents ++ [quote do
defp __raw_impl__(_) do
nil
@@ -156,13 +109,65 @@ defmodule Protocol do
end]
end
Module.eval_quoted env, contents
Module.eval_quoted env.module, contents, [], env.location
end
@doc """
Returns the default conversions according to the given
only/except options.
"""
# 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
meta = quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: unquote(generate_type(conversions, any))
end
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
end
end
end
else
quote do
def __impl_for__(arg), do: __raw_impl__(arg)
end
end
impl_bang = if returns_nil do
quote do
def __impl_for__!(arg) do
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, structure: arg)
end
end
else
quote do
def __impl_for__!(arg), do: __impl_for__(arg)
end
end
Module.eval_quoted env.module, [meta, impl_for, impl_bang], [], env.location
end
# Returns the default conversions according to the given
# only/except options.
@doc false
def conversions_for(module, only, except) do
kinds = all_types
@@ -183,53 +188,63 @@ defmodule Protocol do
nil
end
{ conversions, fallback }
# If any is not in the list and we don't implement all
# protocols, we need to handle nil cases.
returns_nil = L.keyfind(Any, 1, conversions) == false and length(conversions) < 10
{ conversions, fallback, returns_nil }
end
## Helpers
defp all_types do
[
{ Record, :is_record },
{ Tuple, :is_tuple },
{ Atom, :is_atom },
{ List, :is_list },
{ BitString, :is_bitstring },
{ Number, :is_number },
{ Function, :is_function },
{ PID, :is_pid },
{ Port, :is_port },
{ Reference, :is_reference },
{ Any, :is_any }
]
defp generate_type(_conversions, true) do
quote(do: any)
end
# Returns a quoted expression that allow to checks
# if a variable named first is built in or not.
defp is_builtin?([{h,_}]) do
defp generate_type(conversions, false) do
or_function = fn({ _, _, x }, acc) -> { :|, 0, [acc, x] } end
{ _, _, first } = hd(conversions)
:lists.foldl(or_function, first, tl(conversions))
end
defp all_types do
[ { Record, :is_record, quote do: tuple },
{ Tuple, :is_tuple, quote do: tuple },
{ Atom, :is_atom, quote do: atom },
{ List, :is_list, quote do: list },
{ BitString, :is_bitstring, quote do: <<>> },
{ Number, :is_number, quote do: number },
{ Function, :is_function, quote do: (... -> any) },
{ PID, :is_pid, quote do: pid },
{ Port, :is_port, quote do: port },
{ Reference, :is_reference, quote do: reference },
{ Any, :is_any, quote do: any } ]
end
# Returns a quoted expression that allows to check
# if the first item in the tuple is a built-in or not.
defp is_builtin?([{h,_,_}]) do
quote do
first == unquote(h)
end
end
defp is_builtin?([{h,_}|t]) do
defp is_builtin?([{h,_,_}|t]) do
quote do
first == unquote(h) or unquote(is_builtin?(t))
end
end
# Specially handle tuples as they can also be record.
# If this is the case, module.Record will be returned.
defp each_impl_for({ _, :is_record }, conversions) do
# 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
first = :erlang.element(1, arg)
case unquote(is_builtin?(conversions)) do
true -> __MODULE__.Tuple
true -> unquote(fallback)
false ->
case atom_to_list(first) do
'Elixir-' ++ _ -> __MODULE__.Record
_ -> __MODULE__.Tuple
_ -> unquote(fallback)
end
end
end
@@ -237,7 +252,7 @@ defmodule Protocol do
end
# Special case any as we don't need to generate a guard.
defp each_impl_for({ _, :is_any }, _) do
defp each_impl_for({ _, :is_any, _ }, _, _) do
quote do
defp __raw_impl__(_) do
__MODULE__.Any
@@ -246,10 +261,10 @@ defmodule Protocol do
end
# Generate all others protocols.
defp each_impl_for({ kind, fun }, _) do
defp each_impl_for({ kind, fun, _ }, _, _) do
quote do
defp __raw_impl__(arg) when unquote(fun).(arg) do
Module.concat __MODULE__, unquote(kind)
__MODULE__.unquote(kind)
end
end
end
@@ -258,17 +273,11 @@ end
defmodule Protocol.DSL do
@moduledoc false
defmacro def(expression) do
{ name, arity } =
case expression do
{ _, _, args } when args == [] or is_atom(args) ->
raise ArgumentError, message: "protocol functions expect at least one argument"
{ name, _, args } when is_atom(name) and is_list(args) ->
{ name, length(args) }
_ ->
raise ArgumentError, message: "invalid args for defprotocol"
end
# We need to use :lists because Enum is not available yet
require :lists, as: L
@doc false
def args_and_body(module, name, arity) do
# Generate arguments according the arity. The arguments
# are named xa, xb and so forth. We cannot use string
# interpolation to generate the arguments because of compile
@@ -277,32 +286,114 @@ defmodule Protocol.DSL do
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
end
quote do
# Append new function to the list
@functions [unquote({name, arity})|@functions]
{ conversions, fallback, returns_nil } = conversions_for(module)
clauses = [default_clause(name, args)]
Kernel.def unquote(name).(unquote_splicing(args)) do
args = [unquote_splicing(args)]
case __raw_impl__(xA) do
__MODULE__.Record ->
try do
target = Module.concat(__MODULE__, :erlang.element(1, xA))
apply target, unquote(name), args
rescue
UndefinedFunctionError ->
case __fallback__ do
nil ->
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
other ->
apply other, unquote(name), args
end
end
nil ->
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
other ->
apply other, unquote(name), args
end
if returns_nil do
clauses = [nil_clause()|clauses]
end
if L.keyfind(Record, 1, conversions) do
clauses = [record_clause(name, args, fallback)|clauses]
end
body =
quote do
case __raw_impl__(xA), do: unquote({ :->, 0, clauses })
end
{ args, body }
end
@doc false
def callback_from_spec(module, name, arity) do
tuple = { name, arity }
specs = Module.get_attribute(module, :spec)
found = lc { k, v } inlist specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
found != []
end
defp conversions_for(module) do
only = Module.get_attribute(module, :only)
except = Module.get_attribute(module, :except)
Protocol.conversions_for(module, only, except)
end
defp default_clause(name, args) do
quote do: { [other], apply(other, unquote(name), [unquote_splicing(args)]) }
end
defp nil_clause() do
quote do
{ [nil], raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA) }
end
end
defp record_clause(name, args, fallback) do
arity = length(args)
quote do
{ [__MODULE__.Record],
(target = Module.concat(__MODULE__, :erlang.element(1, xA))
try do
target.unquote(name)(unquote_splicing(args))
catch
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
name == unquote(name) and length(args) == unquote(arity) ->
unquote(catch_clause(args, fallback))
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
end
defmacro def(expression) do
case expression do
{ _, _, args } when args == [] or is_atom(args) ->
raise ArgumentError, message: "protocol functions expect at least one argument"
{ name, _, args } when is_atom(name) and is_list(args) ->
:ok
_ ->
raise ArgumentError, message: "invalid args for defprotocol"
end
arity = length(args)
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
quote do
name = unquote(name)
arity = unquote(arity)
@functions [{name, arity}|@functions]
# Generate a fake definition with the user
# signature that will be used by docs
Kernel.def unquote(name).(unquote_splicing(args))
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
Kernel.def unquote(name), args, [], do: body
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
end
end
+8 -42
View File
@@ -1,38 +1,7 @@
defmodule Range do
def __access__(caller, arg), do: Record.access(caller, __MODULE__, __record__(:fields), arg)
def __record__(kind, _), do: __record__(kind)
def __record__(:name), do: __MODULE__
def __record__(:fields), do: [{:first,nil},{:last,nil}]
@doc """
Returns a new range based on the given options.
## Examples
Range.new first: 1, last: 10
defrecord Range, [:first, :last] do
@moduledoc """
Defines a Range.
"""
def new(options) do
{
__MODULE__,
Keyword.get!(options, :first),
Keyword.get!(options, :last)
}
end
@doc """
Returns the first item of the range.
"""
def first(range) do
elem(range, 2)
end
@doc """
Returns the last item of the range.
"""
def last(range) do
elem(range, 3)
end
end
defprotocol Range.Iterator do
@@ -62,16 +31,13 @@ defimpl Enum.Iterator, for: Range do
end
end
defimpl Enum.OrdIterator, for: Range do
def iterator(range) do
Enum.Iterator.Range.iterator(range)
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 to_list({ h, next }, iterator), do: [h|to_list(iterator.(next), iterator)]
def to_list(:stop, _), do: []
end
defimpl Range.Iterator, for: Number 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 }
+474 -198
View File
@@ -1,6 +1,6 @@
defmodule Record do
@moduledoc """
Functions to define and interact with Erlang records
Functions to define Elixir records
"""
@doc """
@@ -17,24 +17,190 @@ defmodule Record do
end
@doc """
Implements the access macro used by records.
It returns a quoted expression that represents
the access given by the keywords.
Main entry point for records definition. It defines a module
with the given `name` and the fields specified in `values`.
This is invoked directly by `Kernel.defrecord`, so check it
for more information and documentation.
"""
def access(caller, atom, fields, keyword) do
unless is_orddict(keyword) do
def defrecord(name, values, opts) do
block = Keyword.get(opts, :do, nil)
opts = Keyword.delete(opts, :do)
quote do
defmodule unquote(name) do
@moduledoc false
import Record.DSL
values = unquote(values)
opts = unquote(opts)
Record.deffunctions(values, opts, __ENV__)
unquote(block)
Record.deftypes(values, @record_type, opts, __ENV__)
end
end
end
@doc """
Main entry point for private records definition. It defines
a set of macros with the given `name` and the fields specified
in `values`. This is invoked directly by `Kernel.defrecordp`,
so check it for more information and documentation.
"""
def defrecordp(name, fields) do
quote do
Record.defmacros(unquote(name), unquote(fields), __ENV__)
end
end
@doc """
Defines record functions skipping the module definition.
This is called directly by `defrecord`. It expects the record
values, a set of options and the module environment.
## Examples
defmodule CustomRecord do
Record.deffunctions [:name, :age], __ENV__
Record.deftypes [:name, :age], __ENV__
end
"""
def deffunctions(values, _opts // [], env) do
values = lc value inlist values, do: convert_value(value)
escaped = Macro.escape(values)
contents = [
reflection(escaped),
initializer(escaped),
indexes(escaped),
conversions(values),
updater(values),
extensions(values, 1, [], Record.Extensions),
accessors(values),
]
contents = [quote(do: @__record__ unquote(escaped))|contents]
# Special case for bootstraping purposes
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@doc """
Defines types and specs for the record.
"""
def deftypes(values, types, _opts // [], env) do
types = types || []
values = lc value inlist values do
{ name, default } = convert_value(value)
{ name, default, find_spec(types, name) }
end
contents = [
core_specs(values),
accessor_specs(values, 1, [])
]
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@doc """
Defines macros for manipulating records. This is called
directly by `defrecordp`. It expects the macro name, the
record values and the environment.
## Examples
defmodule CustomRecord do
Record.defmacros :user, [:name, :age], __ENV__
end
"""
def defmacros(name, values, env) do
escaped = lc value inlist values do
{ key, value } = convert_value(value)
{ key, Macro.escape(value) }
end
contents = quote do
defmacrop unquote(name).() do
Record.access(__MODULE__, unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name).(record) when is_tuple(record) do
Record.to_keywords(__MODULE__, unquote(escaped), record)
end
defmacrop unquote(name).(args) do
Record.access(__MODULE__, unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name).(record, key) when is_atom(key) do
Record.get(__MODULE__, unquote(escaped), record, key)
end
defmacrop unquote(name).(record, args) do
Record.dispatch(__MODULE__, unquote(escaped), record, args, __CALLER__)
end
end
Module.eval_quoted(env.module, contents, [], env.location)
end
## Callbacks
# Store all optimizable fields in the record as well
@doc false
defmacro __before_compile__(_) do
quote do
def __record__(:optimizable), do: @record_optimizable
end
end
# Store fields that can be optimized and that cannot be
# optimized as they are overriden
@doc false
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = { name, length(args) }
module = env.module
functions = Module.get_attribute(module, :record_optimizable)
functions =
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
[tuple|functions]
else
List.delete(functions, tuple)
end
Module.put_attribute(module, :record_optimizable, functions)
end
# Implements the access macro used by records.
# It returns a quoted expression that defines
# a record or a match in case the record is
# inside a match.
@doc false
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise "expected contents inside brackets to be a Keyword"
end
in_match = caller.in_match?
has_underscore_value = Keyword.key?(keyword, :_)
has_underscore_value = Keyword.has_key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, { :_, 0, nil })
keyword = Keyword.delete keyword, :_
iterator = fn({field, default}, each_keyword) ->
new_fields =
case Keyword.key?(each_keyword, field) do
case Keyword.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match or has_underscore_value do
@@ -56,48 +222,88 @@ defmodule Record do
end
end
defp is_orddict(list) when is_list(list), do: :lists.all(is_orddict_tuple(&1), list)
defp is_orddict(_), do: false
# Dispatch the call to either update or to_list depending on the args given.
@doc false
def dispatch(atom, fields, record, args, caller) do
if is_keyword(args) do
update(atom, fields, record, args, caller)
else
to_list(atom, fields, record, args)
end
end
defp is_orddict_tuple({ x, _ }) when is_atom(x), do: true
defp is_orddict_tuple(_), do: false
# Implements the update macro defined by defmacros.
# It returns a quoted expression that represents
# the access given by the keywords.
@doc false
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise "expected contents inside brackets to be a Keyword"
end
if caller.in_match? do
raise "cannot invoke update style macro inside match context"
end
@doc """
Main entry point for records definition.
This is invoked directly by `Kernel.defrecord`.
Returns the quoted expression of a module given by name.
"""
def defrecord(name, values, opts) do
moduledoc = Keyword.get(opts, :moduledoc, false)
block = Keyword.get(opts, :do)
definition = Keyword.get(opts, :definition, Record.Definition)
quote do
defmodule unquote(name) do
@moduledoc unquote(moduledoc)
Record.define_functions(__ENV__, unquote(values), unquote(definition))
unquote(block)
Enum.reduce keyword, var, fn({ key, value }, acc) ->
index = find_index(fields, key, 0)
if index do
quote do
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
end
else
raise "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
# Implements the get macro defined by defmacros.
# It returns a quoted expression that represents
# getting the value of a given field.
@doc false
# Private endpoint that defines the functions for the Record.
def define_functions(env, values, definition) do
# Escape the values so they are valid syntax nodes
values = Macro.escape(values)
contents = [
reflection(values),
getters_and_setters(values, 1, [], definition),
initializers(values),
converters(values)
]
Module.eval_quoted env, contents
def get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
:erlang.element(unquote(index + 2), unquote(var))
end
else
raise "record #{inspect atom} does not have the key: #{inspect key}"
end
end
# Implements to_keywords macro defined by defmacros.
# It returns a quoted expression that represents
# converting record to keywords list.
@doc false
def to_keywords(_atom, fields, record) do
Enum.map fields,
fn { key, _default } ->
index = find_index(fields, key, 0)
quote do
{ unquote(key), :erlang.element(unquote(index + 2), unquote(record)) }
end
end
end
# Implements to_list macro defined by defmacros.
# It returns a quoted expression that represents
# extracting given fields from record.
@doc false
defp to_list(atom, fields, record, keys) do
Enum.map keys,
fn(key) ->
index = find_index(fields, key, 0)
if index do
quote do: :erlang.element(unquote(index + 2), unquote(record))
else
raise "record #{inspect atom} does not have the key: #{inspect key}"
end
end
end
## Function generation
# Define __record__/1 and __record__/2 as reflection functions
# that returns the record names and fields.
#
@@ -114,7 +320,6 @@ defmodule Record do
#
defp reflection(values) do
quote do
def __access__(caller, args), do: Record.access(caller, __MODULE__, __record__(:fields), args)
def __record__(kind, _), do: __record__(kind)
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
@@ -139,8 +344,8 @@ defmodule Record do
# { FileInfo, Keyword.get(opts, :atime), Keyword.get(opts, :mtime) }
# end
#
defp initializers(values) do
defaults = lc value inlist values, do: elem(value, 2)
defp initializer(values) do
defaults = lc { _, value } inlist values, do: value
# For each value, define a piece of code that will receive
# an ordered dict of options (opts) and it will try to fetch
@@ -154,7 +359,37 @@ defmodule Record do
def new(), do: new([])
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: setelem(tuple, 1, __MODULE__)
def new(tuple) when is_tuple(tuple), do: :erlang.setelement(1, tuple, __MODULE__)
end
end
# Define method to get index of a given key.
#
# Useful if you need to know position of the key for such applications as:
# - ets
# - mnesia
#
# For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define following method:
#
# def __index__(:atime), do: 2
# def __index__(:mtime), do: 3
# def __index__(_), do: nil
#
defp indexes(values) do
quoted = lc { k, _ } inlist values do
index = find_index(values, k, 0)
quote do
def __index__(unquote(k)), do: unquote(index + 1)
end
end
quote do
unquote(quoted)
def __index__(_), do: nil
def __index__(key, _), do: __index__(key)
end
end
@@ -163,13 +398,13 @@ defmodule Record do
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define one method, to_keywords, which will return a Keyword
#
#
# [atime: nil, mtime: nil]
#
defp converters(values) do
defp conversions(values) do
sorted = lc { k, _ } inlist values do
index = find_index(values, k, 1)
{ k, quote(do: :erlang.element(unquote(index + 1), record)) }
index = find_index(values, k, 0)
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
end
quote do
@@ -179,206 +414,247 @@ defmodule Record do
end
end
defp find_index([{ k, _ }|_], k, i), do: i
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
# Implement getters and setters for each attribute.
# For a declaration like:
# Implement accessors. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define four methods:
#
# def :atime.(record) do
# elem(record, 2)
# end
#
# def :atime.(record, value) do
# setelem(record, 2, value)
# elem(record, 1)
# end
#
# def :mtime.(record) do
# elem(record, 3)
# elem(record, 2)
# end
#
# def :mtime.(record, value) do
# setelem(record, value, 3)
# def :atime.(value, record) do
# setelem(record, 1, value)
# end
#
# `element` and `setelement` will simply get and set values
# from the record tuple. Notice that `:atime.(record)` is just
# a dynamic way to say `atime(record)`. We need to use this
# syntax as `unquote(key)(record)` wouldn't be valid (as Elixir
# allows you to parenthesis just on specific cases as `foo()`
# and `foo.bar()`)
defp getters_and_setters([{ key, default }|t], i, acc, definition) do
i = i + 1
functions = definition.functions_for(key, default, i)
getters_and_setters(t, i, [functions | acc], definition)
# def :mtime.(record) do
# setelem(record, 2, value)
# end
#
# def :atime.(callback, record) do
# setelem(record, 1, callback.(elem(record, 1)))
# end
#
# def :mtime.(callback, 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 getters_and_setters([], _i, acc, _), do: acc
end
defmodule Record.Extractor do
@moduledoc false
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def retrieve(name, from: string) do
file = to_char_list(string)
case Erlang.code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
end
retrieve_record(name, realfile)
defp accessors([{ :__exception__, _ }|t], 1) do
accessors(t, 2)
end
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def retrieve(name, from_lib: file) do
[app|path] = Erlang.filename.split(to_char_list(file))
case Erlang.code.lib_dir(to_char_list(app)) do
{ :error, _ } ->
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
libpath ->
retrieve_record name, Erlang.filename.join([libpath|path])
end
end
defp accessors([{ key, _default }|t], i) do
update = binary_to_atom "update_" <> atom_to_binary(key)
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
records = retrieve_from_file(file)
if record = List.keyfind(records, name, 1) do
parse_record(record)
else
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_from_file(file) do
lc { :attribute, _, :record, record } inlist read_file(file), do: record
end
# Read a file and return its abstract syntax form that also
# includes record and other preprocessor modules. This is done
# by using Erlang's epp_dodger.
defp read_file(file) do
case Erlang.epp_dodger.quick_parse_file(file) do
{ :ok, form } ->
form
other ->
raise "Error parsing file #{to_binary(file)}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of second order tuples where the first element
# is the field and the second is its default value.
defp parse_record({ _name, fields }) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = Erlang.erl_eval.expr(cons, [])
list
end
defp parse_field({ :typed_record_field, record_field, _type }) do
parse_field(record_field)
end
defp parse_field({ :record_field, _, key }) do
{ :tuple, 0, [key, {:atom, 0, :nil}] }
end
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
end
defmodule Record.Definition do
@moduledoc false
# Main entry point. It defines both default functions
# via `default_for` and extensions via `extension_for`.
def functions_for(key, default, i) do
[
default_for(key, default, i),
extension_for(key, default, i)
]
end
# Skip the __exception__ for defexception.
def default_for(:__exception__, _default, _i) do
nil
end
# Define the default functions for each field.
def default_for(key, _default, i) do
bin_update = "update_" <> atom_to_binary(key)
update = binary_to_atom(bin_update)
quote do
contents = quote do
def unquote(key).(record) do
:erlang.element(unquote(i), record)
:erlang.element(unquote(i + 1), record)
end
def unquote(key).(value, record) do
:erlang.setelement(unquote(i), record, value)
:erlang.setelement(unquote(i + 1), record, value)
end
def unquote(update).(function, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, function.(current))
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
end
end
[contents|accessors(t, i + 1)]
end
defp accessors([], _i) do
[quote do: @record_optimized false]
end
# Define an updater method that receives a
# keyword list and updates the record.
defp updater(values) do
fields =
lc {key, _default} inlist values do
index = find_index(values, key, 1)
quote do
Keyword.get(keywords, unquote(key), elem(record, unquote(index)))
end
end
contents = { :{}, 0, [(quote do: __MODULE__)|fields] }
quote do
def update([], record) do
record
end
def update(keywords, record) do
unquote(contents)
end
end
end
# Define extensions based on the default type.
def extension_for(key, default, i) when is_list(default) do
bin_key = atom_to_binary(key)
prepend = :"prepend_#{bin_key}"
merge = :"merge_#{bin_key}"
# 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)
end
defp extensions([], _i, acc, _), do: acc
## Types/specs generation
defp core_specs(values) do
types = lc { _, _, spec } inlist values, do: spec
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: { __MODULE__, unquote_splicing(types) }
end
@spec new :: t
@spec new(Keyword.t | tuple) :: t
@spec to_keywords(t) :: Keyword.t
@spec update(Keyword.t, t) :: t
@spec __index__(atom) :: non_neg_integer | nil
end
end
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
accessor_specs(t, 2, acc)
end
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@spec unquote(key)(t) :: unquote(spec)
@spec unquote(key)(unquote(spec), t) :: t
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
end
accessor_specs(t, i + 1, [contents | acc])
end
defp accessor_specs([], _i, acc), do: acc
## Helpers
defp is_keyword(list) when is_list(list), do: :lists.all(is_keyword_tuple(&1), list)
defp is_keyword(_), do: false
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
defp is_keyword_tuple(_), do: false
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
defp convert_value({ atom, other }) when is_atom(atom) and is_function(other), do:
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a function"
defp convert_value({ atom, other }) when is_atom(atom) and (is_reference(other) or is_pid(other) or is_port(other)), do:
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port"
defp convert_value({ atom, _ } = tuple) when is_atom(atom), do: tuple
defp convert_value({ field, _ }), do:
raise ArgumentError, message: "record field name has to be an atom, got #{inspect field}"
defp find_index([{ k, _ }|_], k, i), do: i
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
defp find_spec(types, name) do
matches = lc { k, v } inlist types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
end
end
end
defmodule Record.DSL do
@moduledoc false
@doc """
Defines the type for each field in the record.
Expects a keyword list.
"""
defmacro record_type(opts) when is_list(opts) do
quote do
@record_type quote do: unquote(opts)
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
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, value ++ current)
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
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, Keyword.merge(current, value))
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
def extension_for(key, default, i) when is_number(default) do
bin_key = atom_to_binary(key)
increment = :"increment_#{bin_key}"
defp extension_for(key, default, i) when is_number(default) do
increment = prefix("increment_", key)
quote do
def unquote(increment).(value // 1, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, current + value)
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
def extension_for(key, default, i) when is_boolean(default) do
bin_key = atom_to_binary(key)
toggle = :"toggle_#{bin_key}"
defp extension_for(key, default, i) when is_boolean(default) do
toggle = prefix("toggle_", key)
quote do
def unquote(toggle).(value // false, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, not current)
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
def extension_for(_, _, _), do: nil
defp extension_for(_, _, _), do: nil
# Helpers
defp prefix(prefix, key) do
binary_to_atom prefix <> atom_to_binary(key)
end
end
+79
View File
@@ -0,0 +1,79 @@
defmodule Record.Extractor do
@moduledoc false
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def retrieve(name, from: string) do
file = to_char_list(string)
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
end
retrieve_record(name, realfile)
end
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def retrieve(name, from_lib: file) do
[app|path] = :filename.split(to_char_list(file))
case :code.lib_dir(to_char_list(app)) do
{ :error, _ } ->
raise ArgumentError, "lib file #{to_binary(file)} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
end
end
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
records = retrieve_from_file(file)
if record = List.keyfind(records, name, 0) do
parse_record(record)
else
raise ArgumentError, "no record #{name} found at #{to_binary(file)}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_from_file(file) do
lc { :attribute, _, :record, record } inlist read_file(file), do: record
end
# Read a file and return its abstract syntax form that also
# includes record and other preprocessor modules. This is done
# by using Erlang's epp_dodger.
defp read_file(file) do
case :epp_dodger.quick_parse_file(file) do
{ :ok, form } ->
form
other ->
raise "error parsing file #{to_binary(file)}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of tuples where the first element is the field
# and the second is its default value.
defp parse_record({ _name, fields }) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = :erl_eval.expr(cons, [])
list
end
defp parse_field({ :typed_record_field, record_field, _type }) do
parse_field(record_field)
end
defp parse_field({ :record_field, _, key }) do
{ :tuple, 0, [key, {:atom, 0, :undefined}] }
end
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
end
+121 -70
View File
@@ -16,7 +16,7 @@ defmodule Regex do
The re module provides several options, the one available in Elixir, followed by
their shortcut in parenthesis, are:
* unicode (u) - used when you want to match against specific unicode characters
* unicode (u) - enable unicode specific patterns like \p
* caseless (i) - add case insensitivity
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
@@ -28,6 +28,7 @@ defmodule Regex do
* firstline (f) - forces the unanchored pattern to match before or at the first
newline, though the matched text may continue over the newline
* ungreedy (r) - invert the "greediness" of the regexp
* groups (g) - compile with info about groups available
The options not available are:
@@ -42,6 +43,9 @@ defmodule Regex do
a binary, a char list will return a char list).
"""
defrecordp :regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, term, term, term }
defexception CompileError, message: "regex could not be compiled"
@doc """
@@ -53,10 +57,13 @@ defmodule Regex do
def compile(source, options // "") do
source = to_binary(source)
options = to_binary(options)
re_opts = translate_options(options)
case Erlang.re.compile(source, re_opts) do
{ :ok, compiled } ->
{ :ok, { Regex, compiled, source, options } }
opts = translate_options(options)
re_opts = opts -- [:groups]
groups = if opts != re_opts, do: parse_groups(source)
case :re.compile(source, re_opts) do
{ :ok, re_pattern } ->
{ :ok, regex(re_pattern: re_pattern, source: source, options: options, groups: groups) }
error ->
error
end
@@ -84,8 +91,8 @@ defmodule Regex do
Regex.index %r/e/, "abcd" #=> nil
"""
def index({ Regex, compiled, _, _ }, string) do
case Erlang.re.run(string, compiled, [{ :capture, :first, :index }]) do
def index(regex(re_pattern: compiled), string) do
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
end
@@ -100,8 +107,8 @@ defmodule Regex do
Regex.match? %r/foo/, "bar" #=> false
"""
def match?({ Regex, compiled, _, _ }, string) do
:nomatch != Erlang.re.run(string, compiled)
def match?(regex(re_pattern: compiled), string) do
:re.run(string, compiled, [{ :capture, :none }]) == :match
end
@doc """
@@ -114,40 +121,48 @@ defmodule Regex do
Regex.run %r/e/, "abcd" #=> nil
"""
def run({ Regex, compiled, _, _ }, string) do
case Erlang.re.run(string, compiled, [{ :capture, :all, return_for(string) }]) do
:nomatch ->
nil
{ :match, results } ->
results
def run(regex, string, options // [])
def run(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise "regex was not compiled with g"
others -> others
end
case :re.run(string, compiled, [{ :capture, captures, return }]) do
:nomatch -> nil
{ :match, results } -> results
end
end
@doc """
Returns a list with the match indexes in the given string.
The matches are tuples where the first element is the index
(zero indexed) the match happened and the second is the length
of the match.
Returns the given captures as a list of tuples.
## Examples
Regex.indexes %r/c(d)/, "abcd" #=> [{2,2},{3,1}]
Regex.indexes %r/e/, "abcd" #=> nil
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
"""
def indexes({ Regex, compiled, _, _ }, string) do
case Erlang.re.run(string, compiled, [{ :capture, :all, :index }]) do
:nomatch ->
nil
{ :match, results } ->
results
def captures(regex(groups: groups) = regex, string, options // []) do
unless captures = Keyword.get(options, :capture) do
captures = if groups do
Enum.sort(groups)
else
raise "Regex was not compiled with g"
end
options = Keyword.put(options, :capture, captures)
end
results = run(regex, string, options)
if results, do: Enum.zip captures, results
end
@doc """
Returns the underlying re_pattern in the regular expression.
"""
def re_pattern({ Regex, compiled, _, _ }) do
def re_pattern(regex(re_pattern: compiled)) do
compiled
end
@@ -159,7 +174,7 @@ defmodule Regex do
Regex.source %r(foo) #=> "foo"
"""
def source({ Regex, _, source, _ }) do
def source(regex(source: source)) do
source
end
@@ -171,8 +186,20 @@ defmodule Regex do
Regex.opts %r(foo)m #=> 'm'
"""
def opts({ Regex, _, _, opts }) do
opts
def opts(regex(options: options)) do
options
end
@doc """
Returns list of named groups in regex.
## Examples
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
"""
def groups(regex(groups: groups)) do
groups
end
@doc """
@@ -188,35 +215,45 @@ defmodule Regex do
Regex.scan %r/e/, "abcd" #=> []
"""
def scan({ Regex, compiled, _, _ }, string) do
options = [{ :capture, :all, return_for(string) }, :global, { :offset, 0 }]
case Erlang.re.run(string, compiled, options) do
def scan(regex, string, options // [])
def scan(regex(re_pattern: compiled), string, options) do
return = Keyword.get(options, :return, return_for(string))
options = [{ :capture, :all, return }, :global]
case :re.run(string, compiled, options) do
:nomatch -> []
{ :match, results } ->
lc result inlist results do
case result do
[t] -> t
[h|t] -> t
end
end
{ :match, results } -> flatten_result(results)
end
end
@doc """
Split the given target in the number of parts specified. If no ammount
of parts is given, it defaults to :infinity.
Split the given target in the number of parts specified.
If no ammount of parts is given, it defaults to :infinity.
"""
def split({ Regex, compiled, _, _ }, string, parts // :infinity) do
options = [{ :return, return_for(string) }, :trim, { :parts, parts }]
Erlang.re.split(string, compiled, options)
def split(regex, string, options // [])
def split(regex(re_pattern: compiled), string, options) do
parts =
cond do
Keyword.get(options, :global) == false -> 2
p = Keyword.get(options, :parts) -> p
true -> :infinity
end
return = Keyword.get(options, :return, return_for(string))
opts = [return: return, parts: parts]
:re.split(string, compiled, opts)
end
@doc %B"""
Receives a string and a replacement and returns a string where the
first match of the regular expressions is replaced by replacement.
Inside the replacement, you can either give "&" to access the whole
regular expression or \N, where N is in integer to access an specific
matching parens.
Receives a regex, a binary and a replacement and returns a new
binary where the all matches are replaced by replacement.
Inside the replacement, you can either give "&" to access the
whole regular expression or \N, where N is in integer to access
a specific matching parens. You can also set global to false
if you want to replace just the first occurrence.
## Examples
@@ -227,23 +264,17 @@ defmodule Regex do
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
"""
def replace({ Regex, compiled, _, _ }, string, replacement) do
Erlang.re.replace(string, compiled, replacement, [{ :return, return_for(string) }])
end
@doc """
The same as replace, but replaces all parts where the regular
expressions matches in the string. Please read `replace/3` for
documentation and examples.
"""
def replace_all({ Regex, compiled, _, _ }, string, replacement) do
Erlang.re.replace(string, compiled, replacement, [{ :return, return_for(string) }, :global])
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
return = Keyword.get(options, :return, return_for(string))
opts = [{ :return, return }|opts]
:re.replace(string, compiled, replacement, opts)
end
# Helpers
@doc false
# Unescape map function used by Binary.unescape.
# Unescape map function used by Macro.unescape_binary.
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
@@ -256,12 +287,32 @@ defmodule Regex do
defp return_for(element) when is_binary(element), do: :binary
defp return_for(element) when is_list(element), do: :list
defp translate_options(<<?u, t|:binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?i, t|:binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t|:binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t|:binary>>), do: [:firstline|translate_options(t)]
defp translate_options(<<?r, t|:binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t|:binary>>), do: [:dotall,{:newline,:anycrlf}|translate_options(t)]
defp translate_options(<<?m, t|:binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall,{:newline,:anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
defp translate_options(<<>>), do: []
defp flatten_result(results) do
lc result inlist results do
case result do
[t] -> t
[h|t] -> t
end
end
end
defp parse_groups(source) do
options = [:global, {:capture, ['G'], :binary}]
{:ok, pattern} = :re.compile(%B"\(\?<(?<G>[^>]*)>")
case :re.run(source, pattern, options) do
:nomatch -> []
{ :match, results } ->
lc [group] inlist results, do: binary_to_atom(group)
end
end
end
+479
View File
@@ -0,0 +1,479 @@
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.
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.
For working with raw binaries, use Erlang's :binary
module.
"""
@type t :: binary
@type codepoint :: t
@type grapheme :: t
@doc """
Checks if a string is printable considering it is encoded
as UTF-8. Returns true if so, false otherwise.
## Examples
String.printable?("abc") #=> true
"""
@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
printable?(t)
end
def printable?(<<?\n, t :: binary>>), do: printable?(t)
def printable?(<<?\r, t :: binary>>), do: printable?(t)
def printable?(<<?\t, t :: binary>>), do: printable?(t)
def printable?(<<?\v, t :: binary>>), do: printable?(t)
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?(<<>>), do: true
def printable?(_), do: false
@doc """
Divides a string into sub string based on a pattern,
returning a list of these sub string. The pattern can
be a string, a list of strings or a regular expression.
The string is split into two parts by default, unless
`global` option is true. If a pattern is not specified,
the string is split on whitespace occurrences.
It returns a list with the original string if the pattern
can't be matched.
## Examples
String.split("a,b,c", ",") #=> ["a", "b", "c"]
String.split("a,b,c", ",", global: false) #=> ["a", "b,c"]
String.split("foo bar") #=> ["foo", "bar"]
String.split("1,2 3,4", [" ", ","]) #=> ["1", "2", "3", "4"]
String.split("a,b,c", %r{,}) #=> ["a", "b", "c"]
String.split("a,b,c", %r{,}, global: false) #=> ["a", "b,c"]
String.split("a,b", %r{\.}) #=> ["a,b"]
"""
@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
Regex.split(pattern, binary, global: options[:global])
end
def split(binary, pattern, options) do
opts = if options[:global] != false, do: [:global], else: []
:binary.split(binary, pattern, opts)
end
@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"
String.upcase("ab 123 xpto") #=> "AB 123 XPTO"
String.upcase("josé") #=> "JOSÉ"
"""
@spec upcase(t) :: t
defdelegate upcase(binary), to: String.Unicode
@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"
String.downcase("AB 123 XPTO") #=> "ab 123 xpto"
String.downcase("JOSÉ") #=> "josé"
"""
@spec downcase(t) :: t
defdelegate downcase(binary), to: String.Unicode
@doc """
Returns a string where trailing whitespace characters
and new line have been removed.
## Examples
String.rstrip(" abc ") #=> " abc"
"""
@spec rstrip(t) :: t
defdelegate rstrip(binary), to: String.Unicode
@doc """
Returns a string where trailing `char` have been removed.
## Examples
String.rstrip(" abc _", ?_) #=> " abc "
"""
@spec rstrip(t, char) :: t
def rstrip("", _char), do: ""
# Do a quick check before we traverse the whole
# binary. :binary.last is a fast operation (it
# does not traverse the whole binary).
def rstrip(string, char) do
if :binary.last(string) == char do
do_rstrip(string, "", char)
else
string
end
end
defp do_rstrip(<<char, string :: binary>>, buffer, char) do
do_rstrip(string, <<char, buffer :: binary>>, char)
end
defp do_rstrip(<<char, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char, do_rstrip(string, "", another_char) :: binary>>
end
defp do_rstrip(<<>>, _, _) do
<<>>
end
@doc """
Returns a string where leading whitespace characters
have been removed.
## Examples
String.lstrip(" abc ") #=> "abc "
"""
defdelegate lstrip(binary), to: String.Unicode
@doc """
Returns a string where leading `char` have been removed.
## Examples
String.lstrip("_ abc _", ?_) #=> " abc _"
"""
@spec lstrip(t, char) :: t
def lstrip(<<char, rest :: binary>>, char) do
<<lstrip(rest, char) :: binary>>
end
def lstrip(other, _char) do
other
end
@doc """
Returns a string where leading/trailing whitespace
and new line characters have been removed.
## Examples
String.strip(" abc ") #=> "abc"
"""
@spec strip(t) :: t
def strip(string) do
rstrip(lstrip(string))
end
@doc """
Returns a string where leading/trailing `char` have been
removed.
## Examples
String.strip("a abc a", ?a) #=> " abc "
"""
@spec strip(t, char) :: t
def strip(string, char) do
rstrip(lstrip(string, char), char)
end
@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.
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`.
## Examples
String.replace("a,b,c", ",", "-") #=> "a-b-c"
String.replace("a,b,c", ",", "-", global: false) #=> "a-b,c"
String.replace("a,b,c", "b", "[]", insert_replaced: 1) #=> "a,[b],c"
String.replace("a,b,c", ",", "[]", insert_replaced: 2) #=> "a[],b[],c"
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
"""
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options // []) do
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
end
defp translate_replace_options(options) do
opts = if options[:global] != false, do: [:global], else: []
if insert = options[:insert_replaced] do
opts = [{:insert_replaced,insert}|opts]
end
opts
end
@doc """
Returns a binary `subject` duplicated `n` times.
## Examples
String.duplicate("abc", 1) #=> "abc"
String.duplicate("abc", 2) #=> "abcabc"
"""
@spec duplicate(t, pos_integer) :: t
def duplicate(subject, n) when is_integer(n) and n > 0 do
:binary.copy(subject, n)
end
@doc """
Returns a list with codepoints from an utf8 string.
## Examples
String.codepoints("josé") #=> ["j", "o", "s", "é"]
String.codepoints("оптими зации") #=> ["о","п","т","и","м","и"," ","з","а","ц","и","и"]
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
"""
@spec codepoints(t) :: [codepoint]
defdelegate codepoints(string), to: String.Unicode
@doc """
Returns the next codepoint in a String.
The result is a tuple with the codepoint and the
remaining of the string or `:no_codepoint` in case
the String reached its end.
## Examples
String.next_codepoint("josé") #=> { "j", "osé" }
"""
@spec next_codepoint(t) :: codepoint | :no_codepoint
defdelegate next_codepoint(string), to: String.Unicode
@doc """
Returns unicode graphemes in the string
## Examples
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
"""
@spec graphemes(t) :: [grapheme]
defdelegate graphemes(string), to: String.Unicode
@doc """
Returns the next grapheme in a String.
The result is a tuple with the grapheme and the
remaining of the string or `:no_grapheme` in case
the String reached its end.
## Examples
String.next_grapheme("josé") #=> { "j", "osé" }
"""
@spec next_grapheme(t) :: grapheme | :no_grapheme
defdelegate next_grapheme(string), to: String.Unicode
@doc """
Returns the first grapheme from an utf8 string.
## Examples
String.first("elixir") #=> "e"
String.first("եոգլի") #=> "ե"
"""
@spec first(t) :: grapheme
def first(string) do
case next_grapheme(string) do
{ char, _ } -> char
:no_grapheme -> ""
end
end
@doc """
Returns the last grapheme from an utf8 string.
## Examples
String.last("elixir") #=> "r"
String.last("եոգլի") #=> "ի"
"""
@spec last(t) :: grapheme
def last(string) do
do_last(next_grapheme(string), "")
end
defp do_last({char, rest}, _) do
do_last(next_grapheme(rest), char)
end
defp do_last(:no_grapheme, last_char), do: last_char
@doc """
Returns the number of unicode graphemes in an utf8 string.
## Examples
String.length("elixir") #=> 6
String.length("եոգլի") #=> 5
"""
@spec length(t) :: non_neg_integer
def length(string) do
do_length(next_grapheme(string))
end
defp do_length({_, rest}) do
1 + do_length(next_grapheme(rest))
end
defp do_length(:no_grapheme), do: 0
@doc """
Returns the grapheme in the `position` of the given utf8 `string`.
If `position` is greater than `string` length, than it returns `nil`.
## Examples
String.at("elixir", 0) #=> "e"
String.at("elixir", 1) #=> "l"
String.at("elixir", 10) #=> nil
String.at("elixir", -1) #=> "r"
String.at("elixir", -10) #=> nil
"""
@spec at(t, integer) :: grapheme | nil
def at(string, position) when position >= 0 do
do_at(next_grapheme(string), position, 0)
end
def at(string, position) when position < 0 do
real_pos = do_length(next_grapheme(string)) - abs(position)
case real_pos >= 0 do
true -> do_at(next_grapheme(string), real_pos, 0)
false -> nil
end
end
defp do_at({_ , rest}, desired_pos, current_pos) when desired_pos > current_pos do
do_at(next_grapheme(rest), desired_pos, current_pos + 1)
end
defp do_at({char, _}, desired_pos, current_pos) when desired_pos == current_pos do
char
end
defp do_at(:no_grapheme, _, _), do: nil
end
+81 -19
View File
@@ -5,12 +5,13 @@ defmodule System do
with the VM or the host system.
"""
# Tries to run `git rev-parse HEAD`. In case of success
# returns the commit sha, otherwise returns an empty string.
defmacrop get_head_sha do
if :os.find_executable('git') do
data = :os.cmd('git rev-parse HEAD')
Regex.replace_all %r/\n/, to_binary(data), ""
# 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")
if :os.find_executable('git') && File.exists?(dotgit) do
data = :os.cmd('git describe --always --tags')
Regex.replace %r/\n/, to_binary(data), ""
else
""
end
@@ -24,23 +25,23 @@ defmodule System do
@doc """
Returns Elixir's version as binary.
"""
def version, do: "0.6.0"
@spec version() :: String.t
def version, do: "0.7.2"
@doc """
Returns a tuple { Elixir version, commit sha-1, build date }.
The format of the return value may change in a future release. Please
make sure your code doesn't depend on it.
Returns a keywords list with version, git tag info and date.
"""
@spec build_info() :: Keyword.t
def build_info do
{ version, get_head_sha, get_date }
[version: version, tag: get_describe, date: get_date]
end
@doc """
Returns the list of command-line arguments passed to the program.
"""
@spec argv() :: [String.t]
def argv do
Erlang.gen_server.call(:elixir_code_server, :argv)
:gen_server.call(:elixir_code_server, :argv)
end
@doc """
@@ -63,6 +64,8 @@ defmodule System do
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec cmd(char_list) :: char_list
@spec cmd(String.t) :: String.t
def cmd(command) when is_list(command) do
:os.cmd(command)
end
@@ -79,6 +82,8 @@ defmodule System do
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(char_list) :: char_list | nil
@spec find_executable(String.t) :: String.t | nil
def find_executable(command) when is_list(command) do
:os.find_executable(command) || nil
end
@@ -95,8 +100,9 @@ defmodule System do
given as a single string of the format "VarName=Value", where VarName is the
name of the variable and Value its value.
"""
@spec get_env() :: [{String.t, String.t}]
def get_env do
Enum.map :os.getenv, list_to_binary &1
Enum.map :os.getenv, :unicode.characters_to_binary &1
end
@doc """
@@ -104,10 +110,11 @@ defmodule System do
`varname` as a binary, or nil if the environment
variable is undefined.
"""
@spec get_env(String.t) :: String.t | nil
def get_env(varname) do
case :os.getenv(to_char_list(varname)) do
false -> nil
other -> list_to_binary(other)
other -> :unicode.characters_to_binary(other)
end
end
@@ -117,19 +124,23 @@ defmodule System do
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
"""
@spec get_pid() :: String.t
def get_pid, do: list_to_binary(:os.getpid)
@doc """
Sets a new `value` for the environment variable `varname`.
"""
def put_env(varname, value) do
:os.putenv to_char_list(varname), to_char_list(value)
@spec put_env(String.t, String.t | char_list) :: :ok
def put_env(varname, value) when is_binary(value) or is_list(value) do
:os.putenv to_char_list(varname), :unicode.characters_to_list(value)
:ok
end
@doc """
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
@spec put_env(Dict.t) :: :ok
def put_env(dict) do
Enum.each dict, fn {key, val} -> put_env key, val end
end
@@ -138,7 +149,58 @@ defmodule System do
Get the stacktrace.
"""
def stacktrace do
filter_stacktrace Erlang.erlang.get_stacktrace
filter_stacktrace :erlang.get_stacktrace
end
@doc """
Halts the Erlang runtime system where the first argument status must be a
non-negative integer, the atom `:abort` or any type that can be converted
to a char list.
* If an integer, the runtime system exits with the integer value which
is returned to the Operating System;
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system;
* If a char list, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1;
Note that on many platforms, only the status codes 0-255 are supported
by the operating system.
For integer status, Erlang runtime system closes all ports and allows async
threads to finish their operations before exiting. To exit without such
flushing, pass options [flush: false] instead.
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-2
## Examples
System.halt(0)
System.halt(1, flush: false)
System.halt(:abort)
"""
@spec halt() :: no_return
@spec halt(non_neg_integer | List.Chars.t | :abort) :: no_return
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]) :: no_return
def halt(status // 0, options // [])
def halt(status, options) when is_integer(status) or status == :abort do
do_halt(status, options)
end
def halt(status, options) do
do_halt(to_char_list(status), options)
end
# Support R15B
if List.member?(:erlang.module_info(:exports), { :halt, 2 }) do
defp do_halt(status, options), do: :erlang.halt(status, options)
else
IO.puts "Using limited halt support. Upgrade to R15B01 or later is recommended."
defp do_halt(status, _options), do: :erlang.halt(status)
end
## Helpers
@@ -151,6 +213,6 @@ defmodule System do
defp filter_stacktrace([]), do: []
defp server_call(args) do
Erlang.gen_server.call(:elixir_code_server, args)
:gen_server.call(:elixir_code_server, args)
end
end
+40 -35
View File
@@ -19,35 +19,45 @@ defmodule URI do
that implements the Binary.Chars protocol (i.e. can be converted
to binary).
"""
def encode_query(l), do: Enum.join(Enum.map(l, pair(&1)), "&")
def encode_query(l), do: Enum.map_join(l, "&", pair(&1))
@doc """
Given a query string of the form "key1=value1&key=value2...", produces an
orddict with one entry for each key-value pair. Each key and value will be a
binary. It also does percent-unescaping of both keys and values.
Returns nil if the query string is malformed.
Use decoder/1 if you want to customize or iterate each value manually.
"""
def decode_query(q, dict // Orddict.new) do
if Regex.match?(%r/^\s*$/, q) do
dict
else
parts = Regex.split %r/&/, to_binary(q)
impl = Dict.__impl_for__!(dict)
def decode_query(q, dict // OrdDict.new) do
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
end
try do
List.foldl parts, dict, fn kvstr, acc ->
case Regex.split(%r/=/, kvstr) do
[ key, value ] when key != "" ->
impl.put acc, decode(key), decode(value)
_ ->
throw :malformed_query_string
end
end
catch
:malformed_query_string -> nil
@doc """
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
end
defp do_decoder("") do
:stop
end
defp do_decoder(q) do
next =
case :binary.split(q, "&") do
[first, rest] -> rest
[first] -> ""
end
end
current =
case :binary.split(first, "=") do
[ key, value ] -> { decode(key), decode(value) }
[ key ] -> { decode(key), nil }
end
{ current, next }
end
defp pair({k, v}) do
@@ -57,38 +67,33 @@ defmodule URI do
@doc """
Percent (URL) encodes a URI.
"""
def encode(s), do: bc <<c>> inbits s, do: <<percent(c)|:binary>>
def encode(s), do: bc <<c>> inbits s, do: <<percent(c) :: binary>>
defp percent(32), do: <<?+>>
defp percent(?-), do: <<?->>
defp percent(?_), do: <<?_>>
defp percent(?.), do: <<?.>>
defp percent(c) when
c >= ?0 and c <= ?9 when
c >= ?a and c <= ?z when
c >= ?A and c <= ?Z do
defp percent(c)
when c >= ?0 and c <= ?9
when c >= ?a and c <= ?z
when c >= ?A and c <= ?Z do
<<c>>
end
defp percent(c), do: escape_byte(c)
defp escape_byte(c), do: "%" <> hex(c)
defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
defp hex(n) when n <= 9, do: <<n + ?0>>
defp hex(n) when n > 15 do
hex(bsr(n, 4)) <> hex(band(n, 15))
end
defp hex(n), do: <<n + ?A - 10>>
@doc """
Unpercent (URL) decodes a URI.
"""
def decode(<<?%, hex1, hex2, tail |:binary >>) do
def decode(<<?%, hex1, hex2, tail :: binary >>) do
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
end
def decode(<<head, tail |:binary >>) do
def decode(<<head, tail :: binary >>) do
<<check_plus(head)>> <> decode(tail)
end
@@ -98,7 +103,7 @@ defmodule URI do
defp hex2dec(n) when n in ?0..?9, do: n - ?0
defp check_plus(?+), do: 32
defp check_plus(c), do: c
defp check_plus(c), do: c
@doc """
Parses a URI into components.
@@ -121,7 +126,7 @@ defmodule URI do
def parse(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
parts = nillify(Regex.run(regex, s))
parts = nillify(Regex.run(regex, to_binary(s)))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
{ userinfo, host, port } = split_authority(authority)
+11 -4
View File
@@ -4,8 +4,15 @@ defmodule URI.Parser do
Check URI.HTTP for a possible implementation.
"""
def behaviour_info(:callbacks) do
[parse: 1,
default_port: 0]
end
use Behaviour
@doc """
Responsible for parsing extra URL information.
"""
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
@doc """
Responsible for returning the default port.
"""
defcallback default_port() :: integer
end
+11
View File
@@ -0,0 +1,11 @@
defmodule Elixir.Mixfile do
use Mix.Project
def project do
[ app: :elixir,
version: System.version,
escript_embed_elixir: false,
escript_main_module: :elixir,
escript_emu_args: "%%! -noshell\n" ]
end
end
+420
View File
@@ -0,0 +1,420 @@
LATIN CAPITAL LETTER A WITH MACRON AND GRAVE;0100 0300
LATIN SMALL LETTER A WITH MACRON AND GRAVE;0101 0300
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW;0045 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW;0065 0329
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00C8 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00E8 0329
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00C9 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00E9 0329
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND MACRON;00CA 0304
LATIN SMALL LETTER E WITH CIRCUMFLEX AND MACRON;00EA 0304
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND CARON;00CA 030C
LATIN SMALL LETTER E WITH CIRCUMFLEX AND CARON;00EA 030C
LATIN CAPITAL LETTER I WITH MACRON AND GRAVE;012A 0300
LATIN SMALL LETTER I WITH MACRON AND GRAVE;012B 0300
LATIN SMALL LETTER I WITH DOT ABOVE AND ACUTE;0069 0307 0301
LATIN SMALL LETTER NG WITH TILDE ABOVE;006E 0360 0067
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW;004F 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW;006F 0329
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00D2 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00F2 0329
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00D3 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00F3 0329
LATIN CAPITAL LETTER S WITH VERTICAL LINE BELOW;0053 0329
LATIN SMALL LETTER S WITH VERTICAL LINE BELOW;0073 0329
LATIN CAPITAL LETTER U WITH MACRON AND GRAVE;016A 0300
LATIN SMALL LETTER U WITH MACRON AND GRAVE;016B 0300
LATIN CAPITAL LETTER A WITH OGONEK AND ACUTE;0104 0301
LATIN SMALL LETTER A WITH OGONEK AND ACUTE;0105 0301
LATIN CAPITAL LETTER A WITH OGONEK AND TILDE;0104 0303
LATIN SMALL LETTER A WITH OGONEK AND TILDE;0105 0303
LATIN CAPITAL LETTER E WITH OGONEK AND ACUTE;0118 0301
LATIN SMALL LETTER E WITH OGONEK AND ACUTE;0119 0301
LATIN CAPITAL LETTER E WITH OGONEK AND TILDE;0118 0303
LATIN SMALL LETTER E WITH OGONEK AND TILDE;0119 0303
LATIN CAPITAL LETTER E WITH DOT ABOVE AND ACUTE;0116 0301
LATIN SMALL LETTER E WITH DOT ABOVE AND ACUTE;0117 0301
LATIN CAPITAL LETTER E WITH DOT ABOVE AND TILDE;0116 0303
LATIN SMALL LETTER E WITH DOT ABOVE AND TILDE;0117 0303
LATIN SMALL LETTER I WITH DOT ABOVE AND GRAVE;0069 0307 0300
LATIN SMALL LETTER I WITH DOT ABOVE AND TILDE;0069 0307 0303
LATIN CAPITAL LETTER I WITH OGONEK AND ACUTE;012E 0301
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND ACUTE;012F 0307 0301
LATIN CAPITAL LETTER I WITH OGONEK AND TILDE;012E 0303
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND TILDE;012F 0307 0303
LATIN CAPITAL LETTER J WITH TILDE;004A 0303
LATIN SMALL LETTER J WITH DOT ABOVE AND TILDE;006A 0307 0303
LATIN CAPITAL LETTER L WITH TILDE;004C 0303
LATIN SMALL LETTER L WITH TILDE;006C 0303
LATIN CAPITAL LETTER M WITH TILDE;004D 0303
LATIN SMALL LETTER M WITH TILDE;006D 0303
LATIN CAPITAL LETTER R WITH TILDE;0052 0303
LATIN SMALL LETTER R WITH TILDE;0072 0303
LATIN CAPITAL LETTER U WITH OGONEK AND ACUTE;0172 0301
LATIN SMALL LETTER U WITH OGONEK AND ACUTE;0173 0301
LATIN CAPITAL LETTER U WITH OGONEK AND TILDE;0172 0303
LATIN SMALL LETTER U WITH OGONEK AND TILDE;0173 0303
LATIN CAPITAL LETTER U WITH MACRON AND ACUTE;016A 0301
LATIN SMALL LETTER U WITH MACRON AND ACUTE;016B 0301
LATIN CAPITAL LETTER U WITH MACRON AND TILDE;016A 0303
LATIN SMALL LETTER U WITH MACRON AND TILDE;016B 0303
LATIN SMALL LETTER AE WITH GRAVE;00E6 0300
LATIN SMALL LETTER OPEN O WITH GRAVE;0254 0300
LATIN SMALL LETTER OPEN O WITH ACUTE;0254 0301
LATIN SMALL LETTER TURNED V WITH GRAVE;028C 0300
LATIN SMALL LETTER TURNED V WITH ACUTE;028C 0301
LATIN SMALL LETTER SCHWA WITH GRAVE;0259 0300
LATIN SMALL LETTER SCHWA WITH ACUTE;0259 0301
LATIN SMALL LETTER HOOKED SCHWA WITH GRAVE;025A 0300
LATIN SMALL LETTER HOOKED SCHWA WITH ACUTE;025A 0301
BENGALI LETTER KHINYA;0995 09CD 09B7
TAMIL CONSONANT K; 0B95 0BCD
TAMIL CONSONANT NG; 0B99 0BCD
TAMIL CONSONANT C; 0B9A 0BCD
TAMIL CONSONANT NY; 0B9E 0BCD
TAMIL CONSONANT TT; 0B9F 0BCD
TAMIL CONSONANT NN; 0BA3 0BCD
TAMIL CONSONANT T; 0BA4 0BCD
TAMIL CONSONANT N; 0BA8 0BCD
TAMIL CONSONANT P; 0BAA 0BCD
TAMIL CONSONANT M; 0BAE 0BCD
TAMIL CONSONANT Y; 0BAF 0BCD
TAMIL CONSONANT R; 0BB0 0BCD
TAMIL CONSONANT L; 0BB2 0BCD
TAMIL CONSONANT V; 0BB5 0BCD
TAMIL CONSONANT LLL;0BB4 0BCD
TAMIL CONSONANT LL; 0BB3 0BCD
TAMIL CONSONANT RR; 0BB1 0BCD
TAMIL CONSONANT NNN;0BA9 0BCD
TAMIL CONSONANT J; 0B9C 0BCD
TAMIL CONSONANT SH; 0BB6 0BCD
TAMIL CONSONANT SS; 0BB7 0BCD
TAMIL CONSONANT S; 0BB8 0BCD
TAMIL CONSONANT H; 0BB9 0BCD
TAMIL CONSONANT KSS;0B95 0BCD 0BB7 0BCD
TAMIL SYLLABLE KAA; 0B95 0BBE
TAMIL SYLLABLE KI; 0B95 0BBF
TAMIL SYLLABLE KII; 0B95 0BC0
TAMIL SYLLABLE KU; 0B95 0BC1
TAMIL SYLLABLE KUU; 0B95 0BC2
TAMIL SYLLABLE KE; 0B95 0BC6
TAMIL SYLLABLE KEE; 0B95 0BC7
TAMIL SYLLABLE KAI; 0B95 0BC8
TAMIL SYLLABLE KO; 0B95 0BCA
TAMIL SYLLABLE KOO; 0B95 0BCB
TAMIL SYLLABLE KAU; 0B95 0BCC
TAMIL SYLLABLE NGAA; 0B99 0BBE
TAMIL SYLLABLE NGI; 0B99 0BBF
TAMIL SYLLABLE NGII; 0B99 0BC0
TAMIL SYLLABLE NGU; 0B99 0BC1
TAMIL SYLLABLE NGUU; 0B99 0BC2
TAMIL SYLLABLE NGE; 0B99 0BC6
TAMIL SYLLABLE NGEE; 0B99 0BC7
TAMIL SYLLABLE NGAI; 0B99 0BC8
TAMIL SYLLABLE NGO; 0B99 0BCA
TAMIL SYLLABLE NGOO; 0B99 0BCB
TAMIL SYLLABLE NGAU; 0B99 0BCC
TAMIL SYLLABLE CI; 0B9A 0BBF
TAMIL SYLLABLE CII; 0B9A 0BC0
TAMIL SYLLABLE CU; 0B9A 0BC1
TAMIL SYLLABLE CUU; 0B9A 0BC2
TAMIL SYLLABLE CE; 0B9A 0BC6
TAMIL SYLLABLE CEE; 0B9A 0BC7
TAMIL SYLLABLE CAI; 0B9A 0BC8
TAMIL SYLLABLE CO; 0B9A 0BCA
TAMIL SYLLABLE COO; 0B9A 0BCB
TAMIL SYLLABLE CAU; 0B9A 0BCC
TAMIL SYLLABLE NYAA; 0B9E 0BBE
TAMIL SYLLABLE NYI; 0B9E 0BBF
TAMIL SYLLABLE NYII; 0B9E 0BC0
TAMIL SYLLABLE NYU; 0B9E 0BC1
TAMIL SYLLABLE NYUU; 0B9E 0BC2
TAMIL SYLLABLE NYE; 0B9E 0BC6
TAMIL SYLLABLE NYEE; 0B9E 0BC7
TAMIL SYLLABLE NYAI; 0B9E 0BC8
TAMIL SYLLABLE NYO; 0B9E 0BCA
TAMIL SYLLABLE NYOO; 0B9E 0BCB
TAMIL SYLLABLE NYAU; 0B9E 0BCC
TAMIL SYLLABLE TTAA; 0B9F 0BBE
TAMIL SYLLABLE TTI; 0B9F 0BBF
TAMIL SYLLABLE TTII; 0B9F 0BC0
TAMIL SYLLABLE TTU; 0B9F 0BC1
TAMIL SYLLABLE TTUU; 0B9F 0BC2
TAMIL SYLLABLE TTE; 0B9F 0BC6
TAMIL SYLLABLE TTEE; 0B9F 0BC7
TAMIL SYLLABLE TTAI; 0B9F 0BC8
TAMIL SYLLABLE TTO; 0B9F 0BCA
TAMIL SYLLABLE TTOO; 0B9F 0BCB
TAMIL SYLLABLE TTAU; 0B9F 0BCC
TAMIL SYLLABLE NNAA; 0BA3 0BBE
TAMIL SYLLABLE NNI; 0BA3 0BBF
TAMIL SYLLABLE NNII; 0BA3 0BC0
TAMIL SYLLABLE NNU; 0BA3 0BC1
TAMIL SYLLABLE NNUU; 0BA3 0BC2
TAMIL SYLLABLE NNE; 0BA3 0BC6
TAMIL SYLLABLE NNEE; 0BA3 0BC7
TAMIL SYLLABLE NNAI; 0BA3 0BC8
TAMIL SYLLABLE NNO; 0BA3 0BCA
TAMIL SYLLABLE NNOO; 0BA3 0BCB
TAMIL SYLLABLE NNAU; 0BA3 0BCC
TAMIL SYLLABLE TAA; 0BA4 0BBE
TAMIL SYLLABLE TI; 0BA4 0BBF
TAMIL SYLLABLE TII; 0BA4 0BC0
TAMIL SYLLABLE TU; 0BA4 0BC1
TAMIL SYLLABLE TUU; 0BA4 0BC2
TAMIL SYLLABLE TE; 0BA4 0BC6
TAMIL SYLLABLE TEE; 0BA4 0BC7
TAMIL SYLLABLE TAI; 0BA4 0BC8
TAMIL SYLLABLE TO; 0BA4 0BCA
TAMIL SYLLABLE TOO; 0BA4 0BCB
TAMIL SYLLABLE TAU; 0BA4 0BCC
TAMIL SYLLABLE NAA; 0BA8 0BBE
TAMIL SYLLABLE NI; 0BA8 0BBF
TAMIL SYLLABLE NII; 0BA8 0BC0
TAMIL SYLLABLE NU; 0BA8 0BC1
TAMIL SYLLABLE NUU; 0BA8 0BC2
TAMIL SYLLABLE NE; 0BA8 0BC6
TAMIL SYLLABLE NEE; 0BA8 0BC7
TAMIL SYLLABLE NAI; 0BA8 0BC8
TAMIL SYLLABLE NO; 0BA8 0BCA
TAMIL SYLLABLE NOO; 0BA8 0BCB
TAMIL SYLLABLE NAU; 0BA8 0BCC
TAMIL SYLLABLE PAA; 0BAA 0BBE
TAMIL SYLLABLE PI; 0BAA 0BBF
TAMIL SYLLABLE PII; 0BAA 0BC0
TAMIL SYLLABLE PU; 0BAA 0BC1
TAMIL SYLLABLE PUU; 0BAA 0BC2
TAMIL SYLLABLE PE; 0BAA 0BC6
TAMIL SYLLABLE PEE; 0BAA 0BC7
TAMIL SYLLABLE PAI; 0BAA 0BC8
TAMIL SYLLABLE PO; 0BAA 0BCA
TAMIL SYLLABLE POO; 0BAA 0BCB
TAMIL SYLLABLE PAU; 0BAA 0BCC
TAMIL SYLLABLE MAA; 0BAE 0BBE
TAMIL SYLLABLE MI; 0BAE 0BBF
TAMIL SYLLABLE MII; 0BAE 0BC0
TAMIL SYLLABLE MU; 0BAE 0BC1
TAMIL SYLLABLE MUU; 0BAE 0BC2
TAMIL SYLLABLE ME; 0BAE 0BC6
TAMIL SYLLABLE MEE; 0BAE 0BC7
TAMIL SYLLABLE MAI; 0BAE 0BC8
TAMIL SYLLABLE MO; 0BAE 0BCA
TAMIL SYLLABLE MOO; 0BAE 0BCB
TAMIL SYLLABLE MAU; 0BAE 0BCC
TAMIL SYLLABLE YAA; 0BAF 0BBE
TAMIL SYLLABLE YI; 0BAF 0BBF
TAMIL SYLLABLE YII; 0BAF 0BC0
TAMIL SYLLABLE YU; 0BAF 0BC1
TAMIL SYLLABLE YUU; 0BAF 0BC2
TAMIL SYLLABLE YE; 0BAF 0BC6
TAMIL SYLLABLE YEE; 0BAF 0BC7
TAMIL SYLLABLE YAI; 0BAF 0BC8
TAMIL SYLLABLE YO; 0BAF 0BCA
TAMIL SYLLABLE YOO; 0BAF 0BCB
TAMIL SYLLABLE YAU; 0BAF 0BCC
TAMIL SYLLABLE RAA; 0BB0 0BBE
TAMIL SYLLABLE RI; 0BB0 0BBF
TAMIL SYLLABLE RII; 0BB0 0BC0
TAMIL SYLLABLE RU; 0BB0 0BC1
TAMIL SYLLABLE RUU; 0BB0 0BC2
TAMIL SYLLABLE RE; 0BB0 0BC6
TAMIL SYLLABLE REE; 0BB0 0BC7
TAMIL SYLLABLE RAI; 0BB0 0BC8
TAMIL SYLLABLE RO; 0BB0 0BCA
TAMIL SYLLABLE ROO; 0BB0 0BCB
TAMIL SYLLABLE RAU; 0BB0 0BCC
TAMIL SYLLABLE LAA; 0BB2 0BBE
TAMIL SYLLABLE LI; 0BB2 0BBF
TAMIL SYLLABLE LII; 0BB2 0BC0
TAMIL SYLLABLE LU; 0BB2 0BC1
TAMIL SYLLABLE LUU; 0BB2 0BC2
TAMIL SYLLABLE LE; 0BB2 0BC6
TAMIL SYLLABLE LEE; 0BB2 0BC7
TAMIL SYLLABLE LAI; 0BB2 0BC8
TAMIL SYLLABLE LO; 0BB2 0BCA
TAMIL SYLLABLE LOO; 0BB2 0BCB
TAMIL SYLLABLE LAU; 0BB2 0BCC
TAMIL SYLLABLE VAA; 0BB5 0BBE
TAMIL SYLLABLE VI; 0BB5 0BBF
TAMIL SYLLABLE VII; 0BB5 0BC0
TAMIL SYLLABLE VU; 0BB5 0BC1
TAMIL SYLLABLE VUU; 0BB5 0BC2
TAMIL SYLLABLE VE; 0BB5 0BC6
TAMIL SYLLABLE VEE; 0BB5 0BC7
TAMIL SYLLABLE VAI; 0BB5 0BC8
TAMIL SYLLABLE VO; 0BB5 0BCA
TAMIL SYLLABLE VOO; 0BB5 0BCB
TAMIL SYLLABLE VAU; 0BB5 0BCC
TAMIL SYLLABLE LLLAA; 0BB4 0BBE
TAMIL SYLLABLE LLLI; 0BB4 0BBF
TAMIL SYLLABLE LLLII; 0BB4 0BC0
TAMIL SYLLABLE LLLU; 0BB4 0BC1
TAMIL SYLLABLE LLLUU; 0BB4 0BC2
TAMIL SYLLABLE LLLE; 0BB4 0BC6
TAMIL SYLLABLE LLLEE; 0BB4 0BC7
TAMIL SYLLABLE LLLAI; 0BB4 0BC8
TAMIL SYLLABLE LLLO; 0BB4 0BCA
TAMIL SYLLABLE LLLOO; 0BB4 0BCB
TAMIL SYLLABLE LLLAU; 0BB4 0BCC
TAMIL SYLLABLE LLAA; 0BB3 0BBE
TAMIL SYLLABLE LLI; 0BB3 0BBF
TAMIL SYLLABLE LLII; 0BB3 0BC0
TAMIL SYLLABLE LLU; 0BB3 0BC1
TAMIL SYLLABLE LLUU; 0BB3 0BC2
TAMIL SYLLABLE LLE; 0BB3 0BC6
TAMIL SYLLABLE LLEE; 0BB3 0BC7
TAMIL SYLLABLE LLAI; 0BB3 0BC8
TAMIL SYLLABLE LLO; 0BB3 0BCA
TAMIL SYLLABLE LLOO; 0BB3 0BCB
TAMIL SYLLABLE LLAU; 0BB3 0BCC
TAMIL SYLLABLE RRAA; 0BB1 0BBE
TAMIL SYLLABLE RRI; 0BB1 0BBF
TAMIL SYLLABLE RRII; 0BB1 0BC0
TAMIL SYLLABLE RRU; 0BB1 0BC1
TAMIL SYLLABLE RRUU; 0BB1 0BC2
TAMIL SYLLABLE RRE; 0BB1 0BC6
TAMIL SYLLABLE RREE; 0BB1 0BC7
TAMIL SYLLABLE RRAI; 0BB1 0BC8
TAMIL SYLLABLE RRO; 0BB1 0BCA
TAMIL SYLLABLE RROO; 0BB1 0BCB
TAMIL SYLLABLE RRAU; 0BB1 0BCC
TAMIL SYLLABLE NNNAA; 0BA9 0BBE
TAMIL SYLLABLE NNNI; 0BA9 0BBF
TAMIL SYLLABLE NNNII; 0BA9 0BC0
TAMIL SYLLABLE NNNU; 0BA9 0BC1
TAMIL SYLLABLE NNNUU; 0BA9 0BC2
TAMIL SYLLABLE NNNE; 0BA9 0BC6
TAMIL SYLLABLE NNNEE; 0BA9 0BC7
TAMIL SYLLABLE NNNAI; 0BA9 0BC8
TAMIL SYLLABLE NNNO; 0BA9 0BCA
TAMIL SYLLABLE NNNOO; 0BA9 0BCB
TAMIL SYLLABLE NNNAU; 0BA9 0BCC
TAMIL SYLLABLE JAA; 0B9C 0BBE
TAMIL SYLLABLE JI; 0B9C 0BBF
TAMIL SYLLABLE JII; 0B9C 0BC0
TAMIL SYLLABLE JU; 0B9C 0BC1
TAMIL SYLLABLE JUU; 0B9C 0BC2
TAMIL SYLLABLE JE; 0B9C 0BC6
TAMIL SYLLABLE JEE; 0B9C 0BC7
TAMIL SYLLABLE JAI; 0B9C 0BC8
TAMIL SYLLABLE JO; 0B9C 0BCA
TAMIL SYLLABLE JOO; 0B9C 0BCB
TAMIL SYLLABLE JAU; 0B9C 0BCC
TAMIL SYLLABLE SHAA; 0BB6 0BBE
TAMIL SYLLABLE SHI; 0BB6 0BBF
TAMIL SYLLABLE SHII; 0BB6 0BC0
TAMIL SYLLABLE SHU; 0BB6 0BC1
TAMIL SYLLABLE SHUU; 0BB6 0BC2
TAMIL SYLLABLE SHE; 0BB6 0BC6
TAMIL SYLLABLE SHEE; 0BB6 0BC7
TAMIL SYLLABLE SHAI; 0BB6 0BC8
TAMIL SYLLABLE SHO; 0BB6 0BCA
TAMIL SYLLABLE SHOO; 0BB6 0BCB
TAMIL SYLLABLE SHAU; 0BB6 0BCC
TAMIL SYLLABLE SSAA; 0BB7 0BBE
TAMIL SYLLABLE SSI; 0BB7 0BBF
TAMIL SYLLABLE SSII; 0BB7 0BC0
TAMIL SYLLABLE SSU; 0BB7 0BC1
TAMIL SYLLABLE SSUU; 0BB7 0BC2
TAMIL SYLLABLE SSE; 0BB7 0BC6
TAMIL SYLLABLE SSEE; 0BB7 0BC7
TAMIL SYLLABLE SSAI; 0BB7 0BC8
TAMIL SYLLABLE SSO; 0BB7 0BCA
TAMIL SYLLABLE SSOO; 0BB7 0BCB
TAMIL SYLLABLE SSAU; 0BB7 0BCC
TAMIL SYLLABLE SAA; 0BB8 0BBE
TAMIL SYLLABLE SI; 0BB8 0BBF
TAMIL SYLLABLE SII; 0BB8 0BC0
TAMIL SYLLABLE SU; 0BB8 0BC1
TAMIL SYLLABLE SUU; 0BB8 0BC2
TAMIL SYLLABLE SE; 0BB8 0BC6
TAMIL SYLLABLE SEE; 0BB8 0BC7
TAMIL SYLLABLE SAI; 0BB8 0BC8
TAMIL SYLLABLE SO; 0BB8 0BCA
TAMIL SYLLABLE SOO; 0BB8 0BCB
TAMIL SYLLABLE SAU; 0BB8 0BCC
TAMIL SYLLABLE HAA; 0BB9 0BBE
TAMIL SYLLABLE HI; 0BB9 0BBF
TAMIL SYLLABLE HII; 0BB9 0BC0
TAMIL SYLLABLE HU; 0BB9 0BC1
TAMIL SYLLABLE HUU; 0BB9 0BC2
TAMIL SYLLABLE HE; 0BB9 0BC6
TAMIL SYLLABLE HEE; 0BB9 0BC7
TAMIL SYLLABLE HAI; 0BB9 0BC8
TAMIL SYLLABLE HO; 0BB9 0BCA
TAMIL SYLLABLE HOO; 0BB9 0BCB
TAMIL SYLLABLE HAU; 0BB9 0BCC
TAMIL SYLLABLE KSSA; 0B95 0BCD 0BB7
TAMIL SYLLABLE KSSAA; 0B95 0BCD 0BB7 0BBE
TAMIL SYLLABLE KSSI; 0B95 0BCD 0BB7 0BBF
TAMIL SYLLABLE KSSII; 0B95 0BCD 0BB7 0BC0
TAMIL SYLLABLE KSSU; 0B95 0BCD 0BB7 0BC1
TAMIL SYLLABLE KSSUU; 0B95 0BCD 0BB7 0BC2
TAMIL SYLLABLE KSSE; 0B95 0BCD 0BB7 0BC6
TAMIL SYLLABLE KSSEE; 0B95 0BCD 0BB7 0BC7
TAMIL SYLLABLE KSSAI; 0B95 0BCD 0BB7 0BC8
TAMIL SYLLABLE KSSO; 0B95 0BCD 0BB7 0BCA
TAMIL SYLLABLE KSSOO; 0B95 0BCD 0BB7 0BCB
TAMIL SYLLABLE KSSAU; 0B95 0BCD 0BB7 0BCC
TAMIL SYLLABLE SHRII; 0BB6 0BCD 0BB0 0BC0
SINHALA CONSONANT SIGN YANSAYA;0DCA 200D 0DBA
SINHALA CONSONANT SIGN RAKAARAANSAYA;0DCA 200D 0DBB
SINHALA CONSONANT SIGN REPAYA;0DBB 0DCA 200D
GEORGIAN LETTER U-BRJGU;10E3 0302
KHMER CONSONANT SIGN COENG KA;17D2 1780
KHMER CONSONANT SIGN COENG KHA;17D2 1781
KHMER CONSONANT SIGN COENG KO;17D2 1782
KHMER CONSONANT SIGN COENG KHO;17D2 1783
KHMER CONSONANT SIGN COENG NGO;17D2 1784
KHMER CONSONANT SIGN COENG CA;17D2 1785
KHMER CONSONANT SIGN COENG CHA;17D2 1786
KHMER CONSONANT SIGN COENG CO;17D2 1787
KHMER CONSONANT SIGN COENG CHO;17D2 1788
KHMER CONSONANT SIGN COENG NYO;17D2 1789
KHMER CONSONANT SIGN COENG DA;17D2 178A
KHMER CONSONANT SIGN COENG TTHA;17D2 178B
KHMER CONSONANT SIGN COENG DO;17D2 178C
KHMER CONSONANT SIGN COENG TTHO;17D2 178D
KHMER CONSONANT SIGN COENG NA;17D2 178E
KHMER CONSONANT SIGN COENG TA;17D2 178F
KHMER CONSONANT SIGN COENG THA;17D2 1790
KHMER CONSONANT SIGN COENG TO;17D2 1791
KHMER CONSONANT SIGN COENG THO;17D2 1792
KHMER CONSONANT SIGN COENG NO;17D2 1793
KHMER CONSONANT SIGN COENG BA;17D2 1794
KHMER CONSONANT SIGN COENG PHA;17D2 1795
KHMER CONSONANT SIGN COENG PO;17D2 1796
KHMER CONSONANT SIGN COENG PHO;17D2 1797
KHMER CONSONANT SIGN COENG MO;17D2 1798
KHMER CONSONANT SIGN COENG YO;17D2 1799
KHMER CONSONANT SIGN COENG RO;17D2 179A
KHMER CONSONANT SIGN COENG LO;17D2 179B
KHMER CONSONANT SIGN COENG VO;17D2 179C
KHMER CONSONANT SIGN COENG SHA;17D2 179D
KHMER CONSONANT SIGN COENG SSA;17D2 179E
KHMER CONSONANT SIGN COENG SA;17D2 179F
KHMER CONSONANT SIGN COENG HA;17D2 17A0
KHMER CONSONANT SIGN COENG LA;17D2 17A1
KHMER VOWEL SIGN COENG QA;17D2 17A2
KHMER INDEPENDENT VOWEL SIGN COENG QU;17D2 17A7
KHMER INDEPENDENT VOWEL SIGN COENG RY;17D2 17AB
KHMER INDEPENDENT VOWEL SIGN COENG RYY;17D2 17AC
KHMER INDEPENDENT VOWEL SIGN COENG QE;17D2 17AF
KHMER VOWEL SIGN OM;17BB 17C6
KHMER VOWEL SIGN AAM;17B6 17C6
HIRAGANA LETTER BIDAKUON NGA;304B 309A
HIRAGANA LETTER BIDAKUON NGI;304D 309A
HIRAGANA LETTER BIDAKUON NGU;304F 309A
HIRAGANA LETTER BIDAKUON NGE;3051 309A
HIRAGANA LETTER BIDAKUON NGO;3053 309A
KATAKANA LETTER BIDAKUON NGA;30AB 309A
KATAKANA LETTER BIDAKUON NGI;30AD 309A
KATAKANA LETTER BIDAKUON NGU;30AF 309A
KATAKANA LETTER BIDAKUON NGE;30B1 309A
KATAKANA LETTER BIDAKUON NGO;30B3 309A
KATAKANA LETTER AINU CE;30BB 309A
KATAKANA LETTER AINU TU;30C4 309A
KATAKANA LETTER AINU TO;30C8 309A
KATAKANA LETTER AINU P;31F7 309A
MODIFIER LETTER EXTRA-HIGH EXTRA-LOW CONTOUR TONE BAR;02E5 02E9
MODIFIER LETTER EXTRA-LOW EXTRA-HIGH CONTOUR TONE BAR;02E9 02E5
File diff suppressed because it is too large Load Diff
+170
View File
@@ -0,0 +1,170 @@
# This file has its own compilation step because
# it needs to parse String.Unicode data and
# compile a digested module.
defmodule String.Unicode do
@moduledoc false
def version, do: {6,2,0}
to_binary = fn(codepoint) ->
:unicode.characters_to_binary([binary_to_integer(codepoint, 16)])
end
data_path = File.expand_path("../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])
cond do
upper != "" or lower != "" ->
{ [{ to_binary.(codepoint), upper, lower } | cacc], wacc }
bidi in ["B", "S", "WS"] ->
{ cacc, [to_binary.(codepoint) | wacc] }
true ->
{ cacc, wacc }
end
end
seqs_path = File.expand_path("../NamedSequences.txt", __FILE__)
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
codepoints = :binary.split(codepoints, " ", [:global])
codepoints = Enum.map codepoints, Regex.replace(%r/\s+/, &1, "")
codepoints = Enum.filter codepoints, fn(x) -> size(x) > 0 end
Enum.map codepoints, to_binary.(&1)
end
# 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)
def :downcase, args, [], do: code
end
def downcase(<< h, t :: binary >>) do
<< h >> <> downcase(t)
end
def downcase(<< >>) do
<< >>
end
# 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)
def :upcase, args, [], do: code
end
def upcase(<< h, t :: binary >>) do
<< h >> <> upcase(t)
end
def upcase(<< >>) do
<< >>
end
# Strip
def lstrip(""), do: ""
lc char inlist whitespace do
args = quote do: [unquote(char) <> rest]
exprs = quote do: lstrip(rest)
def :lstrip, args, [], do: exprs
end
def lstrip(other), do: other
def rstrip(""), do: ""
def rstrip(string) do
do_rstrip(string, "")
end
lc char inlist whitespace do
args = quote do: [unquote(char) <> rest, buffer]
exprs = quote do: do_rstrip(rest, unquote(char) <> buffer)
defp :do_rstrip, args, [], do: exprs
end
defp do_rstrip(<< char, string :: binary >>, buffer) do
<< buffer :: binary, char, do_rstrip(string, "") :: binary >>
end
defp do_rstrip(<<>>, _), do: <<>>
# Graphemes
lc codepoints inlist seqs do
seq_args = quote do: [<< unquote_splicing(codepoints), t :: binary >>]
seq_code = quote do: {<< unquote_splicing(codepoints) >>, t}
def :next_grapheme, seq_args, [], do: seq_code
end
def next_grapheme(<<>>) do
:no_grapheme
end
def next_grapheme(binary) when is_binary(binary) do
case next_codepoint(binary) do
:no_codepoint -> :no_grapheme
other -> other
end
end
def graphemes(binary) when is_binary(binary) do
do_graphemes(next_grapheme(binary))
end
defp do_graphemes({ c, rest }) do
[c|do_graphemes(next_grapheme(rest))]
end
defp do_graphemes(:no_grapheme) do
[]
end
# Codepoints
def next_codepoint(<<194, char, rest :: binary>>)
when char in 161..191,
do: { <<194, char>>, rest }
def next_codepoint(<<first, char, rest :: binary>>)
when first in 195..223 and char in 128..191,
do: { <<first, char>>, rest }
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 codepoints(binary) when is_binary(binary) do
do_codepoints(next_codepoint(binary))
end
defp do_codepoints({ c, rest }) do
[c|do_codepoints(next_codepoint(rest))]
end
defp do_codepoints(:no_codepoint) do
[]
end
end
+1 -1
View File
@@ -16,7 +16,7 @@
%% warn_missing_spec,
%% warn_untyped_record,
%% warnings_as_errors,
no_debug_info
debug_info
]}.
{yrl_opts, [
+69 -37
View File
@@ -1,20 +1,27 @@
-module(elixir).
-behaviour(application).
-export([start_cli/0, start_app/0,
-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]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Top level types
-export_type([char_list/0]).
-type char_list() :: string().
% OTP APPLICATION API
-export([start/2, stop/1, config_change/3]).
start(_Type, _Args) ->
%% Set the shell to unicode so printing inside files work
io:setopts(standard_io, [{encoding,unicode}]),
io:setopts(standard_error, [{encoding,unicode}]),
%% Set the shell to unicode so printing inside scripts work
%% Those can take a while, so let's do it in a new process
spawn(fun() ->
io:setopts(standard_io, [{encoding,unicode}]),
io:setopts(standard_error, [{encoding,unicode}])
end),
elixir_sup:start_link([]).
stop(_S) ->
@@ -23,52 +30,70 @@ stop(_S) ->
config_change(_Changed, _New, _Remove) ->
ok.
%% ELIXIR ENTRY POINTS
%% escript entry point
% Start the Elixir app. This is the proper way to boot Elixir from
% inside an Erlang process.
start_app() ->
case lists:keyfind(?MODULE, 1, application:loaded_applications()) of
false -> application:start(?MODULE);
_ -> ok
end.
main(Args) ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(Args).
% Boot and process given options. Invoked by Elixir's script.
start_cli() ->
start_app(),
'Elixir.Kernel.CLI':process_argv(init:get_plain_arguments()).
application:start(?MODULE),
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% EVAL HOOKS
scope_for_eval(Opts) ->
File = case orddict:find(file, Opts) of
{ ok, F } -> to_binary(F);
error -> <<"nofile">>
case lists:keyfind(file, 1, Opts) of
{ file, RawFile } -> File = to_binary(RawFile);
false -> File = <<"nofile">>
end,
Local = case orddict:find(delegate_locals_to, Opts) of
{ ok, L } -> L;
error -> nil
case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, Local } -> Local;
false -> Local = nil
end,
#elixir_scope{file=File,local=Local}.
case lists:keyfind(aliases, 1, Opts) of
{ aliases, Aliases } -> Aliases;
false -> Aliases = []
end,
case lists:keyfind(requires, 1, Opts) of
{ requires, Requires } -> Requires;
false -> Requires = elixir_dispatch:default_requires()
end,
case lists:keyfind(functions, 1, Opts) of
{ functions, Functions } -> Functions;
false -> Functions = elixir_dispatch:default_functions()
end,
case lists:keyfind(macros, 1, Opts) of
{ macros, Macros } -> Macros;
false -> Macros = elixir_dispatch:default_macros()
end,
#elixir_scope{
file=File, local=Local,
macros=Macros, functions=Functions,
requires=Requires, aliases=Aliases }.
%% String evaluation
eval(String, Binding) -> eval(String, Binding, []).
eval(String, Binding, Opts) ->
case orddict:find(line, Opts) of
{ ok, Line } -> [];
error -> Line = 1
case lists:keyfind(line, 1, Opts) of
false -> Line = 1;
{ line, Line } -> []
end,
eval(String, Binding, Line, scope_for_eval(Opts)).
eval(String, Binding, Line, #elixir_scope{file=File} = S) when
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
Forms = elixir_translator:forms(String, Line, File),
Forms = elixir_translator:'forms!'(String, Line, File, []),
eval_forms(Forms, Binding, S).
%% Quoted evaluation
@@ -76,9 +101,9 @@ eval(String, Binding, Line, #elixir_scope{file=File} = S) when
eval_quoted(Tree, Binding) -> eval_quoted(Tree, Binding, []).
eval_quoted(Tree, Binding, Opts) ->
case orddict:find(line, Opts) of
{ ok, Line } -> [];
error -> Line = 1
case lists:keyfind(line, 1, Opts) of
{ line, Line } -> [];
false -> Line = 1
end,
eval_quoted(Tree, Binding, Line, scope_for_eval(Opts)).
@@ -89,7 +114,12 @@ eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) ->
%% internal API not meant for external usage.
eval_forms(Tree, Binding, RawScope) ->
Scope = RawScope#elixir_scope{vars=binding_dict(Binding)},
Scope = RawScope#elixir_scope{
vars=binding_dict(Binding),
temp_vars=[],
clause_vars=nil,
counter=[]
},
{ ParseTree, NewScope } = elixir_translator:translate(Tree, Scope),
case ParseTree of
[] -> { nil, Binding, NewScope };
@@ -103,16 +133,18 @@ eval_forms(Tree, Binding, RawScope) ->
to_binary(Bin) when is_binary(Bin) -> Bin;
to_binary(List) when is_list(List) -> list_to_binary(List).
binding_dict(List) -> binding_dict(List, dict:new()).
binding_dict([{H,_}|T], Dict) -> binding_dict(T, dict:store(H, H, Dict));
binding_dict(List) -> binding_dict(List, orddict:new()).
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
binding_dict([], Dict) -> Dict.
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
case atom_to_list(Var) of
"_@" ++ _ -> final_binding(T, Acc, Binding, Vars);
_ ->
RealName = dict:fetch(Var, Vars),
case lists:member($@, atom_to_list(Var)) of
true ->
final_binding(T, Acc, Binding, Vars);
false ->
RealName = orddict:fetch({ Var, nil }, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
@@ -124,4 +156,4 @@ normalize_binding(Binding) ->
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_@MODULE', nil, Keyword)
end.
end.
+71 -54
View File
@@ -13,12 +13,12 @@ get_pairs(Line, Key, Clauses, S) ->
get_pairs(Line, Key, Clauses, S, false).
get_pairs(Line, Key, Clauses, S, AllowNil) ->
case orddict:find(Key, Clauses) of
{ ok, { '->', _, Pairs } } ->
case lists:keyfind(Key, 1, Clauses) of
{ Key, { '->', _, Pairs } } ->
[{ Key, Left, Right } || { Left, Right } <- Pairs];
{ ok, nil } when AllowNil ->
{ Key, nil } when AllowNil ->
[];
{ ok, _ } ->
{ Key, _ } ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
_ ->
[]
@@ -27,7 +27,7 @@ get_pairs(Line, Key, Clauses, S, AllowNil) ->
% Function for translating assigns.
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= assign ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=dict:new()}),
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=orddict:new()}),
{ Result, NewS#elixir_scope{context=Context} };
assigns(Fun, Args, S) -> Fun(Args, S).
@@ -75,7 +75,8 @@ extract_or_clauses(Term, Acc) -> [Term|Acc].
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args }.
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args(_) -> error.
% Extract guards when it is in the last element of the args
@@ -87,9 +88,12 @@ extract_last_guards(Args) ->
% Function for translating macros with match style like case and receive.
match(Line, DecoupledClauses, RawS) ->
S = RawS#elixir_scope{clause_vars=dict:new()},
match(Line, Clauses, #elixir_scope{clause_vars=C1} = S) ->
{ TC, TS } = do_match(Line, 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),
@@ -106,9 +110,11 @@ match(Line, DecoupledClauses, RawS) ->
% Now get all the variables defined inside each clause
CV = lists:reverse(RawCV),
NewVars = lists:umerge([lists:sort(dict:fetch_keys(X)) || X <- CV]),
case NewVars of
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
SharedVars = ordsets:intersection(CV),
case AllVars of
[] -> { TClauses, TS };
_ ->
% Create a new scope that contains a list of all variables
@@ -116,47 +122,48 @@ match(Line, DecoupledClauses, RawS) ->
% 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 normalize_vars/2, TS, NewVars),
{ 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
LeftVars = [{var, Line, NewValue} || {_, NewValue,_} <- FinalVars],
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, FS),
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.
Expander = fun(Clause, Counter) ->
ClauseVars = lists:nth(Counter, CV),
RightVars = [normalize_clause_var(Var, OldValue, ClauseVars) || {Var, _, OldValue} <- FinalVars],
AssignExpr = generate_match(Line, LeftVars, RightVars),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
% If the last sentence has a match clause, we need to assign its value
% in the variable list. If not, we insert the variable list before the
% final clause in order to keep it tail call optimized.
FinalClauseExprs = case has_match_tuple(Final) of
true ->
case Final of
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
[UserVar,AssignExpr,Final|RawClauseExprs];
_ ->
StorageExpr = { match, Line, StorageVar, Final },
[StorageVar,AssignExpr,StorageExpr|RawClauseExprs]
end;
false ->
[Final,AssignExpr|RawClauseExprs]
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
{ FinalClause, Counter + 1 }
end,
{ FClauses, _ } = lists:mapfoldl(Expander, 1, TClauses),
{ FClauses, SS }
% 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
end.
expand_clauses(Line, [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),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
% If the last sentence has a match clause, we need to assign its value
% in the variable list. If not, we insert the variable list before the
% final clause in order to keep it tail call optimized.
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
true ->
case Final of
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
_ ->
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
StorageExpr = { match, Line, StorageVar, Final },
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
end;
false ->
{ [Final,AssignExpr|RawClauseExprs], S }
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
expand_clauses(Line, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
expand_clauses(_Line, [], [], _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) ->
@@ -199,26 +206,36 @@ has_match_tuple(_) -> false.
% Normalize the given var checking its existence in the scope var dictionary.
normalize_vars(Var, #elixir_scope{vars=Vars, clause_vars=ClauseVars} = S) ->
{ { _, _, NewValue }, NS } = elixir_scope:build_erl_var(0, S),
normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
{ NewValue, S1 } =
case orddict:find(Key, Shared) of
{ ok, SharedValue } ->
{ SharedValue, S };
error ->
{ { _, _, ErlValue }, ErlS } = case (Kind == quoted) or (S#elixir_scope.noname) of
true -> elixir_scope:build_erl_var(0, S);
false -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
end,
{ ErlValue, ErlS }
end,
FS = NS#elixir_scope{
vars=dict:store(Var, NewValue, Vars),
clause_vars=dict:store(Var, NewValue, ClauseVars)
S2 = S1#elixir_scope{
vars=orddict:store(Key, NewValue, Vars),
clause_vars=orddict:store(Key, NewValue, ClauseVars)
},
Expr = case dict:find(Var, Vars) of
Expr = case orddict:find(Key, Vars) of
{ ok, OldValue } -> { var, 0, OldValue };
error -> { atom, 0, nil }
end,
{ { Var, NewValue, Expr }, FS }.
{ { Var, Kind, NewValue, Expr }, S2 }.
% Normalize a var by checking if it was defined in the clause.
% If so, use it, otherwise use from main scope.
normalize_clause_var(Var, OldValue, ClauseVars) ->
case dict:find(Var, ClauseVars) of
normalize_clause_var(Var, Kind, OldValue, ClauseVars) ->
case orddict:find({ Var, Kind }, ClauseVars) of
{ ok, ClauseValue } -> { var, 0, ClauseValue };
error -> OldValue
end.
+69 -17
View File
@@ -6,60 +6,112 @@
argv=[],
loaded=[],
at_exit=[],
compiler_options=[{docs,true}]
pool=[],
counter=0,
compiler_options=[{docs,true},{debug_info,true}],
waiting=[]
}).
start_link() ->
{ ok, _ } = gen_server:start_link({local, elixir_code_server}, ?MODULE, [], []).
init(_args) ->
process_flag(trap_exit, true),
{ ok, #elixir_code_server{} }.
handle_call({loaded, Path}, _From, Config) ->
Current = Config#elixir_code_server.loaded,
case lists:member(Path, Current) of
true ->
{ reply, duplicated, Config#elixir_code_server{loaded=Current} };
false ->
{ reply, ok, Config#elixir_code_server{loaded=[Path|Current]} }
handle_call({ wait_until_finished, Pid }, _, Config) ->
Waiting = Config#elixir_code_server.waiting,
case is_list(Waiting) of
true -> { reply, wait, Config#elixir_code_server{waiting=[Pid|Waiting]} };
false -> { reply, ok, Config }
end;
handle_call({at_exit, AtExit}, _From, Config) ->
handle_call({ acquire, Path }, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
{ ok, true } ->
{ reply, loaded, Config };
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
Queued = orddict:store(Path, { Ref, [From|List] }, Current),
{ reply, { queued, Ref }, Config#elixir_code_server{loaded=Queued} };
error ->
Queued = orddict:store(Path, { make_ref(), [] }, Current),
{ 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) ->
handle_call({ argv, Argv }, _From, Config) ->
{ reply, ok, Config#elixir_code_server{argv=Argv} };
handle_call({compiler_options, Options}, _From, Config) ->
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, Config#elixir_code_server.loaded, Config };
{ reply, [F || { F, true } <- Config#elixir_code_server.loaded], Config };
handle_call(at_exit, _From, Config) ->
{ reply, Config#elixir_code_server.at_exit, Config };
handle_call(flush_at_exit, _From, Config) ->
{ reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]} };
handle_call(argv, _From, Config) ->
{ reply, Config#elixir_code_server.argv, Config };
handle_call(compiler_options, _From, Config) ->
{ reply, Config#elixir_code_server.compiler_options, Config };
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
[H|T] ->
{ reply, module_tuple(H), Config#elixir_code_server{pool=T} };
[] ->
Counter = Config#elixir_code_server.counter,
{ reply, module_tuple(Counter), Config#elixir_code_server{counter=Counter+1} }
end;
handle_call(_Request, _From, Config) ->
{ reply, undef, Config }.
handle_cast(finished, Config) ->
Waiting = Config#elixir_code_server.waiting,
[Pid ! { elixir_code_server, finished } || Pid <- lists:reverse(Waiting)],
{ noreply, Config#elixir_code_server{waiting=done} };
handle_cast({ loaded, Path }, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
{ ok, true } ->
{ noreply, Config };
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
[Pid ! { elixir_code_server, Ref, loaded } || { Pid, _Tag } <- lists:reverse(List)],
Done = orddict:store(Path, true, Current),
{ noreply, Config#elixir_code_server{loaded=Done} };
error ->
Done = orddict:store(Path, true, Current),
{ noreply, Config#elixir_code_server{loaded=Done} }
end;
handle_cast({ unload_files, Files }, Config) ->
Current = Config#elixir_code_server.loaded,
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
handle_cast({ return_module_name, I }, #elixir_code_server{pool=Pool} = Config) ->
{ noreply, Config#elixir_code_server{pool=[I|Pool]} };
handle_cast(_Request, Config) ->
{ noreply, Config }.
handle_info(_Request, Config) ->
{ noreply, Config }.
terminate(Reason, Config) ->
io:format("[FATAL] ~p crashed:\n~p~n", [?MODULE, Reason]),
io:format("[FATAL] ~p snapshot:\n~p~n", [?MODULE, Config]),
terminate(_Reason, _Config) ->
ok.
code_change(_Old, Config, _Extra) ->
{ ok, Config }.
{ ok, Config }.
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
+45 -81
View File
@@ -1,6 +1,6 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/5]).
-export([core/0, module/3, eval_forms/4]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -11,9 +11,9 @@
get_opt(Key) -> get_opt(Key, get_opts()).
get_opt(Key, Dict) ->
case orddict:find(Key, Dict) of
{ ok, Value } -> Value;
error -> false
case lists:keyfind(Key, 1, Dict) of
false -> false;
{ Key, Value } -> Value
end.
get_opts() ->
@@ -26,8 +26,8 @@ string(Contents, File) when is_list(Contents), is_binary(File) ->
try
put(elixir_compiled, []),
Forms = elixir_translator:forms(Contents, 1, File),
eval_forms(Forms, 1, File, [], #elixir_scope{file=File}),
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
lists:reverse(get(elixir_compiled))
after
put(elixir_compiled, Previous)
@@ -37,12 +37,8 @@ string(Contents, File) when is_list(Contents), is_binary(File) ->
file(Relative) when is_binary(Relative) ->
File = filename:absname(Relative),
case file:read_file(File) of
{ok, Bin} ->
string(unicode:characters_to_list(Bin), File);
Error ->
erlang:error(Error)
end.
{ ok, Bin } = file:read_file(File),
string(unicode:characters_to_list(Bin), File).
%% Compiles a file to the given path (directory).
@@ -53,34 +49,22 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
%% Evaluates the contents/forms by compiling them to an Erlang module.
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=nil} = S) ->
eval_forms(Forms, Line, Module, nil, Vars, S);
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=Value} = S) ->
eval_forms(Forms, Line, Module, Value, Vars, S).
eval_forms(Forms, Line, RawModule, Value, Vars, S) ->
case (Value == nil) andalso allows_fast_compilation(Forms) of
true -> eval_compilation(Forms, Vars, S);
false -> code_loading_compilation(Forms, Line, RawModule, Value, Vars, S)
end.
eval_compilation(Forms, Vars, S) ->
Binding = [{ Var, Value } || { _, Var, Value } <- Vars],
{ Result, _Binding, FS } = elixir:eval_forms(Forms, [{'_@MODULE',nil}|Binding], S),
{ Result, FS }.
code_loading_compilation(Forms, Line, RawModule, Value, Vars, S) ->
Module = escape_module(RawModule),
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),
Args = [X || { _, _, X } <- Vars],
Args = [X || { _, _, _, X } <- Vars],
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(Mod, _) ->
Res = Mod:'BOOTSTRAP'(Value, Args),
code:purge(Module),
%% 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),
code:delete(Module),
case code:soft_purge(Module) of
true -> return_module_name(I);
false -> ok
end,
Res
end), FS }.
@@ -113,23 +97,20 @@ module(Forms, File, Options, Bootstrap, Callback) when
%% Invoked from the Makefile.
core() ->
elixir:start_app(),
application:start(elixir),
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()],
AllLists = [filelib:wildcard(Wildcard) || Wildcard <- core_list()],
Files = lists:append(AllLists) -- core_main(),
[core_file(File) || File <- 'Elixir.List':uniq(Files)].
[core_file(File) || File <- core_main()].
%% HELPERS
no_auto_import() ->
{ attribute, 0, compile, {
no_auto_import, erlang:module_info(exports) } }.
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) ->
Cons = lists:foldr(fun({ _, Var, _ }, Acc) ->
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
@@ -144,31 +125,13 @@ module_form(Exprs, Line, File, Module, Vars) when
] }
].
%% Fast compilation is available?
%% Generate module names from code server.
allows_fast_compilation([{defmodule,_,_}|T]) -> allows_fast_compilation(T);
allows_fast_compilation([]) -> true;
allows_fast_compilation(_) -> false.
retrieve_module_name() ->
gen_server:call(elixir_code_server, retrieve_module_name).
%% Escape the module name, removing slashes, dots,
%% so it can be loaded by Erlang.
escape_module(Module) when is_atom(Module) ->
escape_module(atom_to_list(Module));
escape_module(Module) when is_binary(Module) ->
escape_module(binary_to_list(Module));
escape_module(Module) when is_list(Module) ->
list_to_atom(escape_each(Module)).
escape_each([H|T]) when H >= $A, H =< $Z; H >= $a, H =< $z; H >= $0, H =< $9; H == $- ->
[H|escape_each(T)];
escape_each([_|T]) ->
[$_|escape_each(T)];
escape_each([]) -> [].
return_module_name(I) ->
gen_server:cast(elixir_code_server, { return_module_name, I }).
%% Receives a module Binary and outputs it in the given path.
@@ -190,32 +153,33 @@ core_file(File) ->
exit(1)
end.
core_list() ->
[
"lib/elixir/lib/uri/parser.ex",
"lib/elixir/lib/elixir/formatter.ex",
"lib/elixir/lib/dict.ex",
"lib/elixir/lib/*/*.ex",
"lib/elixir/lib/*.ex"
].
core_main() ->
[
"lib/elixir/lib/kernel.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/keyword.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/list.ex",
"lib/elixir/lib/kernel/typespec.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/record/extractor.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/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/gen_server/behavior.ex",
"lib/elixir/lib/code.ex"
"lib/elixir/lib/io.ex",
"lib/elixir/lib/file.ex",
"lib/elixir/lib/access.ex",
"lib/elixir/lib/regex.ex",
"lib/elixir/lib/system.ex",
"lib/elixir/lib/kernel/cli.ex",
"lib/elixir/lib/kernel/error_handler.ex",
"lib/elixir/lib/kernel/parallel_compiler.ex",
"lib/elixir/lib/kernel/record_rewriter.ex"
].
%% ERROR HANDLING
+54 -57
View File
@@ -6,7 +6,7 @@
reset_last/1,
wrap_definition/7,
store_definition/8,
store_each/8,
store_each/7,
unwrap_stored_definitions/1,
format_error/1]).
-include("elixir.hrl").
@@ -19,7 +19,7 @@ table(Module) -> ?ELIXIR_ATOM_CONCAT([f, Module]).
build_table(Module) ->
FunctionTable = table(Module),
ets:new(FunctionTable, [set, named_table, public]),
ets:insert(FunctionTable, { last, [] }),
reset_last(Module),
FunctionTable.
delete_table(Module) ->
@@ -63,71 +63,63 @@ wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Expr, RawS) ->
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Body, RawS) ->
S = elixir_scope:deserialize(RawS),
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~s", [Kind]);
store_definition(Kind, Line, Module, Name, Args, Guards, RawExpr, RawS) ->
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
Arity = length(Args),
DS = elixir_scope:deserialize(RawS),
S = DS#elixir_scope{function={Name,Arity}, module=Module},
Expr = def_body(Line, RawExpr),
Expr = def_body(Line, Body),
CO = elixir_compiler:get_opts(),
Location = retrieve_file(Module, 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),
CO = elixir_compiler:get_opts(),
compile_docs(Kind, Line, Module, Name, Arity, Args, TS, CO),
File = TS#elixir_scope.file,
Table = table(Module),
Stack = S#elixir_scope.macro,
Location = retrieve_file(Module, CO),
File = TS#elixir_scope.file,
Table = table(Module),
%% Store function
case RawExpr of
skip_definition -> [];
_ ->
if
(Body == nil) -> [];
true ->
compile_super(Module, TS),
CheckClauses = S#elixir_scope.check_clauses,
store_each(CheckClauses, Kind, File, Location,
Stack, Table, length(Defaults), Function)
Table, length(Defaults), Function)
end,
%% Store defaults
[store_each(false, Kind, File, Location, Stack, Table, 0,
function_for_default(Kind, Name, Default)) || Default <- Defaults],
[store_each(false, Kind, File, Location, Table, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity }.
def_body(_Line, skip_definition) -> nil;
def_body(_Line, [{ do, Expr }]) -> Expr;
def_body(Line, Else) -> { 'try', Line, [Else] }.
def_body(_Line, nil) -> nil;
def_body(_Line, [{ do, Expr }]) -> Expr;
def_body(Line, Else) -> { 'try', Line, [Else] }.
%% Compile super clause
%% @on_definition
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
elixir_def_overridable:store(Module, Function, true);
compile_super(_Module, _S) -> [].
%% Compile docs
compile_docs(Kind, Line, Module, Name, Arity, Args, S, CO) ->
case elixir_compiler:get_opt(docs, CO) of
false -> [];
true ->
case 'Elixir.Module':compile_doc(Module, Line, Kind, { Name, Arity }, Args) of
{ error, Message } -> elixir_errors:handle_file_warning(S#elixir_scope.file,
{ Line, ?MODULE, { Message, { Name, Arity } } });
_ -> []
end
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO) ->
case elixir_compiler:get_opt(internal, CO) of
true ->
ok;
_ ->
Env = elixir_scope:to_ex_env({ Line, S }),
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
end.
%% Retrieve @file
retrieve_file(Module, CO) ->
case elixir_compiler:get_opt(internal, CO) of
true -> [];
_ ->
case 'Elixir.Module':read_attribute(Module, file) of
case 'Elixir.Module':get_attribute(Module, file) of
nil -> [];
Else ->
'Elixir.Module':delete_attribute(Module, file),
@@ -135,10 +127,20 @@ retrieve_file(Module, CO) ->
end
end.
%% Compile super
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
elixir_def_overridable:store(Module, Function, true);
compile_super(_Module, _S) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) ->
Args = elixir_quote:linify(Line, RawArgs),
Guards = elixir_quote:linify(Line, RawGuards),
Expr = elixir_quote:linify(Line, RawExpr),
Arity = length(Args),
IsMacro = is_macro(Kind),
@@ -212,9 +214,10 @@ unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Fu
);
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, Exports, [element(1, Fun)|Private], Def, Defmacro,
[{ element(1, Fun), element(3, Fun) }|Defmacrop], Functions
T, Exports, [Tuple|Private], Def, Defmacro,
[{ Tuple, element(3, Fun), element(5, Fun) }|Defmacrop], Functions
);
unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
@@ -223,13 +226,13 @@ unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functi
%% Helpers
function_for_stored_definition({{Name, Arity}, _, Line, _, [], _, _, Clauses}, {Functions,Tail}) ->
function_for_stored_definition({{Name, Arity}, _, Line, _, _, [], _, Clauses}, {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}, {Functions,Tail}) ->
{
Functions,
[
@@ -238,33 +241,33 @@ function_for_stored_definition({{Name, Arity}, _, Line, _, Location, _, _, Claus
]
}.
function_for_default(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
when Kind == defmacro; Kind == defmacrop ->
{ function, Line, Name, length(Args) - 1, [Clause] };
function_for_default(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
{ function, Line, Name, length(Args), [Clause] }.
%% Store each definition in the table.
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Stack, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
store_each(Check, Kind, File, Location, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
case ets:lookup(Table, {Name, Arity}) of
[{{Name, Arity}, StoredKind, _, _, StoredLocation, StoredStack, StoredDefaults, StoredClauses}] ->
[{{Name, Arity}, StoredKind, _, _, StoredCheck, StoredLocation, StoredDefaults, StoredClauses}] ->
FinalLocation = StoredLocation,
FinalDefaults = Defaults + StoredDefaults,
FinalClauses = Clauses ++ StoredClauses,
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
Check andalso (Stack == StoredStack) andalso check_valid_clause(Line, File, Name, Arity, Table);
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
[] ->
FinalLocation = Location,
FinalDefaults = Defaults,
FinalClauses = Clauses,
Check andalso ets:insert(Table, { last, { Name, Arity } })
end,
ets:insert(Table, {{Name, Arity}, Kind, Line, File, FinalLocation, Stack, FinalDefaults, FinalClauses}).
ets:insert(Table, {{Name, Arity}, Kind, Line, File, Check, FinalLocation, FinalDefaults, FinalClauses}).
%% Validations
@@ -291,12 +294,6 @@ check_valid_defaults(Line, File, Name, Arity, _) ->
format_error({clauses_with_docs,{Name,Arity}}) ->
io_lib:format("function ~s/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
format_error({private_doc,{Name,Arity}}) ->
io_lib:format("function ~s/~B is private, @doc's are always discarded for private functions", [Name, Arity]);
format_error({existing_doc,{Name,Arity}}) ->
io_lib:format("@doc's for function ~s/~B have been given more than once, the first version is being kept", [Name, Arity]);
format_error({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
io_lib:format("function ~s/~B does not match previous clause ~s/~B", [Name, Arity, ElseName, ElseArity]);
+9 -9
View File
@@ -13,10 +13,9 @@ unpack(Kind, Name, Args, S) ->
%% clauses and a list of clauses for the default calls.
unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = build_match(Acc, Line, []),
{ Args, Invoke } = extract_defaults(List, [], []),
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
SM = S#elixir_scope{counter=length(Base)},
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, SM),
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, S),
{ InvokeArgs, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
Call = { call, Line,
@@ -35,13 +34,14 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
% Extract default values from args following the current default clause.
extract_defaults([{'//', _, [_Expr, Default]}|T], NewArgs, NewInvoke) ->
extract_defaults(T, NewArgs, [Default|NewInvoke]);
extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
extract_defaults([H|T], NewArgs, NewInvoke) ->
extract_defaults(T, [H|NewArgs], [H|NewInvoke]);
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
H = { ?ELIXIR_ATOM_CONCAT(["_@D", Counter]), Line, nil },
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
extract_defaults([], NewArgs, NewInvoke) ->
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
{ lists:reverse(NewArgs), lists:reverse(NewInvoke) }.
% Build matches for all the previous argument until the current default clause.
@@ -49,7 +49,7 @@ extract_defaults([], NewArgs, NewInvoke) ->
build_match([], _Line, Acc) -> Acc;
build_match([_|T], Line, Acc) ->
Var = { ?ELIXIR_ATOM_CONCAT(["_@", length(T)]), Line, nil },
Var = { ?ELIXIR_ATOM_CONCAT(["_@D", length(T)]), Line, nil },
build_match(T, Line, [Var|Acc]).
% Given the invoked function name based on the kind
+3 -2
View File
@@ -69,9 +69,10 @@ rewrite_clause(Else, _) -> Else.
%% Error handling
check_unused_local_macros(File, Recorded, PMacros) ->
check_unused_local_macros(File, Recorded, Defmacrop) ->
[elixir_errors:handle_file_warning(File,
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line } <- PMacros, not lists:member(Fun, Recorded)].
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line, Check } <- Defmacrop,
Check, not lists:member(Fun, Recorded)].
format_error({unused_macro,{Name, Arity}}) ->
io_lib:format("macro ~s/~B is unused", [Name, Arity]).
+18 -16
View File
@@ -16,7 +16,7 @@ overridable(Module, Value) ->
is_defined(Module, Tuple) ->
Overridable = overridable(Module),
case orddict:find(Tuple, Overridable) of
{ ok, { _, [_|_] } } -> true;
{ ok, { _, _, _ } } -> true;
_ -> false
end.
@@ -53,37 +53,39 @@ name(Module, Function) ->
name(Module, Function, overridable(Module)).
name(_Module, { Name, _ } = Function, Overridable) ->
{ Count, _ } = orddict:fetch(Function, Overridable),
?ELIXIR_ATOM_CONCAT(["OVERRIDABLE-", Count, "-", Name]).
{ Count, _, _ } = orddict:fetch(Function, Overridable),
?ELIXIR_ATOM_CONCAT([Name, " (overridable ", Count, ")"]).
%% Store
store(Module, Function, GenerateName) ->
Overridable = overridable(Module),
{ Count, [H|T] } = orddict:fetch(Function, Overridable),
overridable(Module, orddict:store(Function, { Count, T }, Overridable)),
case orddict:fetch(Function, Overridable) of
{ _Count, _Clause, true } -> ok;
{ Count, Clause, false } ->
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
{ { Name, Arity }, Kind, Line, File, _Check, Location, Defaults, Clauses } = Clause,
{ { Name, Arity }, Kind, Line, File, Location, Stack, Defaults, Clauses } = H,
{ FinalKind, FinalName } = case GenerateName of
true -> { defp, name(Module, Function, Overridable) };
false -> { Kind, Name }
end,
{ FinalKind, FinalName } = case GenerateName of
true -> { defp, name(Module, Function, Overridable) };
false -> { Kind, Name }
end,
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, File, Location,
Stack, elixir_def:table(Module), Defaults, Def).
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), Defaults, Def)
end.
%% Store pending declarations that were not manually made concrete.
store_pending(Module) ->
[store(Module, X, false) || { X, { _, [_|_] } } <- overridable(Module),
[store(Module, X, false) || { X, { _, _, false } } <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)].
%% Error handling
format_error({ no_super, Module, { Name, Arity } }) ->
Bins = [ format_fa(X) || { X, { _, [_|_] } } <- overridable(Module)],
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
[Name, Arity, elixir_errors:inspect(Module), Joined]).
+16 -16
View File
@@ -6,7 +6,7 @@
dispatch_require/6, dispatch_import/5,
require_function/5, import_function/4,
expand_import/8, expand_require/8,
format_error/1]).
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -58,9 +58,9 @@ dispatch_import(Line, Name, Args, S, Callback) ->
Callback();
{ error, internal } ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
elixir_macros:translate_macro({ Name, Line, Args }, S);
{ ok, Receiver, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
elixir_macros:translate({ Name, Line, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Line, Tree, S)
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
@@ -85,9 +85,9 @@ dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_macros:translate_macro({ Name, Line, Args }, S);
elixir_macros:translate({ Name, Line, Args }, S);
{ ok, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
translate_expansion(Line, Tree, S)
end
end.
@@ -105,9 +105,9 @@ expand_import(Line, { Name, Arity } = Tuple, Args, Module, Function, Requires, M
{ ok, Module, expand_macro_fun(Line, Fun, Module, Name, Args, Module, Requires, SEnv) }
end;
?BUILTIN ->
case is_element(Tuple, in_elixir_macros()) of
false -> { error, internal };
true ->
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) }
end;
@@ -167,10 +167,12 @@ expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv).
translate_expansion(Line, Tree, Receiver, Name, Arity, S) ->
NewS = S#elixir_scope{macro=[{Line,Receiver,Name,Arity}|S#elixir_scope.macro]},
{ TTree, TS } = elixir_translator:translate_each(elixir_quote:linify(Line, Tree), NewS),
{ TTree, TS#elixir_scope{macro=S#elixir_scope.macro} }.
translate_expansion(Line, Tree, S) ->
{ TR, TS } = elixir_translator:translate_each(
elixir_quote:linify(Line, Tree),
S#elixir_scope{check_clauses=false}
),
{ TR, TS#elixir_scope{check_clauses=S#elixir_scope.check_clauses} }.
%% Helpers
@@ -268,14 +270,12 @@ in_erlang_functions() ->
{ exit, 1 },
{ float, 1 },
{ float_to_list, 1 },
{ halt, 0 },
{ halt, 1 },
{ halt, 2 },
{ hd, 1 },
{ integer_to_list, 1 },
{ integer_to_list, 2 },
{ iolist_size, 1 },
{ iolist_to_binary, 1 },
{ is_alive, 0 },
{ is_atom, 1 },
{ is_binary, 1 },
{ is_bitstring, 1 },
+46 -9
View File
@@ -26,6 +26,9 @@ 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().
syntax_error(Line, File, Message) when is_list(Message) ->
syntax_error(Line, File, iolist_to_binary(Message));
@@ -38,6 +41,8 @@ syntax_error(Line, File, Format, Args) ->
%% Raised on tokenizing/parsing
-spec parse_error(non_neg_integer(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
parse_error(Line, File, Error, []) ->
Message = case Error of
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
@@ -64,6 +69,8 @@ parse_error(Line, File, Error, Token) ->
%% Raised during compilation
-spec form_error(non_neg_integer(), file:filename(), module(), any()) -> no_return().
form_error(Line, File, Module, Desc) ->
Message = iolist_to_binary(format_error(Module, Desc)),
raise(Line, File, 'Elixir.CompileError', Message).
@@ -89,18 +96,25 @@ handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% Rewrite
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
Raw = "undefined callback function ~s/~B (behaviour ~s)",
Message = io_lib:format(Raw, [Fun,Arity,inspect(Module)]),
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~s function ~s/~B (for ~s ~s)",
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
io:format(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
Raw = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
case elixir_compiler:get_opt(internal) of
true -> [];
false ->
Message = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
io:format(file_format(Line, File, Message))
end;
Message = case erlang:function_exported(Module, behavior_info, 1) of
true -> Raw ++ " (maybe you meant behaviour_info instead of behavior_info?)";
false -> Raw
end,
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
io:format(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
io:format(file_format(Line, File, Message));
%% Default behavior
@@ -111,6 +125,8 @@ handle_file_warning(_, File, {Line,Module,Desc}) ->
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
-spec handle_file_error(file:filename(), {non_neg_integer(), module(), any()}) -> no_return().
handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc).
@@ -144,13 +160,34 @@ assert_function_scope(_Line, _Kind, #elixir_scope{function=Function}) -> Functio
raise(Line, File, Kind, Message) ->
Stacktrace = erlang:get_stacktrace(),
erlang:raise(error, { Kind, '__exception__', Message, iolist_to_binary(File), Line }, Stacktrace).
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
erlang:raise(error, Exception, Stacktrace).
file_format(Line, File, Message) ->
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
format_var(Var) ->
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
format_error([], Desc) ->
io_lib:format("~p", [Desc]);
format_error(Module, Desc) ->
Module:format_error(Desc).
Module:format_error(Desc).
protocol_or_behaviour(Module) ->
case is_protocol(Module) of
true -> protocol;
false -> behaviour
end.
is_protocol(Module) ->
case code:ensure_loaded(Module) of
{ module, _ } ->
case erlang:function_exported(Module, '__protocol__', 1) of
true -> Module:'__protocol__'(name) == Module;
false -> false
end;
{ error, _ } ->
false
end.
+62 -24
View File
@@ -35,22 +35,24 @@ recorded_locals(Module) ->
%% based on the given options and selector.
import(Line, Ref, Opts, Selector, S) ->
SF = case (Selector == all) or (Selector == functions) of
IncludeAll = (Selector == all) or (Selector == default),
SF = case IncludeAll or (Selector == functions) of
false -> S;
true ->
FunctionsFun = fun() -> get_functions(Ref) end,
FunctionsFun = fun() -> remove_underscored(Selector, get_functions(Ref)) end,
Functions = calculate(Line, Ref, Opts,
S#elixir_scope.functions, FunctionsFun, S),
S#elixir_scope{functions=Functions}
end,
SM = case (Selector == all) or (Selector == macros) of
SM = case IncludeAll or (Selector == macros) of
false -> SF;
true ->
MacrosFun = fun() ->
case Selector of
all -> get_optional_macros(Ref);
_ -> get_macros(Line, Ref, SF)
case IncludeAll of
true -> remove_underscored(Selector, get_optional_macros(Ref));
false -> get_macros(Line, Ref, SF)
end
end,
Macros = calculate(Line, Ref, Opts,
@@ -68,27 +70,31 @@ calculate(Line, Key, Opts, Old, AvailableFun, S) ->
File = S#elixir_scope.file,
All = keydelete(Key, Old),
New = case orddict:find(only, Opts) of
{ ok, Only } ->
New = case keyfind(only, Opts) of
{ only, RawOnly } ->
Only = expand_fun_arity(Line, 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())
end;
error ->
case orddict:find(except, Opts) of
{ ok, Except } ->
false ->
case keyfind(except, Opts) of
false -> AvailableFun() ;
{ except, [] } -> AvailableFun();
{ except, RawExcept } ->
Except = expand_fun_arity(Line, except, RawExcept, S),
case keyfind(Key, Old) of
false -> AvailableFun() -- Except;
{Key,ToRemove} -> ToRemove -- Except
end;
error -> AvailableFun()
end
end
end,
%% Normalize the data before storing it
Final = ordsets:from_list(New -- internal_funs()),
Set = ordsets:from_list(New),
Final = remove_internals(Set),
case Final of
[] -> All;
@@ -99,6 +105,29 @@ calculate(Line, Key, Opts, Old, AvailableFun, S) ->
[{ Key, Final }|All]
end.
%% Ensure we are expanding macros and stuff
expand_fun_arity(Line, Kind, Value, S) ->
{ TValue, _S } = elixir_translator:translate_each(Value, S),
cons_to_keywords(Line, Kind, TValue, S).
cons_to_keywords(Line, Kind, { cons, _, Left, { nil, _ } }, S) ->
[tuple_to_fun_arity(Line, 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(Line, Kind, _, S) ->
elixir_errors:syntax_error(Line, 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) ->
{ Atom, Integer };
tuple_to_fun_arity(Line, Kind, _, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file,
"invalid value for :~s, expected a list with functions and arities", [Kind]).
%% Retrieve functions and macros from modules
get_exports(Module) ->
@@ -207,10 +236,10 @@ format_error({import_conflict,{Receiver, Name, Arity}}) ->
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({local_conflict,{_, Name, Arity}}) ->
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir internal macros", [Name, Arity]);
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir special forms", [Name, Arity]);
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir internal macros",
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir special forms",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({ no_macros, Module }) ->
@@ -236,13 +265,22 @@ intersection([], _All) -> [].
%% Internal funs that are never imported etc.
internal_funs() ->
[
{ module_info, 0 },
{ module_info, 1 },
{ '__info__', 1 },
{ '__using__', 1 }
].
remove_underscored(default, List) -> remove_underscored(List);
remove_underscored(_, List) -> List.
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
case atom_to_list(Name) of
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
"_" ++ _ -> remove_underscored(T);
_ -> [H|remove_underscored(T)]
end;
remove_underscored(T) ->
T.
remove_internals(Set) ->
ordsets:del_element({ module_info, 1 },
ordsets:del_element({ module_info, 0 }, Set)).
%% Macros implemented in Erlang that are not overridable.
@@ -280,4 +318,4 @@ non_overridable_macros() ->
{'super?',0},
{'bc','*'},
{'lc','*'}
].
].
+43 -25
View File
@@ -2,7 +2,6 @@
-module(elixir_interpolation).
-export([extract/5, unescape_chars/1, unescape_chars/2,
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
-define(is_octal(S), S >= $0 andalso S =< $7).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -75,42 +74,61 @@ unescape_token(Other, _Map) -> Other.
% Unescape chars. For instance, "\" "n" (two chars) needs to be converted to "\n" (one char).
-define(to_octal(List),
).
-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).
unescape_chars(String, Map) ->
Octals = case Map($0) of
false -> false;
_ -> true
end,
unescape_chars(String, Map, Octals, <<>>).
Octals = Map($0) /= false,
Hex = Map($x) /= false,
unescape_chars(String, Map, Octals, Hex, <<>>).
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B), ?is_octal(C) ->
to_octal(Rest, Map, [A,B,C], Acc);
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?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, Acc) when ?is_octal(A), ?is_octal(B) ->
to_octal(Rest, Map, [A,B], Acc);
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A), ?is_octal(B) ->
append_escaped(Rest, Map, [A,B], true, Hex, Acc, 8);
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Acc) when ?is_octal(A) ->
to_octal(Rest, Map, [A], Acc);
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Hex, Acc) when ?is_octal(A) ->
append_escaped(Rest, Map, [A], true, Hex, Acc, 8);
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Acc) ->
unescape_chars(<<$\\,$x,A,B,Rest/binary>>, Map, Octal, true, Acc) when ?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) ->
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) ->
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) ->
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) ->
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) ->
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) ->
append_escaped(Rest, Map, [A,B,C,D,E,F], Octal, true, Acc, 16);
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Hex, Acc) ->
case Map(Escaped) of
false -> unescape_chars(Rest, Map, Octals, <<Acc/binary, $\\, Escaped>>);
Other -> unescape_chars(Rest, Map, Octals, <<Acc/binary, Other>>)
false -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, $\\, Escaped>>);
Other -> unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Other>>)
end;
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Acc) ->
unescape_chars(Rest, Map, Octals, <<Acc/binary, Char>>);
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Hex, Acc) ->
unescape_chars(Rest, Map, Octals, Hex, <<Acc/binary, Char>>);
unescape_chars(<<>>, _Map, _Octals, Acc) -> Acc.
unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
to_octal(Rest, Map, Octal, Acc) ->
unescape_chars(Rest, Map, true, <<Acc/binary, (list_to_integer(Octal, 8))/integer>>).
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>>).
% Unescape Helpers
@@ -147,10 +165,10 @@ wrap_interpol(_Line, Form) when is_binary(Form) ->
Form;
wrap_interpol(Line, Form) ->
{ '|', Line, [{ { '.', Line, ['Elixir.Binary.Chars', to_binary] }, Line, [Form]}, binary]}.
{ '::', Line, [{ { '.', Line, ['Elixir.Binary.Chars', to_binary] }, Line, [Form]}, { binary, Line, nil }]}.
forms(String, StartLine, File) ->
case elixir_translator:raw_forms(String, StartLine, File) of
case elixir_translator:forms(String, StartLine, File, []) of
{ ok, [] } -> nil;
{ ok, [Forms] } when not is_list(Forms) -> Forms;
{ ok, Forms } -> { '__block__', StartLine, Forms };
+201
View File
@@ -0,0 +1,201 @@
-module(elixir_literal).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2]).
-import(elixir_scope, [umergev/2, umergec/2]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
translate({ '<<>>', Line, 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);
_ ->
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Line, { S, S }),
{ TArgs, umergec(SV, SC) }
end;
Else -> Else
end;
translate({ '{}', Line, 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 };
Else -> Else
end;
translate({ '[]', _Line, [] }, S) ->
{ { nil, 0 }, S };
translate({ '[]', _Line, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
[RTail|RArgs] = lists:reverse(Args),
case RTail of
{'|',_,[Left,Right]} ->
Exprs = [Left|RArgs],
{ Tail, ST } = translate_each(Right, S),
ListS = umergec(S, ST);
_ ->
Exprs = [RTail|RArgs],
Tail = { nil, 0 },
ST = S,
ListS = S
end,
{ FExprs, FS } = case S#elixir_scope.context of
assign ->
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
_ ->
{ TArgs, { SC, SV } } =
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
{ TArgs, umergec(SV, SC) }
end,
{ FExprs, umergev(ST, FS) };
Else -> Else
end;
translate({ Left, Right }, S) ->
translate({ '{}', 0, [Left, Right]}, S);
translate(Args, S) when is_list(Args) ->
translate({ '[]', 0, Args }, S);
translate(Number, S) when is_integer(Number) ->
{ { integer, 0, Number }, S };
translate(Number, S) when is_float(Number) ->
{ { float, 0, Number }, S };
translate(Atom, S) when is_atom(Atom) ->
{ { atom, 0, Atom }, S };
translate(Bitstring, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
%% 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
% * 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_each(_Fun, [], _Line, 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),
%% 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
end,
{ Size, Types } = extract_bit_values(Line, V, ES),
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Size, Types }|Acc]);
build_bitstr_each(Fun, [H|T], Line, S, Acc) ->
{ Expr, NS } = Fun(H, S),
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, default, default }|Acc]).
%% Extra bitstring specifiers
extract_bit_values(Line, V, S) when is_list(V) ->
extract_bit_values(Line, V, default, [], S);
extract_bit_values(Line, V, S) ->
extract_bit_values(Line, [V], S).
extract_bit_values(Line, [{ Value, _CallLine, 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);
NewTypes when is_list(NewTypes) ->
extract_bit_values(Line, T, Size, NewTypes, S)
end;
extract_bit_values(Line, [{ Value, CallLine, [_] = 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);
{ error, Msg } ->
elixir_errors:syntax_error(CallLine, S#elixir_scope.file, Msg);
{ NewSize, NewTypes } ->
extract_bit_values(Line, 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(Line, [_|_] = All, Size, Types, S) ->
handle_unknown_specifier(Line, All, Size, Types, S);
extract_bit_values(_Line, [], Size, [], _S) ->
{ Size, default };
extract_bit_values(_Line, [], Size, Types, _S) ->
{ Size, lists:reverse(Types) }.
extract_bit_type(Value, Types) when
Value == binary; Value == integer; Value == float; Value == bitstring;
Value == bytes; Value == bits;
Value == utf8; Value == utf16; Value == utf32;
Value == signed; Value == unsigned;
Value == big; Value == little; Value == native ->
[Value|Types];
extract_bit_type(_Value, _Types) ->
{ error, unknown }.
extract_bit_type_or_size(size, [{ Kind, _, _ } = Arg], default, Types) when Kind == var; Kind == integer ->
{ Arg, Types };
extract_bit_type_or_size(size, _Args, default, _Types) ->
{ error, "size in bitstring expects an integer or a variable as argument" };
extract_bit_type_or_size(size, _Args, _Other, _Types) ->
{ error, "duplicated size definition for bitstring" };
extract_bit_type_or_size(unit, [{ integer, _, Arg }], Size, Types) ->
{ Size, [{ unit, Arg }|Types] };
extract_bit_type_or_size(unit, _Args, _Other, _Types) ->
{ error, "unit in bitstring expects an integer as argument" };
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
H ->
elixir_errors:syntax_error(Line, 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)
end.
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
join_expansion(Expanded, Tail) -> [Expanded|Tail].
+109 -60
View File
@@ -1,30 +1,32 @@
%% Those macros behave like they belong to Kernel,
%% but do not since they need to be implemented in Erlang.
-module(elixir_macros).
-export([translate_macro/2]).
-import(elixir_translator, [translate_each/2, translate/2, translate_args/2, translate_apply/7]).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
-import(elixir_scope, [umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3, assert_no_assign_or_guard_scope/3]).
-include("elixir.hrl").
-define(FUNS(), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-define(FUNS(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-define(IN_TYPES(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
-compile({parse_transform, elixir_transform}).
%% Operators
translate_macro({ '+', _Line, [Expr] }, S) when is_number(Expr) ->
translate({ '+', _Line, [Expr] }, S) when is_number(Expr) ->
translate_each(Expr, S);
translate_macro({ '-', _Line, [Expr] }, S) when is_number(Expr) ->
translate({ '-', _Line, [Expr] }, S) when is_number(Expr) ->
translate_each(-1 * Expr, S);
translate_macro({ Op, Line, Exprs }, S) when is_list(Exprs),
translate({ Op, Line, 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);
translate_macro({ Op, Line, Exprs }, S) when is_list(Exprs),
translate({ Op, Line, 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
@@ -32,33 +34,39 @@ translate_macro({ Op, Line, Exprs }, S) when is_list(Exprs),
Op == '!==' ->
translate_each({ '__op__', Line, [Op|Exprs] }, S);
translate_macro({ '!', Line, [{ '!', _, [Expr] }] }, S) ->
translate({ '!', Line, [{ '!', _, [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, TExpr, true, S#elixir_scope.context == guard, SE);
translate_macro({ '!', Line, [Expr] }, S) ->
translate({ '!', Line, [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, TExpr, false, S#elixir_scope.context == guard, SE);
translate_macro({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
translate({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
{ _, TExpr, TS } = translate_in(Line, Left, Right, S),
{ TExpr, TS };
translate_macro({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
translate({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
{ TVar, TExpr, TS } = translate_in(Line, Left, Right, S),
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
%% Functions
translate_macro({ function, Line, [[{do,{ '->',_,Pairs}}]] }, S) ->
translate({ function, Line, [[{do,{ '->',_,Pairs}}]] }, S) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
elixir_translator:translate_fn(Line, Pairs, S);
translate_macro({ function, Line, [_] }, S) ->
translate({ function, Line, [{ '/', _, [{{ '.', _ ,[M, F] }, _ , [] }, A]}] }, S) ->
translate({ function, Line, [M, F, A] }, S);
translate({ function, Line, [{ '/', _, [{F, _, Q}, A]}] }, S) when is_atom(Q) ->
translate({ function, Line, [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_macro({ function, Line, [_, _] = Args }, S) ->
translate({ function, Line, [_, _] = Args }, S) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
case translate_args(Args, S) of
@@ -71,14 +79,14 @@ translate_macro({ function, Line, [_, _] = Args }, S) ->
syntax_error(Line, S#elixir_scope.file, "cannot dynamically retrieve local function. use function(module, fun, arity) instead")
end;
translate_macro({ function, Line, [_,_,_] = Args }, S) when is_list(Args) ->
translate({ function, Line, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_assign_or_guard_scope(Line, 'function', S),
{ [A,B,C], SA } = translate_args(Args, S),
{ { 'fun', Line, { function, A, B, C } }, SA };
%% @
translate_macro({'@', Line, [{ Name, _, Args }]}, S) when Name == typep; Name == type; Name == spec; Name == callback ->
translate({'@', Line, [{ Name, _, Args }]}, S) when is_list(Args) andalso (Name == typep orelse Name == type orelse Name == spec orelse Name == callback orelse Name == opaque) ->
case elixir_compiler:get_opt(internal) of
true -> { { nil, Line }, S };
false ->
@@ -86,7 +94,7 @@ translate_macro({'@', Line, [{ Name, _, Args }]}, S) when Name == typep; Name ==
translate_each(Call, S)
end;
translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
translate({'@', Line, [{ Name, _, Args }]}, S) ->
assert_module_scope(Line, '@', S),
case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
@@ -98,7 +106,7 @@ translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
case S#elixir_scope.function of
nil ->
translate_each({
{ '.', Line, ['Elixir.Module', add_attribute] },
{ '.', Line, ['Elixir.Module', put_attribute] },
Line,
[ { '__MODULE__', Line, false }, Name, Arg ]
}, S);
@@ -110,12 +118,12 @@ translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
case S#elixir_scope.function of
nil ->
translate_each({
{ '.', Line, ['Elixir.Module', read_attribute] },
{ '.', Line, ['Elixir.Module', get_attribute] },
Line,
[ { '__MODULE__', Line, false }, Name ]
}, S);
_ ->
Contents = 'Elixir.Module':read_attribute(S#elixir_scope.module, Name),
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
{ elixir_tree_helpers:abstract_syntax(Contents), S }
end;
_ ->
@@ -125,7 +133,7 @@ translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
%% Case
translate_macro({'case', Line, [Expr, KV]}, S) ->
translate({'case', Line, [Expr, KV]}, S) ->
assert_no_assign_or_guard_scope(Line, 'case', S),
Clauses = elixir_clauses:get_pairs(Line, do, KV, S),
{ TExpr, NS } = translate_each(Expr, S),
@@ -140,49 +148,49 @@ translate_macro({'case', Line, [Expr, KV]}, S) ->
%% Try
translate_macro({'try', Line, [Clauses]}, RawS) ->
translate({'try', Line, [Clauses]}, RawS) ->
S = RawS#elixir_scope{noname=true},
assert_no_assign_or_guard_scope(Line, 'try', S),
Do = proplists:get_value('do', Clauses, []),
{ TDo, SB } = translate([Do], S),
{ TDo, SB } = elixir_translator:translate([Do], S),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Line, Catch, umergec(S, SB)),
{ TAfter, SA } = case orddict:find('after', Clauses) of
{ ok, After } -> translate([After], umergec(S, SC));
error -> { [], SC }
{ TAfter, SA } = case lists:keyfind('after', 1, Clauses) of
{ 'after', After } -> elixir_translator:translate([After], umergec(S, SC));
false -> { [], SC }
end,
{ { 'try', Line, unpack(TDo), [], TCatch, unpack(TAfter) }, umergec(RawS, SA) };
%% Receive
translate_macro({'receive', Line, [KV] }, S) ->
translate({'receive', Line, [KV] }, S) ->
assert_no_assign_or_guard_scope(Line, 'receive', S),
Do = elixir_clauses:get_pairs(Line, do, KV, S, true),
case orddict:is_key('after', KV) of
true ->
case lists:keyfind('after', 1, KV) of
false ->
{ TClauses, SC } = elixir_clauses:match(Line, Do, S),
{ { 'receive', Line, TClauses }, SC };
_ ->
After = elixir_clauses:get_pairs(Line, 'after', KV, S),
{ TClauses, SC } = elixir_clauses:match(Line, Do ++ After, S),
{ FClauses, TAfter } = elixir_tree_helpers:split_last(TClauses),
{ _, _, [FExpr], _, FAfter } = TAfter,
{ { 'receive', Line, FClauses, FExpr, FAfter }, SC };
false ->
{ TClauses, SC } = elixir_clauses:match(Line, Do, S),
{ { 'receive', Line, TClauses }, SC }
{ { 'receive', Line, FClauses, FExpr, FAfter }, SC }
end;
%% Definitions
translate_macro({defmodule, Line, [Ref, KV]}, S) ->
translate({defmodule, Line, [Ref, KV]}, S) ->
{ TRef, _ } = translate_each(Ref, S),
Block = case orddict:find(do, KV) of
{ ok, DoValue } -> DoValue;
error -> syntax_error(Line, S#elixir_scope.file, "expected do: argument in defmodule")
Block = case lists:keyfind(do, 1, KV) of
{ do, DoValue } -> DoValue;
false -> syntax_error(Line, S#elixir_scope.file, "expected do: argument in defmodule")
end,
{ FRef, FS } = case TRef of
@@ -205,51 +213,60 @@ translate_macro({defmodule, Line, [Ref, KV]}, S) ->
{ TRef, S }
end,
{ elixir_module:translate(Line, FRef, Block, S), FS };
{ elixir_module:translate(Line, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
translate_macro({Kind, Line, [Call]}, S) when ?FUNS() ->
translate_macro({Kind, Line, [Call, skip_definition]}, S);
translate({Kind, Line, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Line, [Call, nil]}, S);
translate_macro({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Line, Kind, S),
assert_no_function_scope(Line, Kind, S),
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
{ Name, Args } = elixir_clauses:extract_args(TCall),
{ Name, Args } = case elixir_clauses:extract_args(TCall) of
error -> syntax_error(Line, 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),
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 };
translate_macro({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS() ->
translate({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Line, Kind, S),
assert_no_function_scope(Line, Kind, S),
{ TName, NS } = translate_each(Name, S),
{ TArgs, AS } = translate_each(Args, NS),
{ TGuards, TS } = translate_each(Guards, AS),
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, TS), TS };
{ TGuards, GS } = translate_each(Guards, AS),
{ TExpr, ES } = translate_each(Expr, GS),
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, ES), ES };
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
translate_macro({ apply, Line, [Left, Right, Args] }, S) when is_list(Args) ->
translate({ apply, Line, [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_macro({ apply, Line, Args }, S) ->
translate({ apply, Line, Args }, S) ->
{ TArgs, NS } = translate_args(Args, S),
{ ?ELIXIR_WRAP_CALL(Line, erlang, apply, TArgs), NS };
%% Handle forced variables
translate_macro({ 'var!', _, [{Name, Line, Atom}] }, S) when is_atom(Name), is_atom(Atom) ->
elixir_scope:translate_var(Line, Name, S);
translate({ 'var!', _, [{Name, Line, Atom}] }, S) when is_atom(Name), is_atom(Atom) ->
elixir_scope:translate_var(Line, Name, nil, S);
translate_macro({ 'var!', Line, [_] }, S) ->
translate({ 'var!', Line, [_] }, S) ->
syntax_error(Line, S#elixir_scope.file, "invalid args for var!").
%% HELPERS
%% Helpers
translate_in(Line, Left, Right, S) ->
{ TLeft, SL } = case Left of
@@ -271,14 +288,28 @@ translate_in(Line, Left, Right, S) ->
Expr = case TRight of
{ cons, _, _, _ } ->
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
lists:foldl(fun(X, Acc) ->
{ op, Line, 'orelse', Acc, { op, Line, '==', Var, X } }
lists:foldr(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
end, { op, Line, '==', Var, H }, T);
{ string, _, [H|T] } ->
lists:foldl(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
end, { op, Line, '==', Var, { integer, Line, H } }, T);
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
{ op, Line, 'andalso',
{ op, Line, '>=', Var, Start },
{ op, Line, '=<', Var, End }
};
case { Start, End } of
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
increasing_compare(Line, Var, Start, End);
{ { K1, _, _ }, { K2, _, _ } } when ?IN_TYPES(K1), ?IN_TYPES(K2) ->
decreasing_compare(Line, Var, Start, End);
_ ->
{ op, Line, 'orelse',
{ op, Line, 'andalso',
{ op, Line, '=<', Start, End},
increasing_compare(Line, Var, Start, End) },
{ op, Line, 'andalso',
{ op, Line, '<', End, Start},
decreasing_compare(Line, Var, Start, End) } }
end;
_ ->
syntax_error(Line, S#elixir_scope.file, "invalid args for operator in")
end,
@@ -288,6 +319,16 @@ translate_in(Line, Left, Right, S) ->
false -> { Var, Expr, SV }
end.
increasing_compare(Line, Var, Start, End) ->
{ op, Line, 'andalso',
{ op, Line, '>=', Var, Start },
{ op, Line, '=<', Var, End } }.
decreasing_compare(Line, Var, Start, End) ->
{ op, Line, 'andalso',
{ op, Line, '=<', Var, Start },
{ op, Line, '>=', Var, End } }.
rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}],True}]) ->
[{do,[false],False},{do,[true],True}];
@@ -309,9 +350,17 @@ is_reserved_data(_) -> false.
spec_to_macro(type) -> deftype;
spec_to_macro(typep) -> deftypep;
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.
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
Message = "function names should start with lowercase characters or underscore, invalid name ~s",
syntax_error(Line, File, Message, [atom_to_binary(Atom, utf8)]);
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
ok.
+122 -97
View File
@@ -1,9 +1,22 @@
-module(elixir_module).
-export([translate/4, compile/5, data_table/1,
format_error/1, scope_for_eval/2, binding_for_eval/2]).
-export([translate/4, compile/5, data_table/1, eval_quoted/4, format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
eval_quoted(Module, Quoted, RawBinding, Opts) ->
Binding = binding_for_eval(Module, RawBinding),
Scope = scope_for_eval(Module, Opts),
elixir_def:reset_last(Module),
case lists:keyfind(line, 1, Opts) of
{ line, Line } -> Line;
false -> Line = 1
end,
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope#elixir_scope{check_clauses=false}),
{ Value, FinalBinding }.
scope_for_eval(Module, #elixir_scope{} = S) ->
S#elixir_scope{module=Module};
@@ -15,40 +28,27 @@ binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
%% TABLE METHODS
data_table(Module) ->
?ELIXIR_ATOM_CONCAT([d, Module]).
Module.
docs_table(Module) ->
?ELIXIR_ATOM_CONCAT([o, Module]).
spec_table(Module) ->
?ELIXIR_ATOM_CONCAT([s, Module]).
%% TRANSFORMATION METHODS
%% TRANSFORMATION FUNCTIONS
%% Transformation of args and scope into a compiled erlang call.
%% The abstract form for extra arguments may be given and they
%% will be passed to the invoked function.
translate(Line, Ref, Block, S) ->
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
MetaS = elixir_scope:serialize(S),
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Vars = dict:fold(fun(Key, Value, { Acc, Counter }) ->
{ { cons, Line, { tuple, Line, [
{ atom, Line, Key },
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
{ var, Line, Value }
] }, Acc }, Counter + 1 }
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
Args = [{integer, Line, Line}, Ref, MetaBlock, element(1, Vars), MetaS],
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
?ELIXIR_WRAP_CALL(Line, ?MODULE, compile, Args).
%% The compilation hook.
compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
Dict = [{ X, Y } || { X, Y, _ } <- Vars],
S = elixir_scope:deserialize(RawS, Dict),
compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
C = elixir_compiler:get_opts(),
File = S#elixir_scope.file,
@@ -60,30 +60,33 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(Module),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
Forms1 = attributes_form(Line, File, Module, Forms0),
Forms2 = specs_form(Line, Module, Def, Defmacro, Defmacrop, Forms1),
Forms1 = specs_form(Line, Module, Defmacro, Defmacrop, Forms0, C),
Forms2 = attributes_form(Line, File, Module, Forms1),
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
Final = [
{attribute, Line, file, {File,Line}},
{attribute, Line, module, Module} | Forms2
{ attribute, Line, file, { binary_to_list(File), Line } },
{ attribute, Line, module, Module } | Forms2
],
load_form(Line, Final, S),
Result
Binary = load_form(Line, Final, S),
{ module, Module, Binary, Result }
after
ets:delete(data_table(Module)),
ets:delete(docs_table(Module)),
ets:delete(spec_table(Module)),
elixir_def:delete_table(Module),
elixir_import:delete_table(Module)
end;
compile(Line, Other, _Block, _Vars, RawS) ->
S = elixir_scope:deserialize(RawS),
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { invalid_module, Other }).
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other });
compile(Line, Module, Block, Vars, RawS) ->
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
S = elixir_scope:deserialize_with_vars(RawS, Dict),
compile(Line, Module, Block, Vars, S).
%% Hook that builds both attribute and functions and set up common hooks.
@@ -98,11 +101,16 @@ build(Line, File, Module) ->
ets:new(DataTable, [set, named_table, public]),
ets:insert(DataTable, { '__overridable', [] }),
ets:insert(DataTable, { 'before_compile', [] }),
ets:insert(DataTable, { 'after_compile', [] }),
ets:insert(DataTable, { before_compile, [] }),
ets:insert(DataTable, { after_compile, [] }),
case elixir_compiler:get_opt(docs) of
true -> ets:insert(DataTable, { on_definition, [{ 'Elixir.Module', compile_doc }] });
_ -> ets:insert(DataTable, { on_definition, [] })
end,
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
ets:insert(DataTable, { '__acc_attributes', [before_compile,after_compile|Attributes] }),
ets:insert(DataTable, { '__acc_attributes', [before_compile,after_compile,on_definition|Attributes] }),
ets:insert(DataTable, { '__persisted_attributes', [vsn|Attributes] }),
%% Keep docs in another table since we don't want to pull out
@@ -110,10 +118,6 @@ build(Line, File, Module) ->
DocsTable = docs_table(Module),
ets:new(DocsTable, [ordered_set, named_table, public]),
%% Holds specs in a format it is easier to query.
SpecTable = spec_table(Module),
ets:new(SpecTable, [duplicate_bag, named_table, public]),
%% We keep a separated table for function definitions
%% and another one for imports. We keep them in different
%% tables for organization and speed purpose (since the
@@ -124,15 +128,20 @@ build(Line, File, Module) ->
%% Receives the module representation and evaluates it.
eval_form(Line, Module, Block, Vars, RawS) ->
Temp = ?ELIXIR_ATOM_CONCAT(["COMPILE-",Module]),
S = scope_for_eval(Module, RawS),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Temp, Vars, S),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
elixir_def_overridable:store_pending(Module),
eval_callbacks(Line, Module, before_compile, [Module], NewS),
elixir_def_overridable:store_pending(Module),
eval_callbacks(Line, Module, 'before_compile', [Module], NewS),
Value.
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C) ->
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, RawFunctions, C) ->
Functions = case elixir_compiler:get_opt(internal, C) of
true -> RawFunctions;
false -> record_rewrite_functions(RawFunctions)
end,
{ FinalExport, FinalFunctions } =
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
@@ -143,6 +152,17 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Fu
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
] }.
record_rewrite_functions(Functions) ->
lists:map(fun
({ function, Line, Name, Arity, Clauses }) ->
Rewriten = [begin
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Clause),
C
end || Clause <- Clauses],
{ function, Line, Name, Arity, Rewriten };
(Other) -> Other
end, Functions).
%% Add attributes handling to the form
attributes_form(Line, _File, Module, Current) ->
@@ -167,39 +187,43 @@ attributes_form(Line, _File, Module, Current) ->
%% Specs
specs_form(Line, Module, Def, Defmacro, Defmacrop, Forms) ->
{ Specs, Callbacks } = ets:foldl(fun(X, Acc) ->
translate_spec(X, Module, Def, Defmacro, Defmacrop, Acc)
end, { [], [] }, spec_table(Module)),
specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
Defmacrop = [Tuple || { Tuple, _, _ } <- DefmacropWithLine],
case elixir_compiler:get_opt(internal, C) of
true -> Forms;
_ ->
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
Temp = specs_attributes(Line, spec, Forms, Specs),
specs_attributes(Line, callback, Temp, Callbacks).
'Elixir.Module':delete_attribute(Module, spec),
'Elixir.Module':delete_attribute(Module, callback),
Temp = specs_attributes(Line, spec, Forms, Specs),
specs_attributes(Line, callback, Temp, Callbacks)
end.
specs_attributes(Line, Type, Forms, Specs) ->
Keys = lists:ukeysort(1, Specs),
Keys = lists:foldl(fun({ Tuple, Value }, Acc) ->
lists:keystore(Tuple, 1, Acc, { Tuple, Value } )
end, [], Specs),
lists:foldl(fun({ Tuple, _ }, Acc) ->
[{ attribute, Line, Type, { Tuple, proplists:append_values(Tuple, Specs) } }|Acc]
Values = [V || { K, V } <- Specs, K == Tuple],
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
end, Forms, Keys).
translate_spec({ { spec, Spec }, Rest }, Module, Def, Defmacro, Defmacrop, { Specs, Callbacks } = Acc) ->
translate_spec({ Spec, Rest }, Defmacro, Defmacrop) ->
case ordsets:is_element(Spec, Defmacrop) of
true -> Acc;
true -> { Spec, Rest };
false ->
case ordsets:is_element(Spec, Defmacro) of
true ->
{ Name, Arity } = Spec,
New = { { ?ELIXIR_MACRO(Name), Arity + 1 }, [spec_for_macro(X) || X <- Rest] },
{ [New|Specs], Callbacks };
{ { ?ELIXIR_MACRO(Name), Arity + 1 }, spec_for_macro(Rest) };
false ->
case (Module == 'Elixir.Kernel') andalso (not ordsets:is_element(Spec, Def)) of
true -> Acc;
false -> { [{ Spec, Rest }|Specs], Callbacks }
end
{ Spec, Rest }
end
end;
translate_spec({ { callback, Spec }, Rest }, _, _, _, _, { Specs, Callbacks }) ->
{ Specs, [{ Spec, Rest }|Callbacks] }.
end.
spec_for_macro({ type, Line, 'fun', [{ type, _, product, Args }|T] }) ->
NewArgs = [{type,Line,term,[]}|Args],
@@ -212,7 +236,7 @@ spec_for_macro(Else) -> Else.
load_form(Line, Forms, S) ->
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
EvalS = scope_for_eval(Module, S),
eval_callbacks(Line, Module, 'after_compile', [Module, Binary], EvalS),
eval_callbacks(Line, Module, after_compile, [Module, Binary], EvalS),
case get(elixir_compiled) of
Current when is_list(Current) ->
@@ -221,9 +245,10 @@ load_form(Line, Forms, S) ->
undefined -> [];
PID -> PID ! { module_available, self(), Module, Binary }
end;
_ ->
[]
end
_ -> []
end,
Binary
end).
check_module_availability(Line, File, Module, Compiler) ->
@@ -247,57 +272,57 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
true -> elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
false ->
Docs = elixir_compiler:get_opt(docs, C),
Contents = { function, Line, '__info__', 1, [
functions_clause(Line, Def),
macros_clause(Line, Defmacro),
docs_clause(Line, Module, Docs),
Contents = { function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Module, Def, Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
compile_clause(Line),
self_clause(Line, Module),
else_clause(Line)
module_clause(Module),
else_clause()
] },
{ [Pair|Export], [Contents|Functions] }
end.
functions_clause(Line, RawDef) ->
Def = ordsets:add_element({'__info__',1}, RawDef),
{ clause, Line, [{ atom, Line, functions }], [], [elixir_tree_helpers:abstract_syntax(Def)] }.
functions_clause(Def) ->
All = ordsets:add_element({'__info__',1}, Def),
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
self_clause(Line, Module) ->
{ clause, Line, [{ atom, Line, self }], [], [{ atom, Line, Module }] }.
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
macros_clause(Line, Defmacro) ->
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(Defmacro)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
elixir_dispatch:in_erlang_macros()), Def);
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
docs_clause(Line, Module, true) ->
module_clause(Module) ->
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
docs_clause(Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
docs_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, docs }], [], [{ atom, Line, nil }] }.
docs_clause(_Module, _) ->
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':read_attribute(Module, moduledoc),
{ clause, Line, [{ atom, Line, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
moduledoc_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, moduledoc }], [], [{ atom, Line, nil }] }.
moduledoc_clause(_Line, _Module, _) ->
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
compile_clause(Line) ->
Info = { call, Line, { atom, Line, module_info }, [{ atom, Line, compile }] },
WrappedInfo = ?ELIXIR_WRAP_CALL(Line, 'Elixir.Keyword', 'from_enum', [Info]),
{ clause, Line, [{ atom, Line, compile }], [], [WrappedInfo] }.
else_clause(Line) ->
Info = { call, Line, { atom, Line, module_info }, [{ var, Line, atom }] },
{ clause, Line, [{ var, Line, atom }], [], [Info] }.
else_clause() ->
Info = { call, 0, { atom, 0, module_info }, [{ var, 0, atom }] },
{ clause, 0, [{ var, 0, atom }], [], [Info] }.
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
S = RawS#elixir_scope{check_clauses=false},
Binding = binding_for_eval(Module, []),
Callbacks = ets:lookup_element(data_table(Module), Name, 2),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Requires = S#elixir_scope.requires,
lists:foreach(fun({M,F}) ->
+96 -79
View File
@@ -3,31 +3,30 @@
Nonterminals
grammar expr_list
expr block_expr fn_expr bracket_expr call_expr max_expr
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
comma_separator
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
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op
open_paren close_paren
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
open_curly close_curly
open_bit close_bit
comma_expr matched_comma_expr call_args_comma_expr
call_args call_args_parens call_args_no_parens
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
kw_eol kw_expr kw_comma kw_base
matched_kw_expr matched_kw_comma matched_kw_base
parens_call dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
var list bracket_access bit_string tuple
fn_block do_block do_eol end_eol block_eol block_item block_list
do_block fn_eol do_eol end_eol block_eol block_item block_list
.
Terminals
identifier kw_identifier punctuated_identifier
bracket_identifier paren_identifier do_identifier block_identifier
fn fn_paren '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'
@@ -44,8 +43,8 @@ Rootsymbol grammar.
Left 5 do.
Right 10 '->'.
Left 20 ','. % Solve nested call_args conflicts
Right 30 when_op.
Right 40 colon_colon_op.
Right 30 colon_colon_op.
Right 40 when_op.
Right 50 default_op.
Left 60 pipe_op.
Left 70 inc_op.
@@ -67,7 +66,8 @@ Right 290 pipeline_op.
Nonassoc 300 unary_op.
Left 310 dot_call_op.
Left 310 dot_op.
Nonassoc 320 var.
Nonassoc 320 at_op.
Nonassoc 330 var.
%%% MAIN FLOW OF EXPRESSIONS
@@ -80,18 +80,25 @@ grammar -> '$empty' : [nil].
% Note expressions are on reverse order
expr_list -> expr : ['$1'].
expr_list -> empty_paren : [nil].
expr_list -> expr_list eol expr : ['$3'|'$1'].
paren_expr -> empty_paren : nil.
paren_expr -> expr : '$1'.
expr -> matched_expr : '$1'.
expr -> unmatched_expr : '$1'.
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> unary_op matched_expr : build_unary_op('$1', '$2').
matched_expr -> at_op matched_expr : build_unary_op('$1', '$2').
matched_expr -> bracket_at_expr : '$1'.
matched_expr -> fn_expr : '$1'.
unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
unmatched_expr -> unary_op expr : build_unary_op('$1', '$2').
unmatched_expr -> at_op expr : build_unary_op('$1', '$2').
unmatched_expr -> block_expr : '$1'.
op_expr -> match_op expr : { '$1', '$2' }.
@@ -137,13 +144,13 @@ 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 -> 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_paren call_args_parens fn_block : build_fn('$1', '$2', '$3').
fn_expr -> fn call_args_no_parens fn_block : build_fn('$1', '$2', '$3').
fn_expr -> fn fn_block : build_fn('$1', [], '$2').
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 -> call_expr : '$1'.
call_expr -> dot_punctuated_identifier call_args_no_parens : build_identifier('$1', '$2').
@@ -152,17 +159,21 @@ call_expr -> dot_identifier call_args_no_parens : build_identifier('$1', '$2').
call_expr -> dot_punctuated_identifier : build_identifier('$1', []).
call_expr -> dot_do_identifier : build_identifier('$1', nil).
call_expr -> var : build_identifier('$1', nil).
call_expr -> bracket_expr : '$1'.
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 -> dot_ref : '$1'.
max_expr -> base_expr : '$1'.
max_expr -> open_paren stab_expr_list close_paren : build_stab(lists:reverse('$2')).
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
bracket_expr -> max_expr : '$1'.
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
max_expr -> dot_ref : '$1'.
max_expr -> base_expr : '$1'.
max_expr -> open_paren ')' : build_block([]).
max_expr -> open_paren expr_list close_paren : build_block('$2').
bracket_at_expr -> at_op dot_bracket_identifier bracket_access : build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
bracket_at_expr -> at_op max_expr bracket_access : build_access(build_unary_op('$1', '$2'), '$3').
bracket_at_expr -> bracket_at_expr bracket_access : build_access('$1', '$2').
base_expr -> number : ?exprs('$1').
base_expr -> signed_number : { element(4, '$1'), ?line('$1'), ?exprs('$1') }.
@@ -182,12 +193,13 @@ base_expr -> sigil : build_sigil('$1').
%% Blocks
fn_block -> '->' grammar 'end' : build_block('$2', false).
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 block_list 'end' : [sort_kw([{ do, nil }|'$2'])].
do_block -> do_eol stab_expr_list eol block_list 'end' : [sort_kw([{ do, build_stab(lists:reverse('$2')) }|'$4'])].
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']].
fn_eol -> 'fn' : '$1'.
fn_eol -> 'fn' eol : '$1'.
do_eol -> 'do' : '$1'.
do_eol -> 'do' eol : '$1'.
@@ -202,7 +214,8 @@ stab_expr_list -> stab_expr : ['$1'].
stab_expr_list -> stab_expr_list eol stab_expr : ['$3'|'$1'].
stab_expr -> expr : '$1'.
stab_expr -> comma_expr stab_op expr : build_op('$2', lists:reverse('$1'), '$3').
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').
block_item -> block_eol stab_expr_list eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
block_item -> block_eol : { ?exprs('$1'), nil }.
@@ -214,14 +227,13 @@ block_list -> block_item block_list : ['$1'|'$2'].
var -> dot_identifier : '$1'.
comma_separator -> ',' : '$1'.
comma_separator -> ',' eol : '$1'.
open_paren -> '(' : '$1'.
open_paren -> '(' eol : '$1'.
close_paren -> ')' : '$1'.
close_paren -> eol ')' : '$2'.
empty_paren -> open_paren ')' : nil.
open_bracket -> '[' : '$1'.
open_bracket -> '[' eol : '$1'.
close_bracket -> ']' : '$1'.
@@ -288,11 +300,12 @@ unary_op -> '^' : '$1'.
unary_op -> '^' eol : '$1'.
unary_op -> 'not' : '$1'.
unary_op -> 'not' eol : '$1'.
unary_op -> '@' : '$1'.
unary_op -> '@' eol : '$1'.
unary_op -> '~~~' : '$1'.
unary_op -> '~~~' eol : '$1'.
at_op -> '@' : '$1'.
at_op -> '@' eol : '$1'.
match_op -> '=' : '$1'.
match_op -> '=' eol : '$1'.
@@ -370,55 +383,68 @@ parens_call -> matched_expr dot_call_op : { '.', ?line('$2'), ['$1'] }. % Fun/lo
% Function calls
matched_comma_expr -> matched_expr : ['$1'].
matched_comma_expr -> matched_comma_expr comma_separator matched_expr : ['$3'|'$1'].
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
call_args_no_parens -> matched_kw_base : ['$1'].
call_args_no_parens -> matched_comma_expr comma_separator matched_kw_base : lists:reverse(['$3'|'$1']).
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
comma_expr -> expr : ['$1'].
comma_expr -> comma_expr comma_separator expr : ['$3'|'$1'].
call_args_parens_not_one -> open_paren ')' : [].
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
call_args_comma_expr -> comma_expr : lists:reverse('$1').
call_args_comma_expr -> kw_base : ['$1'].
call_args_comma_expr -> comma_expr comma_separator kw_base : lists:reverse(['$3'|'$1']).
base_comma_expr -> paren_expr ',' : ['$1'].
base_comma_expr -> base_comma_expr paren_expr ',' : ['$2'|'$1'].
comma_expr -> paren_expr : ['$1'].
comma_expr -> kw_base : ['$1'].
comma_expr -> base_comma_expr : '$1'.
comma_expr -> base_comma_expr paren_expr : ['$2'|'$1'].
comma_expr -> base_comma_expr kw_base : ['$2'|'$1'].
optional_comma_expr -> paren_expr : '$1'.
optional_comma_expr -> paren_expr ',' : '$1'.
call_args -> comma_expr : lists:reverse('$1').
call_args_parens -> open_paren ')' : [].
call_args_parens -> open_paren call_args_comma_expr close_paren : '$2'.
call_args -> call_args_comma_expr : '$1'.
call_args_parens -> open_paren call_args close_paren : '$2'.
% KV
kw_eol -> kw_identifier : '$1'.
kw_eol -> kw_identifier eol : '$1'.
kw_eol -> kw_identifier : '$1'.
kw_eol -> kw_identifier eol : '$1'.
kw_expr -> kw_eol paren_expr : { ?exprs('$1'),'$2' }.
kw_expr -> kw_eol expr : {?exprs('$1'),'$2'}.
kw_comma -> kw_expr : ['$1'].
kw_comma -> kw_expr comma_separator kw_comma : ['$1'|'$3'].
kw_base -> kw_comma : sort_kw('$1').
kw_comma -> kw_expr ',' : ['$1'].
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
kw_base -> kw_expr : ['$1'].
kw_base -> kw_comma : lists:reverse('$1').
kw_base -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
matched_kw_expr -> kw_eol matched_expr : {?exprs('$1'),'$2'}.
matched_kw_expr -> kw_eol empty_paren : {?exprs('$1'),nil}.
matched_kw_comma -> matched_kw_expr : ['$1'].
matched_kw_comma -> matched_kw_expr comma_separator matched_kw_comma : ['$1'|'$3'].
matched_kw_base -> matched_kw_comma : sort_kw('$1').
matched_kw_comma -> matched_kw_expr ',' matched_kw_comma : ['$1'|'$3'].
matched_kw_base -> matched_kw_comma : '$1'.
% Lists
bracket_access -> open_bracket ']' : { [], ?line('$1') }.
bracket_access -> open_bracket expr close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket optional_comma_expr close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket kw_base close_bracket : { '$2', ?line('$1') }.
list -> open_bracket ']' : [].
list -> open_bracket kw_base close_bracket : '$2'.
list -> open_bracket expr close_bracket : ['$2'].
list -> open_bracket expr comma_separator call_args close_bracket : ['$2'|'$4'].
list -> open_bracket optional_comma_expr close_bracket : ['$2'].
list -> open_bracket paren_expr ',' call_args close_bracket : ['$2'|'$4'].
% Tuple
tuple -> open_curly '}' : build_tuple('$1', []).
tuple -> open_curly expr close_curly : build_tuple('$1', ['$2']).
tuple -> open_curly expr comma_separator call_args close_curly : build_tuple('$1', ['$2'|'$4']).
tuple -> open_curly optional_comma_expr close_curly : build_tuple('$1', ['$2']).
tuple -> open_curly paren_expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
% Bitstrings
@@ -460,14 +486,10 @@ build_tuple(Marker, Args) ->
%% Blocks
build_block(Exprs) -> build_block(Exprs, true).
build_block([], _) -> nil;
build_block([nil], _) -> { '__block__', 0, [nil] };
build_block([{Op,_,[_]}]=Exprs, _) when ?rearrange_uop(Op) -> { '__block__', 0, Exprs };
build_block([Expr], _) when not is_list(Expr) -> Expr;
build_block(Exprs, true) -> { '__block__', 0, lists:reverse(Exprs) };
build_block(Exprs, false) -> { '__block__', 0, Exprs }.
build_block([nil]) -> { '__block__', 0, [nil] };
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', 0, Exprs };
build_block([Expr]) when not is_list(Expr) -> Expr;
build_block(Exprs) -> { '__block__', 0, Exprs }.
%% Dots
@@ -489,7 +511,7 @@ build_identifier({ '.', Line, _ } = Dot, Args) ->
end,
{ Dot, Line, FArgs };
build_identifier({ Keyword, Line }, Args) when Keyword == fn; Keyword == fn_paren ->
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
{ fn, Line, Args };
build_identifier({ op_identifier, Line, Identifier }, Args) ->
@@ -507,15 +529,14 @@ extract_identifier(Other) -> Other.
%% Fn
build_fn(Op, Args, Expr) ->
Line = ?line(Op),
Stab = [{do, { '->', Line, [{ Args, Expr }] }}],
{ fn, Line, [Stab] }.
build_fn(Op, Stab) ->
{ fn, ?line(Op), [[{ do, Stab }]] }.
%% Access
build_access(Expr, Access) ->
{ access, ?line(Access), [ Expr, ?op(Access) ] }.
Line = ?line(Access),
{ { '.', Line, ['Elixir-Kernel', access] }, ?line(Access), [ Expr, ?op(Access) ] }.
%% Interpolation aware
@@ -526,11 +547,10 @@ build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', Line, Args }.
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> binary_to_list(H);
build_list_string({ list_string, Line, Args }) -> { binary_to_list, Line, [{ '<<>>', Line, Args}] }.
build_list_string({ list_string, Line, Args }) -> { { '.', Line, [erlang, binary_to_list] }, Line, [{ '<<>>', Line, Args}] }.
build_atom({ atom, _Line, [H] }) when is_atom(H) -> H;
build_atom({ atom, _Line, [H] }) when is_binary(H) -> binary_to_atom(H, utf8);
build_atom({ atom, Line, Args }) -> { binary_to_atom, Line, [{ '<<>>', Line, Args}, utf8] }.
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
build_atom({ atom, Line, Args }) -> { { '.', Line, [erlang, binary_to_atom] }, Line, [{ '<<>>', Line, Args }, utf8] }.
%% Keywords
@@ -538,18 +558,15 @@ build_stab([{ '->', Line, [Left, Right] }|T]) ->
{ '->', Line, build_stab(T, Left, [Right], []) };
build_stab(Else) ->
build_block(Else, false).
build_block(Else).
build_stab([{ '->', _, [Left, Right] }|T], Marker, Temp, Acc) ->
H = { Marker, build_block(Temp) },
H = { Marker, build_block(lists:reverse(Temp)) },
build_stab(T, Left, [Right], [H|Acc]);
build_stab([H|T], Marker, Temp, Acc) ->
build_stab(T, Marker, [H|Temp], Acc);
build_stab([], Marker, Temp, Acc) ->
H = { Marker, build_block(Temp) },
H = { Marker, build_block(lists:reverse(Temp)) },
lists:reverse([H|Acc]).
sort_kw(List) -> lists:sort(fun sort_kw/2, List).
sort_kw({ A, _ }, { B, _ }) -> A =< B.
+65
View File
@@ -0,0 +1,65 @@
-module(elixir_partials).
-export([handle/2, handle/3, is_sequential/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
handle(Original, S) ->
handle(Original, S, default).
handle({ _, Line, 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),
Block = setelement(3, Original, Call),
elixir_translator:translate_fn(Line, [{ Final, Block }], SC);
_ -> error
end;
handle(_Original, _S, _Opt) ->
error.
validate(Line, Def, S) ->
validate(Line, lists:sort(Def), 1, S).
validate(Line, [{ Pos, Item }|T], Pos, S) ->
[Item|validate(Line, 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(_Line, [], _Pos, _S) ->
[].
%% This function receives arguments and then checks
%% the args for partial application. It returns a tuple
%% with three elements where the first element is the list
%% of call args, the second the list of def args for the
%% 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) ->
{ NewArgs, NewDef, NewS } = convert(Args, S, allow_tail, [], DefAcc),
convert(T, NewS, allow_tail, [{ '|', Line, NewArgs}|CallAcc], NewDef);
convert([{'&', Line, [Pos]}|T], S, Opt, CallAcc, DefAcc) ->
case lists:keyfind(Pos, 1, DefAcc) of
false ->
{ Var, SC } = elixir_scope:build_ex_var(Line, S),
convert(T, SC, Opt, [Var|CallAcc], [{Pos,Var}|DefAcc]);
{Pos,Var} ->
convert(T, S, Opt, [Var|CallAcc], DefAcc)
end;
convert([H|T], S, Opt, CallAcc, DefAcc) ->
convert(T, S, Opt, [H|CallAcc], DefAcc);
convert([], S, _Opt, CallAcc, DefAcc) ->
{ lists:reverse(CallAcc), lists:reverse(DefAcc), S }.
is_sequential(List) -> is_sequential(List, 1).
is_sequential([{ '&', _, [Int] }|T], Int) ->
is_sequential(T, Int + 1);
is_sequential([], Int) when Int > 1 -> true;
is_sequential(_, _Int) -> false.
+74 -28
View File
@@ -1,5 +1,5 @@
-module(elixir_quote).
-export([quote/3, linify/2]).
-export([quote/3, linify/2, join_quoted/5]).
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
@@ -15,53 +15,88 @@ linify(Line, List) when is_list(List) ->
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(Line, Left, Right, nil, _File) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir-" ++ _ ->
{ '__aliases__', Line, [Left, Right] };
_ ->
{ { '.', Line, [Left, Right] }, Line, [] }
end;
join_quoted(Line, Left, Right, Args, _File) when is_atom(Right) ->
{ { '.', Line, [Left, Right] }, Line, 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").
%% Translation
quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
quote({ 'unquote_splicing', Line, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
do_quote({ '__block__', Line, [Expr] }, Q, S);
quote(Else, Q, S) ->
do_quote(Else, Q, S).
do_quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
elixir_translator:translate_each(Expr, S);
quote({ Left, Line, nil }, Q, S) when is_atom(Left) ->
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({ { '.', 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({ Left, Line, nil }, Q, S) when is_atom(Left) ->
Tuple = { tuple, Line, [
{ atom, Line, Left },
{ integer, Line, line(Line, Q) },
line(Line, Q),
{ atom, Line, Q#elixir_quote.marker }
] },
{ Tuple, S };
quote({ Left, Line, Right }, Q, S) ->
{ TLeft, LS } = quote(Left, Q, S),
{ TRight, RS } = quote(Right, Q, LS),
do_quote({ Left, Line, 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, [TLeft, { integer, Line, line(Line, Q) }, TRight] },
Tuple = { tuple, Line, [TLeft, line(Line, Q), TRight] },
{ Tuple, RS };
% Handle two item tuples but still allow them to be spliced.
quote({ Left, Right }, Q, S) when
do_quote({ Left, Right }, Q, S) when
not is_tuple(Left) orelse (element(1, Left) /= unquote_splicing),
not is_tuple(Right) orelse (element(1, Right) /= unquote_splicing) ->
{ TLeft, LS } = quote(Left, Q, S),
{ TRight, RS } = quote(Right, Q, LS),
{ { tuple, line(0, Q), [TLeft, TRight] }, RS };
{ TLeft, LS } = do_quote(Left, Q, S),
{ TRight, RS } = do_quote(Right, Q, LS),
{ { tuple, int_line(Q), [TLeft, TRight] }, RS };
quote({ Left, Right }, Q, S) ->
quote({ '{}', 0, [Left, Right] }, Q, S);
do_quote({ Left, Right }, Q, S) ->
do_quote({ '{}', int_line(Q), [Left, Right] }, Q, S);
quote(List, Q, S) when is_list(List) ->
do_quote(List, Q, S) when is_list(List) ->
splice(List, Q, [], [], S);
quote(Number, Q, S) when is_integer(Number) ->
{ { integer, line(0, Q), Number }, S };
do_quote(Number, Q, S) when is_integer(Number) ->
{ { integer, int_line(Q), Number }, S };
quote(Number, Q, S) when is_float(Number) ->
{ { float, line(0, Q), Number }, S };
do_quote(Number, Q, S) when is_float(Number) ->
{ { float, int_line(Q), Number }, S };
quote(Atom, Q, S) when is_atom(Atom) ->
{ { atom, line(0, Q), Atom }, S };
do_quote(Atom, Q, S) when is_atom(Atom) ->
{ { atom, int_line(Q), Atom }, S };
quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
do_quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
% Loop through the list finding each unquote_splicing entry.
@@ -71,6 +106,11 @@ 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) ->
{ TLeft, SL } = splice([Left], Q, Buffer, Acc, S),
{ TRight, SR } = do_quote(Right, Q, SL),
{ { op, Line, '++', TLeft, TRight }, SR };
splice([H|T], Q, Buffer, Acc, S) ->
splice(T, Q, [H|Buffer], Acc, S);
@@ -78,12 +118,12 @@ splice([], Q, Buffer, Acc, S) ->
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
case NewAcc of
[] ->
{ { nil, line(0, Q) }, NewS };
{ { nil, int_line(Q) }, NewS };
[List] ->
{ List, NewS };
_ ->
List = elixir_tree_helpers:build_simple_reverse_list(0, NewAcc),
{ ?ELIXIR_WRAP_CALL(0, lists, append, [List]), NewS }
List = elixir_tree_helpers:build_simple_reverse_list(int_line(Q), NewAcc),
{ ?ELIXIR_WRAP_CALL(int_line(Q), lists, append, [List]), NewS }
end.
from_buffer_to_acc([], _Q, Acc, S) ->
@@ -91,8 +131,14 @@ 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) -> quote(X, Q, AccS) end, Buffer, 0, S),
fun(X, AccS) -> do_quote(X, Q, AccS) end, Buffer, int_line(Q), S),
{ [New|Acc], NewS }.
line(Line, #elixir_quote{line=keep}) -> Line;
line(_Line, #elixir_quote{line=Line}) -> Line.
line(Line, #elixir_quote{line=keep}) -> { integer, Line, Line };
line(_Line, #elixir_quote{line=Line}) -> Line.
int_line(Q) ->
case line(0, Q) of
{ integer, _, Line } -> Line;
_ -> 0
end.
+89 -49
View File
@@ -1,52 +1,77 @@
%% Convenience functions used to manipulate scope
%% and its variables.
-module(elixir_scope).
-export([translate_var/3,
-export([translate_var/4,
build_erl_var/2, build_ex_var/2,
serialize/1, deserialize/1, deserialize/2,
build_erl_var/3, build_ex_var/3,
build_erl_var/4, build_ex_var/4,
serialize/1, deserialize/1,
serialize_with_vars/2, deserialize_with_vars/2,
to_erl_env/1, to_ex_env/1, filename/1,
umergev/2, umergec/2
umergev/2, umergec/2, merge_clause_vars/2
]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
translate_var(Line, Name, S) ->
Match = S#elixir_scope.context == assign,
translate_var(Line, Name, Kind, S) ->
Vars = S#elixir_scope.vars,
TempVars = S#elixir_scope.temp_vars,
ClauseVars = S#elixir_scope.clause_vars,
case Name of
'_' -> { {var, Line, Name}, S };
'_' ->
{ { var, Line, Name }, S };
_ ->
case { Match, dict:is_key(Name, Vars), dict:is_key(Name, TempVars) } of
{ true, true, true } -> { {var, Line, dict:fetch(Name, Vars) }, S };
{ true, Else, _ } ->
{ NewVar, NS } = case Else or S#elixir_scope.noname of
true -> build_erl_var(Line, S);
false -> { {var, Line, Name}, S }
end,
RealName = element(3, NewVar),
{ NewVar, NS#elixir_scope{
vars=dict:store(Name, RealName, Vars),
temp_vars=dict:store(Name, RealName, TempVars),
clause_vars=dict:store(Name, RealName, ClauseVars)
} };
{ false, false, _ } -> elixir_translator:translate_each({Name, Line, []}, S);
{ false, true, _ } -> { {var, Line, dict:fetch(Name, Vars) }, S }
case S#elixir_scope.context of
assign ->
TempVars = S#elixir_scope.temp_vars,
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
{ true, { ok, Kind } } ->
{ { var, Line, orddict:fetch({ Name, Kind }, Vars) }, S };
{ Else, _ } ->
{ NewVar, NS } = if
Kind == quoted -> 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 }
end,
RealName = element(3, NewVar),
ClauseVars = S#elixir_scope.clause_vars,
{ NewVar, NS#elixir_scope{
vars=orddict:store({ Name, Kind }, RealName, Vars),
temp_vars=orddict:store(Name, Kind, TempVars),
clause_vars=if
ClauseVars == nil -> nil;
true -> orddict:store({ Name, Kind }, RealName, ClauseVars)
end
} }
end;
_ ->
case orddict:find({ Name, Kind }, Vars) of
{ ok, VarName } -> { { var, Line, VarName }, S };
error -> elixir_translator:translate_each({ Name, Line, [] }, S)
end
end
end.
% Handle variables translation
build_erl_var(Line, #elixir_scope{counter=Counter} = S) ->
NS = S#elixir_scope{counter=Counter+1},
Var = { var, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
build_ex_var(Line, Key, S) -> build_ex_var(Line, Key, Key, S).
build_erl_var(Line, Key, S) -> build_erl_var(Line, Key, Key, S).
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
New = orddict:update_counter(Key, 1, Counter),
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
build_erl_var(Line, Key, Name, S) ->
{ Counter, NS } = build_var_counter(Key, S),
Var = { var, Line, ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]) },
{ Var, NS }.
build_ex_var(Line, #elixir_scope{counter=Counter} = S) ->
NS = S#elixir_scope{counter=Counter+1},
Var = { ?ELIXIR_ATOM_CONCAT(["_@", Counter]), Line, nil },
build_ex_var(Line, Key, Name, S) ->
{ Counter, NS } = build_var_counter(Key, S),
Var = { ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]), Line, quoted },
{ Var, NS }.
% Handle Macro.Env conversion
@@ -59,8 +84,8 @@ to_ex_env({ Line, Tuple }) when element(1, Tuple) == 'Elixir.Macro.Env' ->
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros} }) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Macros }.
requires=Requires,macros=Macros,functions=Functions} }) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
filename(#elixir_scope{file=File}) -> File;
filename(Other) -> element(3, Other).
@@ -69,26 +94,36 @@ filename(Other) -> element(3, Other).
serialize(S) ->
elixir_tree_helpers:abstract_syntax(
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses, S#elixir_scope.macro,
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases, S#elixir_scope.scheduled }
).
serialize_with_vars(Line, S) ->
{ 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]) },
{ var, Line, Value }
] }, Acc }, Counter + 1 }
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
{ serialize(S), Vars }.
% Fill in the scope with the variables serialization set in serialize_scope.
deserialize(Tuple) -> deserialize(Tuple, []).
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
deserialize({ File, Functions, CheckClauses, Macro, Requires, Macros, Aliases, Scheduled }, Vars) ->
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases, Scheduled }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
check_clauses=CheckClauses,
macro=Macro,
requires=Requires,
macros=Macros,
aliases=Aliases,
scheduled=Scheduled,
vars=dict:from_list(Vars),
counter=length(Vars)
vars=orddict:from_list(Vars),
counter=[{'',length(Vars)}]
}.
% Receives two scopes and return a new scope based on the second
@@ -97,14 +132,11 @@ deserialize({ File, Functions, CheckClauses, Macro, Requires, Macros, Aliases, S
umergev(S1, S2) ->
V1 = S1#elixir_scope.vars,
V2 = S2#elixir_scope.vars,
Q1 = S1#elixir_scope.quote_vars,
Q2 = S2#elixir_scope.quote_vars,
C1 = S1#elixir_scope.clause_vars,
C2 = S2#elixir_scope.clause_vars,
S2#elixir_scope{
vars=dict:merge(fun var_merger/3, V1, V2),
quote_vars=dict:merge(fun var_merger/3, Q1, Q2),
clause_vars=dict:merge(fun var_merger/3, C1, C2)
vars=orddict:merge(fun var_merger/3, V1, V2),
clause_vars=merge_clause_vars(C1, C2)
}.
% Receives two scopes and return a new scope based on the first
@@ -121,13 +153,21 @@ umergec(S1, S2) ->
% Merge variables trying to find the most recently created.
var_merger(Var, Var, K2) -> K2;
var_merger(Var, K1, Var) -> K1;
merge_clause_vars(nil, _C2) -> nil;
merge_clause_vars(_C1, nil) -> nil;
merge_clause_vars(C1, C2) ->
orddict:merge(fun var_merger/3, C1, C2).
var_merger({ Var, _ }, Var, K2) -> K2;
var_merger({ Var, _ }, K1, Var) -> K1;
var_merger(_Var, K1, K2) ->
V1 = list_to_integer(var_number(atom_to_list(K1))),
V2 = list_to_integer(var_number(atom_to_list(K2))),
if V1 > V2 -> K1;
true -> K2
V1 = var_number(atom_to_list(K1), []),
V2 = var_number(atom_to_list(K2), []),
case V1 > V2 of
true -> K1;
false -> K2
end.
var_number([_,_|T]) -> T.
var_number([$@|T], _Acc) -> var_number(T, []);
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
+1 -1
View File
@@ -14,7 +14,7 @@ init(Args) ->
permanent, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir] % Modules = [Module] | dynamic
[elixir_code_server] % Modules = [Module] | dynamic
}
],
+233 -167
View File
@@ -3,6 +3,13 @@
-export([tokenize/3]).
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
-record(scope, {
file,
terminators=[],
check_terminators=true,
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).
@@ -72,53 +79,82 @@
T == $@
).
tokenize(String, Line, File) ->
tokenize(String, Line, File, [], []).
-define(unary_op(T),
T == '+';
T == '-';
T == '@';
T == '!';
T == '^'
).
tokenize([], _Line, _File, [], Tokens) ->
tokenize(String, Line, Opts) ->
File = case lists:keyfind(file, 1, Opts) of
{ file, V1 } -> V1;
false -> <<"nofile">>
end,
Existing = case lists:keyfind(existing_atoms_only, 1, Opts) of
{ existing_atoms_only, true } -> true;
false -> false
end,
Check = case lists:keyfind(check_terminators, 1, Opts) of
{ check_terminators, false } -> false;
false -> true
end,
Scope = #scope{
file=File,
existing_atoms_only=Existing,
check_terminators=Check
},
tokenize(String, Line, Scope, []).
tokenize([], _Line, #scope{terminators=[]}, Tokens) ->
{ ok, lists:reverse(Tokens) };
tokenize([], EndLine, _File, [{ Start, StartLine }|_], _Tokens) ->
tokenize([], EndLine, #scope{terminators=[{ Start, StartLine }|_]}, _Tokens) ->
End = terminator(Start),
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
{ error, { EndLine, Message, [] } };
% Base integers
tokenize([$0,X,H|T], Line, File, Terminators, Tokens) when (X == $x orelse X == $X), ?is_hex(H) ->
tokenize([$0,X,H|T], Line, Scope, Tokens) when (X == $x orelse X == $X), ?is_hex(H) ->
{ Rest, Number } = tokenize_hex([H|T], []),
tokenize(Rest, Line, File, Terminators, [{number,Line,Number}|Tokens]);
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
tokenize([$0,O,H|T], Line, File, Terminators, Tokens) when (O == $o orelse O == $O), ?is_octal(H) ->
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, File, Terminators, [{number,Line,Number}|Tokens]);
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
tokenize([$0,B,H|T], Line, File, Terminators, Tokens) when (B == $b orelse B == $B), ?is_bin(H) ->
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, File, Terminators, [{number,Line,Number}|Tokens]);
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
% Comments
tokenize([$#|String], Line, File, Terminators, Tokens) ->
tokenize([$#|String], Line, Scope, Tokens) ->
Rest = tokenize_comment(String),
tokenize(Rest, Line, File, Terminators, Tokens);
tokenize(Rest, Line, Scope, Tokens);
% Sigils
tokenize([$%,S,H,H,H|T], Line, File, Terminators, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
tokenize([$%,S,H,H,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
case extract_heredoc_with_interpolation(Line, File, ?is_downcase(S), T, H) of
{ error, _ } = Error ->
Error;
{ Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, Line, File, Terminators, [{sigil,Line,S,Parts,Modifiers}|Tokens])
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
end;
tokenize([$%,S,H|T], Line, File, Terminators, Tokens) when not(?is_word(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
case elixir_interpolation:extract(Line, File, ?is_downcase(S), T, sigil_terminator(H)) of
{ NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, NewLine, File, Terminators, [{sigil,Line,S,Parts,Modifiers}|Tokens]);
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
Error ->
Sigil = [$%,S,H],
interpolation_error(Error, " (for sigil ~s starting at line ~B)", [Sigil, Line])
@@ -126,74 +162,85 @@ tokenize([$%,S,H|T], Line, File, Terminators, Tokens) when not(?is_word(H)), ?is
% Char tokens
tokenize([$?,$\\,H|T], Line, File, Terminators, Tokens) ->
tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
Char = elixir_interpolation:unescape_map(H),
tokenize(T, Line, File, Terminators, [{number,Line,Char}|Tokens]);
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,Char|T], Line, File, Terminators, Tokens) ->
tokenize(T, Line, File, Terminators, [{number,Line,Char}|Tokens]);
tokenize([$?,Char|T], Line, Scope, Tokens) ->
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
% Dot identifier
tokenize("..." ++ Rest, Line, File, Terminators, Tokens) ->
tokenize(Rest, Line, File, Terminators, [tokenize_call_identifier(identifier, Line, '...', Rest)|Tokens]);
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
Token = tokenize_call_identifier(identifier, Line, '...', Rest),
tokenize(Rest, Line, Scope, [Token|Tokens]);
% Dot operators
% ## Containers
tokenize(".<<>>" ++ Rest, Line, File, Terminators, Tokens) ->
handle_call_identifier(Line, File, Terminators, '<<>>', Rest, Tokens);
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
tokenize([$.,T1,T2|Rest], Line, File, Terminators, Tokens) when ?container2(T1, T2) ->
handle_call_identifier(Line, File, Terminators, list_to_atom([T1, T2]), Rest, Tokens);
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Three Token Operators
tokenize([$.,T1,T2,T3|Rest], Line, File, Terminators, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
handle_call_identifier(Line, File, Terminators, list_to_atom([T1, T2, T3]), Rest, Tokens);
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
% ## Two Token Operators
tokenize([$.,T1,T2|Rest], Line, File, Terminators, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
handle_call_identifier(Line, File, Terminators, list_to_atom([T1, T2]), Rest, Tokens);
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Single Token Operators
tokenize([$.,T|Rest], Line, File, Terminators, Tokens) when ?comp1(T); ?op1(T); T == $& ->
handle_call_identifier(Line, File, Terminators, list_to_atom([T]), Rest, Tokens);
tokenize([$.,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $& ->
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
% Dot call
% ## Exception for .( as it needs to be treated specially in the parser
tokenize([$.,$(|Rest], Line, File, Terminators, Tokens) ->
tokenize([$(|Rest], Line, File, Terminators, add_token_with_nl({dot_call_op,Line,'.'}, Tokens));
tokenize([$.,$(|Rest], Line, Scope, Tokens) ->
tokenize([$(|Rest], Line, Scope, add_token_with_nl({ dot_call_op, Line, '.' }, Tokens));
tokenize([$.,H|T], Line, File, Terminators, Tokens) when ?is_quote(H) ->
tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, [Part], Rest } when is_binary(Part) ->
Atom = binary_to_atom(Part, utf8),
tokenize(Rest, NewLine, File, Terminators,
[tokenize_call_identifier(identifier, Line, Atom, Rest)|add_token_with_nl({'.',Line}, Tokens)]);
Atom = unsafe_to_atom(Part, Scope),
Token = tokenize_call_identifier(identifier, Line, Atom, Rest),
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
Error ->
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
end;
% Heredocs
tokenize("\"\"\"" ++ T, Line, File, Terminators, Tokens) -> handle_heredocs(Line, File, Terminators, $", T, Tokens);
tokenize("'''" ++ T, Line, File, Terminators, Tokens) -> handle_heredocs(Line, File, Terminators, $', T, Tokens);
tokenize("\"\"\"" ++ T, Line, Scope, Tokens) ->
handle_heredocs(T, Line, $", Scope, Tokens);
tokenize("'''" ++ T, Line, Scope, Tokens) ->
handle_heredocs(T, Line, $', Scope, Tokens);
% Strings
tokenize([$"|T], Line, File, Terminators, Tokens) -> handle_strings(Line, File, Terminators, $", T, Tokens);
tokenize([$'|T], Line, File, Terminators, Tokens) -> handle_strings(Line, File, Terminators, $', T, Tokens);
tokenize([$"|T], Line, Scope, Tokens) ->
handle_strings(T, Line, $", Scope, Tokens);
tokenize([$'|T], Line, Scope, Tokens) ->
handle_strings(T, Line, $', Scope, Tokens);
% Atoms
tokenize([$:,T|String], Line, File, Terminators, Tokens) when ?is_upcase(T); ?is_downcase(T); T == $_ ->
{ Rest, Atom } = tokenize_atom([T|String], []),
tokenize(Rest, Line, File, Terminators, [{atom,Line,[Atom]}|Tokens]);
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_upcase(T); ?is_downcase(T); T == $_ ->
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
tokenize([$:,H|T], Line, File, Terminators, Tokens) when ?is_quote(H) ->
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 } -> tokenize(Rest, NewLine, File, Terminators, [{atom,Line,unescape_tokens(Parts)}|Tokens]);
{ NewLine, Parts, Rest } ->
Token = case unescape_tokens(Parts) of
[Part] -> { atom, Line, unsafe_to_atom(Part, Scope) };
Unescaped -> { atom, Line, Unescaped }
end,
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
Error ->
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
end;
@@ -201,140 +248,146 @@ tokenize([$:,H|T], Line, File, Terminators, Tokens) when ?is_quote(H) ->
% Atom operators
% ## Containers
tokenize(":<<>>" ++ Rest, Line, File, Terminators, Tokens) ->
tokenize(Rest, Line, File, Terminators, [{atom,Line,['<<>>']}|Tokens]);
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, [{ atom, Line, '<<>>' }|Tokens]);
tokenize([$:,T1,T2|Rest], Line, File, Terminators, Tokens) when ?container2(T1, T2) ->
tokenize(Rest, Line, File, Terminators, [{atom,Line,[list_to_atom([T1,T2])]}|Tokens]);
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
% ## Three Token Operators
tokenize([$:,T1,T2,T3|Rest], Line, File, Terminators, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
tokenize(Rest, Line, File, Terminators, [{atom,Line,[list_to_atom([T1,T2,T3])]}|Tokens]);
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2,T3]) }|Tokens]);
% ## Two Token Operators
tokenize([$:,T1,T2|Rest], Line, File, Terminators, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
tokenize(Rest, Line, File, Terminators, [{atom,Line,[list_to_atom([T1,T2])]}|Tokens]);
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
% ## Single Token Operators
tokenize([$:,T|Rest], Line, File, Terminators, Tokens) when ?comp1(T); ?op1(T); T == $&; T == $. ->
tokenize(Rest, Line, File, Terminators, [{atom,Line,[list_to_atom([T])]}|Tokens]);
tokenize([$:,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $&; T == $. ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
% End of line
tokenize(";" ++ Rest, Line, File, Terminators, []) ->
tokenize(Rest, Line, File, Terminators, eol(Line, $;, []));
tokenize(";" ++ Rest, Line, Scope, []) ->
tokenize(Rest, Line, Scope, eol(Line, ';', []));
tokenize(";" ++ Rest, Line, File, Terminators, [Top|Tokens]) when element(1, Top) /= eol ->
tokenize(Rest, Line, File, Terminators, eol(Line, $;, [Top|Tokens]));
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
tokenize(Rest, Line, Scope, eol(Line, ';', [Top|Tokens]));
tokenize("\\\n" ++ Rest, Line, File, Terminators, Tokens) ->
tokenize(Rest, Line + 1, File, Terminators, Tokens);
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, Tokens);
tokenize("\\\r\n" ++ Rest, Line, File, Terminators, Tokens) ->
tokenize(Rest, Line + 1, File, Terminators, Tokens);
tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, Tokens);
tokenize("\n" ++ Rest, Line, File, Terminators, Tokens) ->
tokenize(Rest, Line + 1, File, Terminators, eol(Line, $\n, Tokens));
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
tokenize("\r\n" ++ Rest, Line, File, Terminators, Tokens) ->
tokenize(Rest, Line + 1, File, Terminators, eol(Line, $\n, Tokens));
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
% Stand-alone tokens
% ## &
tokenize([$&,H|Rest], Line, File, Terminators, Tokens) when ?is_digit(H) ->
tokenize(Rest, Line, File, Terminators, [{'&', Line, [list_to_integer([H])]}|Tokens]);
tokenize([$&,H|Rest], Line, Scope, Tokens) when ?is_digit(H) ->
tokenize(Rest, Line, Scope, [{ '&', Line, [list_to_integer([H])] }|Tokens]);
% ## Comparison three token operators
tokenize([T1,T2,T3|Rest], Line, File, Terminators, Tokens) when ?comp3(T1, T2, T3) ->
handle_comp_op(Line, File, Terminators, list_to_atom([T1,T2,T3]), Rest, Tokens);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3) ->
handle_comp_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
% ## Three token operators
tokenize([T1,T2,T3|Rest], Line, File, Terminators, Tokens) when ?op3(T1, T2, T3) ->
handle_op(Line, File, Terminators, list_to_atom([T1,T2,T3]), Rest, Tokens, 3);
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
handle_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
% ## Containers + punctuation tokens
tokenize([T,T|Rest], Line, File, Terminators, Tokens) when T == $<; T == $> ->
tokenize([T,T|Rest], Line, Scope, Tokens) when T == $<; T == $> ->
Token = { list_to_atom([T,T]), Line },
case handle_terminator(File, Token, Terminators) of
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, File, New, [Token|Tokens])
New -> tokenize(Rest, Line, New, [Token|Tokens])
end;
tokenize([T|Rest], Line, File, Terminators, Tokens) when T == $(;
tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
T == ${; T == $}; T == $[; T == $]; T == $); T == $, ->
Token = { list_to_atom([T]), Line },
case handle_terminator(File, Token, Terminators) of
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, File, New, [Token|Tokens])
New -> tokenize(Rest, Line, New, [Token|Tokens])
end;
% ## Comparison two token operators
tokenize([T1,T2|Rest], Line, File, Terminators, Tokens) when ?comp2(T1, T2) ->
handle_comp_op(Line, File, Terminators, list_to_atom([T1,T2]), Rest, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
handle_comp_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Two Token Operators
tokenize([T1,T2|Rest], Line, File, Terminators, Tokens) when ?op2(T1, T2) ->
handle_op(Line, File, Terminators, list_to_atom([T1,T2]), Rest, Tokens, 2);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?op2(T1, T2) ->
handle_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Comparison single token operators
tokenize([T|Rest], Line, File, Terminators, Tokens) when ?comp1(T) ->
handle_comp_op(Line, File, Terminators, list_to_atom([T]), Rest, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?comp1(T) ->
handle_comp_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
% ## Single Token Operators
tokenize([T|Rest], Line, File, Terminators, Tokens) when ?op1(T) ->
handle_op(Line, File, Terminators, list_to_atom([T]), Rest, Tokens, 1);
tokenize([T|Rest], Line, Scope, Tokens) when ?op1(T) ->
handle_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
tokenize([$.|Rest], Line, File, Terminators, Tokens) ->
tokenize(Rest, Line, File, Terminators, add_token_with_nl({'.', Line}, Tokens));
tokenize([$.|Rest], Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
% Integers and floats
tokenize([H|_] = String, Line, File, Terminators, Tokens) when ?is_digit(H) ->
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
{ Rest, Number } = tokenize_number(String, [], false),
tokenize(Rest, Line, File, Terminators, [{number,Line,Number}|Tokens]);
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
% Aliases
tokenize([H|_] = String, Line, File, Terminators, Tokens) when ?is_upcase(H) ->
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
{ Rest, Alias } = tokenize_identifier(String, [], false),
tokenize(Rest, Line, File, Terminators, [{'__aliases__',Line,[list_to_atom(Alias)]}|Tokens]);
Atom = unsafe_to_atom(Alias, Scope),
case Rest of
[$:|T] when hd(T) /= $: ->
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
tokenize(Rest, Line, Scope, [{ '__aliases__', Line, [Atom] }|Tokens])
end;
% Identifier
tokenize([H|_] = String, Line, File, Terminators, Tokens) when ?is_downcase(H); H == $_ ->
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(Line, File, String, []),
case keyword(Line, Kind, Identifier, Tokens) of
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(String, Line, [], Scope),
case handle_keyword(Line, Kind, Identifier, Tokens) of
false ->
tokenize(Rest, Line, File, Terminators, [{Kind,Line,Identifier}|Tokens]);
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
[Check|T] ->
case handle_terminator(File, Check, Terminators) of
case handle_terminator(Check, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, File, New, [Check|T])
New -> tokenize(Rest, Line, New, [Check|T])
end
end;
% Ambiguous unary/binary operators tokens
tokenize([Space,Sign,NotMarker|T], Line, File, Terminators, [{Identifier,_,_} = H|Tokens]) when Sign == $+ orelse Sign == $-,
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
Sign == $+ orelse Sign == $-,
Space == $\s orelse Space == $\t,
NotMarker /= $\s, NotMarker /= $\t, NotMarker /= $\r,
NotMarker /= $\n, NotMarker /= $:, NotMarker /= $(,
NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
Identifier == identifier orelse Identifier == punctuated_identifier ->
Rest = [NotMarker|T],
tokenize(Rest, Line, File, Terminators, [{list_to_atom([Sign]),Line},setelement(1, H, op_identifier)|Tokens]);
tokenize(Rest, Line, Scope, [{ list_to_atom([Sign]), Line }, setelement(1, H, op_identifier)|Tokens]);
% Spaces
tokenize([T|Rest], Line, File, Terminators, Tokens) when T == $\s; T == $\r; T == $\t ->
tokenize(Rest, Line, File, Terminators, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when T == $\s; T == $\r; T == $\t ->
tokenize(Rest, Line, Scope, Tokens);
tokenize([{line,Line}|Rest], _, File, Terminators, Tokens) ->
tokenize(Rest, Line, File, Terminators, Tokens);
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, Tokens);
tokenize(T, Line, _File, _Termiantors, _Tokens) ->
tokenize(T, Line, _Scope, _Tokens) ->
{ error, { Line, "invalid token: ", until_eol(T) } }.
until_eol("\r\n" ++ _) -> [];
@@ -344,58 +397,67 @@ until_eol([H|T]) -> [H|until_eol(T)].
%% Handlers
handle_heredocs(Line, File, Terminators, H, T, Tokens) ->
handle_heredocs(T, Line, H, #scope{file=File} = Scope, Tokens) ->
case extract_heredoc_with_interpolation(Line, File, true, T, H) of
{ error, _ } = Error ->
Error;
{ Parts, Rest } ->
Token = { string_type(H), Line, unescape_tokens(Parts) },
tokenize(Rest, Line, File, Terminators, [Token|Tokens])
tokenize(Rest, Line, Scope, [Token|Tokens])
end.
handle_strings(Line, File, Terminators, H, T, Tokens) ->
handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
{ NewLine, Parts, [$:|Rest] } ->
{ NewLine, Parts, [$:|Rest] } when hd(Rest) /= $: ->
case Parts of
[Bin] when is_binary(Bin) ->
Atom = binary_to_atom(unescape_chars(Bin), utf8),
tokenize(Rest, NewLine, File, Terminators, [{kw_identifier,Line,Atom}|Tokens]);
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
{ error, { Line, "invalid interpolation in key", [$"|T] } }
end;
{ NewLine, Parts, Rest } ->
Token = { string_type(H),Line,unescape_tokens(Parts) },
tokenize(Rest, NewLine, File, Terminators, [Token|Tokens]);
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
Error ->
interpolation_error(Error, " (for string starting at line ~B)", [Line])
end.
handle_comp_op(Line, File, Terminators, Op, [$:,S|Rest], Tokens) when ?is_space(S) ->
tokenize(Rest, Line, File, Terminators, [{kw_identifier, Line, Op}|Tokens]);
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
verify_kw_and_space(Line, Op, Rest, Scope),
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_comp_op(Line, File, Terminators, Op, Rest, Tokens) ->
tokenize(Rest, Line, File, Terminators, add_token_with_nl({comp_op, Line, Op}, Tokens)).
handle_comp_op(Rest, Line, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ comp_op, Line, Op }, Tokens)).
handle_op(Line, File, Terminators, Op, [$:,S|Rest], Tokens, _) when ?is_space(S) ->
tokenize(Rest, Line, File, Terminators, [{kw_identifier, Line, Op}|Tokens]);
handle_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
verify_kw_and_space(Line, Op, Rest, Scope),
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
handle_op(Line, File, Terminators, Op, Rest, Tokens, 1) ->
tokenize(Rest, Line, File, Terminators, [{Op, Line}|Tokens]);
handle_op(Rest, Line, Op, Scope, Tokens) when ?unary_op(Op) ->
tokenize(Rest, Line, Scope, [{ Op, Line }|Tokens]);
handle_op(Line, File, Terminators, Op, Rest, Tokens, _) ->
tokenize(Rest, Line, File, Terminators, add_token_with_nl({Op, Line}, Tokens)).
handle_op(Rest, Line, Op, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
handle_call_identifier(Line, File, Terminators, Op, Rest, Tokens) ->
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
Token = tokenize_call_identifier(identifier, Line, Op, Rest),
tokenize(Rest, Line, File, Terminators, [Token|add_token_with_nl({'.',Line}, Tokens)]).
tokenize(Rest, Line, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]).
%% Helpers
eol(Line, Mod, Tokens) ->
case Tokens of
[{eol,_,_}|_] -> Tokens;
_ -> [{eol,Line,Mod}|Tokens]
end.
eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
unsafe_to_atom(Binary, #scope{existing_atoms_only=true}) when is_binary(Binary) ->
binary_to_existing_atom(Binary, utf8);
unsafe_to_atom(Binary, #scope{}) when is_binary(Binary) ->
binary_to_atom(Binary, utf8);
unsafe_to_atom(List, #scope{existing_atoms_only=true}) when is_list(List) ->
list_to_existing_atom(List);
unsafe_to_atom(List, #scope{}) when is_list(List) ->
list_to_atom(List).
collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
collect_modifiers(T, [H|Buffer]);
@@ -583,32 +645,33 @@ tokenize_identifier(Rest, Acc, _Marker) ->
{ Rest, lists:reverse(Acc) }.
% Tokenize atom identifier, which also accepts punctuated identifiers
tokenize_atom(String, Acc) ->
tokenize_atom(String, Acc, Scope) ->
{ Rest, Identifier } = tokenize_identifier(String, Acc, atom),
case Rest of
[H|T] when H == $?; H == $! ->
{ T, ?ELIXIR_ATOM_CONCAT([Identifier, [H]]) };
{ T, unsafe_to_atom(Identifier ++ [H], Scope) };
_ ->
{ Rest, list_to_atom(Identifier) }
{ Rest, unsafe_to_atom(Identifier, Scope) }
end.
% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
tokenize_any_identifier(Line, File, String, Acc) ->
tokenize_any_identifier(String, Line, Acc, Scope) ->
{ Rest, Identifier } = tokenize_identifier(String, Acc, false),
case Rest of
[H,$:|T] when H == $?; H == $! ->
Atom = ?ELIXIR_ATOM_CONCAT([Identifier, [H]]),
verify_kw_and_space(Line, File, Atom, T),
[H,$:|T] when H == $? orelse H == $!, hd(T) /= $: ->
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
verify_kw_and_space(Line, Atom, T, Scope),
{ T, { kw_identifier, Line, Atom } };
[H|T] when H == $?; H == $! ->
Atom = ?ELIXIR_ATOM_CONCAT([Identifier, [H]]),
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
{ T, tokenize_call_identifier(punctuated_identifier, Line, Atom, T) };
[$:|T] ->
Atom = list_to_atom(Identifier),
verify_kw_and_space(Line, File, Atom, T),
[$:|T] when hd(T) /= $: ->
Atom = unsafe_to_atom(Identifier, Scope),
verify_kw_and_space(Line, Atom, T, Scope),
{ T, { kw_identifier, Line, Atom } };
_ ->
{ Rest, tokenize_call_identifier(identifier, Line, list_to_atom(Identifier), Rest) }
{ Rest, tokenize_call_identifier(identifier, Line, unsafe_to_atom(Identifier, Scope), Rest) }
end.
tokenize_call_identifier(Kind, Line, Atom, Rest) ->
@@ -617,13 +680,13 @@ tokenize_call_identifier(Kind, Line, Atom, Rest) ->
[$[|_] -> { bracket_identifier, Line, Atom };
_ ->
case next_is_block(Rest) of
[] -> { Kind, Line, Atom };
false -> { Kind, Line, Atom };
BlockIdentifier -> { BlockIdentifier, Line, Atom }
end
end.
verify_kw_and_space(_Line, _File, _Atom, [H|_]) when ?is_space(H) -> ok;
verify_kw_and_space(Line, File, Atom, _) ->
verify_kw_and_space(_Line, _Atom, [H|_], #scope{}) when ?is_space(H) -> ok;
verify_kw_and_space(Line, Atom, _, #scope{file=File}) ->
io:format("~ts:~w: keyword argument ~s: must be followed by space~n", [File, Line, Atom]).
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
@@ -631,15 +694,15 @@ next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
next_is_block([$d,$o,H|_]) when
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_; H == $: ->
[];
false;
next_is_block([$d,$o|_]) ->
do_identifier;
next_is_block(_) ->
[].
false.
add_token_with_nl(Left, [{eol,_,$\n}|T]) -> [Left|T];
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
add_token_with_nl(Left, T) -> [Left|T].
% Error handling
@@ -658,17 +721,18 @@ sigil_terminator(${) -> $};
sigil_terminator($<) -> $>;
sigil_terminator(O) -> O.
%% In case File is "__internal__", we don't
%% do any of the terminator checks
handle_terminator(_, #scope{check_terminators=false} = Scope) ->
Scope;
handle_terminator(Token, #scope{terminators=Terminators} = Scope) ->
case check_terminator(Token, Terminators) of
{ error, _ } = Error -> Error;
New -> Scope#scope{terminators=New}
end.
handle_terminator(<<"__internal__">>, _, []) -> [];
handle_terminator(_File, Token, Terminator) ->
handle_terminator(Token, Terminator).
handle_terminator({ S, Line }, Terminators) when S == 'fn'; S == 'fn_paren' ->
check_terminator({ S, Line }, Terminators) when S == 'fn' ->
[{ fn, Line }|Terminators];
handle_terminator({ S, _ } = New, Terminators) when
check_terminator({ S, _ } = New, Terminators) when
S == 'do';
S == '(';
S == '[';
@@ -676,7 +740,7 @@ handle_terminator({ S, _ } = New, Terminators) when
S == '<<' ->
[New|Terminators];
handle_terminator({ E, _ }, [{ S, _ }|Terminators]) when
check_terminator({ E, _ }, [{ S, _ }|Terminators]) when
S == 'do', E == 'end';
S == 'fn', E == 'end';
S == '(', E == ')';
@@ -685,17 +749,17 @@ handle_terminator({ E, _ }, [{ S, _ }|Terminators]) when
S == '<<', E == '>>' ->
Terminators;
handle_terminator({ E, Line }, [{ Start, StartLine }|_]) when
check_terminator({ E, Line }, [{ Start, StartLine }|_]) when
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
End = terminator(Start),
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
{ error, { Line, Message, [] } };
handle_terminator({ E, Line }, []) when
check_terminator({ E, Line }, []) when
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
{ error, { Line, "unexpected token: ", atom_to_list(E) } };
handle_terminator(_, Terminators) ->
check_terminator(_, Terminators) ->
Terminators.
terminator('fn') -> 'end';
@@ -706,10 +770,12 @@ terminator('{') -> '}';
terminator('<<') -> '>>'.
% Keywords check
keyword(Line, paren_identifier, fn, Tokens) ->
[{ 'fn_paren', Line }|Tokens];
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
[{ Identifier, Line, Atom }|Tokens];
keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifier == do_identifier ->
handle_keyword(Line, Identifier, Atom, Tokens) when
Identifier == identifier; Identifier == do_identifier;
Identifier == bracket_identifier; Identifier == paren_identifier ->
case keyword(Atom) of
true -> [{ Atom, Line }|Tokens];
op -> add_token_with_nl({ Atom, Line }, Tokens);
@@ -717,7 +783,7 @@ keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifi
false -> false
end;
keyword(_, _, _, _) -> false.
handle_keyword(_, _, _, _) -> false.
% Keywords
keyword('fn') -> true;
+165 -279
View File
@@ -1,7 +1,9 @@
%% Main entry point for translations. Are macros that cannot be
%% overriden are defined in this file.
-module(elixir_translator).
-export([translate/2, translate_each/2, translate_args/2, translate_apply/7, translate_fn/3, raw_forms/3, forms/3]).
-export([forms/4, 'forms!'/4]).
-export([translate/2, translate_each/2, translate_arg/2,
translate_args/2, translate_apply/7, translate_fn/3]).
-import(elixir_scope, [umergev/2, umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
@@ -9,8 +11,8 @@
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
raw_forms(String, StartLine, File) ->
try elixir_tokenizer:tokenize(String, StartLine, File) of
forms(String, StartLine, File, Opts) ->
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
{ ok, Tokens } ->
case elixir_parser:parse(Tokens) of
{ ok, Forms } -> { ok, Forms };
@@ -21,8 +23,8 @@ raw_forms(String, StartLine, File) ->
{ interpolation_error, { _, _, _ } = Tuple} -> { error, Tuple }
end.
forms(String, StartLine, File) ->
case raw_forms(String, StartLine, File) of
'forms!'(String, StartLine, File, Opts) ->
case forms(String, StartLine, File, Opts) of
{ ok, Forms } -> Forms;
{ error, { Line, Error, Token } } -> parse_error(Line, File, Error, Token)
end.
@@ -42,6 +44,11 @@ translate_each({'=', Line, [Left, Right]}, S) ->
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
{ { match, Line, TLeft, TRight }, SL };
%% Containers
translate_each({ C, _, _ } = Original, S) when C == '[]'; C == '{}'; C == '<<>>' ->
elixir_literal:translate(Original, S);
%% Blocks and scope rewriters
translate_each({ '__block__', Line, [] }, S) ->
@@ -70,9 +77,9 @@ translate_each({ '__op__', Line, [Op, Left, Right] }, S) when is_atom(Op) ->
{ { op, Line, convert_op(Op), TLeft, TRight }, NS };
translate_each({ '__ambiguousop__', Line, [Var, H|T] }, S) ->
{ Name, _, _ } = Var,
{ Name, _, Kind } = Var,
case dict:find(Name, S#elixir_scope.vars) of
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
error -> translate_each({ Name, Line, [H|T] }, S);
_ ->
case T of
@@ -81,45 +88,25 @@ translate_each({ '__ambiguousop__', Line, [Var, H|T] }, S) ->
end
end;
%% Containers
translate_each({ '<<>>', Line, Args }, S) when is_list(Args) ->
case S#elixir_scope.context of
assign ->
elixir_tree_helpers:build_bitstr(fun translate_each/2, Args, Line, S);
_ ->
{ TArgs, { SC, SV } } = elixir_tree_helpers:build_bitstr(fun translate_arg/2, Args, Line, { S, S }),
{ TArgs, umergec(SV, SC) }
end;
translate_each({ '{}', Line, Args }, S) when is_list(Args) ->
{ TArgs, SE } = translate_args(Args, S),
{ {tuple, Line, TArgs}, SE };
translate_each({ '[]', _Line, Args }, S) when is_list(Args) ->
translate_each(Args, S);
%% Lexical
translate_each({ refer, Line, Args }, S) ->
elixir_errors:deprecation(Line, S#elixir_scope.file, "refer is deprecated, please use alias instead"),
translate_each({ alias, Line, Args }, S);
translate_each({ alias, Line, [Ref] }, S) ->
translate_each({ alias, Line, [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),
{ TRef, SR } = translate_each(Ref, S),
case TRef of
{ atom, _, Old } ->
{ New, SF } = case orddict:find(as, KV) of
{ ok, false } ->
{ Old, SR };
Opt when Opt == { ok, true }; Opt == error ->
{ New, SF } = case lists:keyfind(as, 1, KV) of
Opt when Opt == { as, true }; Opt == false ->
{ elixir_aliases:last(Old), SR };
{ ok, Other } ->
{ as, false } ->
{ Old, SR };
{ as, Other } ->
{ TOther, SA } = translate_each(Other, SR#elixir_scope{aliases=[]}),
case TOther of
{ atom, _, Atom } -> { Atom, SA };
@@ -152,11 +139,13 @@ translate_each({ require, Line, [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),
{ TRef, SR } = translate_each(Ref, S),
As = case orddict:find(as, KV) of
{ ok, Value } -> Value;
error -> false
As = case lists:keyfind(as, 1, KV) of
false -> false;
{ as, Value } -> Value
end,
case TRef of
@@ -176,7 +165,7 @@ translate_each({ import, Line, [Left] }, S) ->
translate_each({ import, Line, [Left, []]}, S);
translate_each({ import, Line, [Left,Opts] }, S) when is_list(Opts) ->
translate_each({ import, Line, [all, Left, Opts]}, S);
translate_each({ import, Line, [default, Left, Opts]}, S);
translate_each({ import, Line, [Selector, Left] }, S) ->
translate_each({ import, Line, [Selector, Left, []]}, S);
@@ -188,22 +177,19 @@ translate_each({ import, Line, [Left, Right, Opts] }, S) ->
Selector = case TSelector of
{ atom, _, SelectorAtom } -> SelectorAtom;
_ -> syntax_error(Line, S#elixir_scope.file, "invalid selector for import")
_ -> syntax_error(Line, 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")
_ -> syntax_error(Line, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
end,
case is_list(Opts) of
true -> [];
_ -> syntax_error(Line, S#elixir_scope.file, "invalid options for import")
end,
validate_opts(Line, import, [as, only, except], Opts, S),
As = case orddict:find(as, Opts) of
{ ok, Value } -> Value;
error -> false
As = case lists:keyfind(as, 1, Opts) of
false -> false;
{ as, Value } -> Value
end,
elixir_aliases:ensure_loaded(Line, Ref, SR),
@@ -224,21 +210,6 @@ translate_each({ '__ENV__', Line, Atom }, S) when is_atom(Atom) ->
translate_each({ '__CALLER__', Line, Atom }, S) when is_atom(Atom) ->
{ { var, Line, '__CALLER__' }, S#elixir_scope{caller=true} };
%% Arg-less deprecated macros
translate_each({'__FUNCTION__', Line, Atom}, S) when is_atom(Atom) ->
elixir_errors:deprecation(Line, S#elixir_scope.file, "__FUNCTION__ is deprecated, use __ENV__.function instead"),
case S#elixir_scope.function of
nil ->
{ { atom, Line, nil }, S };
{ Name, Arity } ->
{ { tuple, Line, [ { atom, Line, Name }, { integer, Line, Arity } ] }, S }
end;
translate_each({'__LINE__', Line, Atom}, S) when is_atom(Atom) ->
elixir_errors:deprecation(Line, S#elixir_scope.file, "__LINE__ is deprecated, use __ENV__.line instead"),
{ { integer, Line, Line }, S };
%% Aliases
translate_each({ '__aliases__', Line, [H] }, S) when H /= 'Elixir' ->
@@ -267,47 +238,52 @@ translate_each({ '__aliases__', Line, [H|T] }, S) ->
%% 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({ quote, Line, [Left, Right] }, S) ->
translate_each({ quote, Line, [orddict:from_list(Left ++ Right)] }, S);
translate_each({ quote, GivenLine, [[{do,Exprs}|T]] }, S) ->
Marker = case orddict:find(hygiene, T) of
{ ok, false } -> nil;
translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
Exprs =
case lists:keyfind(do, 1, T) of
{do, E} -> E;
false ->
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote")
end,
Marker = case lists:keyfind(hygiene, 1, T) of
{ hygiene, false } -> nil;
_ -> quoted
end,
{ DefaultLine, WrappedExprs } = case orddict:find(location, T) of
{ ok, keep } ->
{ 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}
end,
Line = case orddict:find(line, T) of
{ ok, keep } -> keep;
{ ok, Value } when is_integer(Value) -> Value;
{ ok, _ } -> syntax_error(GivenLine, S#elixir_scope.file,
"invalid args for quote. expected line to be the atom :keep or an integer");
Line = case lists:keyfind(line, 1, T) of
{ line, Value } -> Value;
_ -> DefaultLine
end,
Unquote = case orddict:find(unquote, T) of
{ ok, false } -> false;
{ TLine, SL } = case Line of
keep -> { keep, S };
_ -> translate_each(Line, S)
end,
Unquote = case lists:keyfind(unquote, 1, T) of
{ unquote, false } -> false;
_ -> true
end,
elixir_quote:quote(WrappedExprs, #elixir_quote{marker=Marker, line=Line, unquote=Unquote}, S);
elixir_quote:quote(WrappedExprs, #elixir_quote{marker=Marker, line=TLine, unquote=Unquote}, SL);
translate_each({ quote, Line, [_] }, S) ->
syntax_error(Line, S#elixir_scope.file, "invalid args for quote");
translate_each({ in_guard, Line, [[{do,Guard},{else,Else}]] }, S) ->
elixir_errors:deprecation(Line, S#elixir_scope.file, "in_guard is deprecated, check __CALLER__.in_guard? instead"),
case S#elixir_scope.context of
guard -> translate_each(Guard, S);
_ -> translate_each(Else, S)
end;
translate_each({ quote, GivenLine, [_] }, S) ->
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote");
%% Functions
@@ -315,27 +291,36 @@ translate_each({ fn, Line, [[{do, { '->', _, Pairs }}]] }, S) ->
assert_no_assign_or_guard_scope(Line, 'fn', S),
translate_fn(Line, Pairs, S);
%% Super
%% Comprehensions
translate_each({ super, Line, Args }, #elixir_scope{file=File} = S) ->
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),
translate_each({ Kind, Line, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Line, Kind, Args, S);
{ _, Arity } = Function,
%% Super (exceptionally supports partial application)
{ TArgs, TS } = if
is_atom(Args) ->
elixir_def_overridable:retrieve_args(Line, Arity, S);
length(Args) == Arity ->
translate_args(Args, S);
true ->
syntax_error(Line, File, "super must be called with the same number of arguments as the current function")
end,
translate_each({ super, Line, 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),
Super = elixir_def_overridable:name(Module, Function),
{ { call, Line, { atom, Line, Super }, TArgs }, TS#elixir_scope{super=true} };
{ _, Arity } = Function,
{ TArgs, TS } = if
is_atom(Args) ->
elixir_def_overridable:retrieve_args(Line, 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")
end,
Super = elixir_def_overridable:name(Module, Function),
{ { call, Line, { atom, Line, Super }, TArgs }, TS#elixir_scope{super=true} };
Else -> Else
end;
translate_each({ 'super?', Line, [] }, S) ->
Module = assert_module_scope(Line, 'super?', S),
@@ -343,58 +328,43 @@ translate_each({ 'super?', Line, [] }, S) ->
Bool = elixir_def_overridable:is_defined(Module, Function),
{ { atom, Line, Bool }, S };
%% Comprehensions
translate_each({ Kind, Line, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Line, Kind, Args, S);
%% Variables
translate_each({ '^', Line, [ { Name, _, Args } ] }, S) ->
Result = case is_atom(Args) of
true ->
case S#elixir_scope.context of
assign ->
case dict:find(Name, S#elixir_scope.vars) of
error -> "unbound variable ^~s";
{ ok, Value } -> { {var, Line, Value}, S }
end;
_ -> "cannot access variable ^~s outside of assignment"
end;
false -> "cannot use ^ with expression at ^~s, ^ must be used only with variables"
end,
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]);
case is_list(Result) of
true ->
syntax_error(Line, S#elixir_scope.file, Result, [Name]);
false ->
Result
translate_each({ '^', Line, [ { 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 };
error ->
syntax_error(Line, S#elixir_scope.file, "unbound variable ^~s", [Name])
end;
translate_each({ Name, Line, quoted }, S) when is_atom(Name) ->
NewS = S#elixir_scope{vars=S#elixir_scope.quote_vars,noname=true},
{ TVar, VS } = elixir_scope:translate_var(Line, Name, NewS),
{ TVar, VS#elixir_scope{
quote_vars=VS#elixir_scope.vars,
noname=S#elixir_scope.noname,
vars=S#elixir_scope.vars
} };
translate_each({ '^', Line, [ { Name, _, Kind } ] }, S) when is_atom(Kind) ->
syntax_error(Line, S#elixir_scope.file,
"cannot access variable ^~s outside of assignment", [Name]);
translate_each({ Name, Line, nil }, S) when is_atom(Name) ->
elixir_scope:translate_var(Line, Name, S);
translate_each({ Name, Line, Kind }, S) when is_atom(Name), (Kind == nil orelse Kind == quoted) ->
elixir_scope:translate_var(Line, Name, Kind, S);
%% Local calls
translate_each({ Atom, Line, Args } = Original, S) when is_atom(Atom) ->
case sequential_partials(Args, 1) andalso
case elixir_partials:is_sequential(Args) andalso
elixir_dispatch:import_function(Line, Atom, length(Args), S) of
false ->
case handle_partials(Line, Original, S) of
case elixir_partials:handle(Original, S) of
error ->
Callback = fun() ->
case S#elixir_scope.context of
guard -> syntax_error(Line, S#elixir_scope.file,
"cannot invoke local ~s/~B inside guard", [Atom, length(Args)]);
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,
@@ -404,29 +374,22 @@ translate_each({ Atom, Line, Args } = Original, S) when is_atom(Atom) ->
Else -> Else
end;
%% Dot calls
%% Remote calls
translate_each({ { '.', _, [Left, Right] }, Line, Args } = Original, S) when is_atom(Right) ->
{ TLeft, SL } = translate_each(Left, S),
Fun = (element(1, TLeft) == atom) andalso sequential_partials(Args, 1) andalso
Fun = (element(1, TLeft) == atom) andalso elixir_partials:is_sequential(Args) andalso
elixir_dispatch:require_function(Line, element(3, TLeft), Right, length(Args), SL),
case Fun of
false ->
case handle_partials(Line, Original, S) of
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,
case TLeft of
{ atom, _, 'Elixir.Erlang' } ->
case Args of
[] -> { { atom, Line, Right }, S };
_ ->
Message = "invalid args for Erlang.~s expression",
syntax_error(Line, S#elixir_scope.file, Message, [Right])
end;
{ atom, _, Receiver } ->
elixir_dispatch:dispatch_require(Line, Receiver, Right, Args, umergev(SL, SR), Callback);
_ ->
@@ -445,7 +408,7 @@ translate_each({ { '.', _, [Expr] }, Line, Args } = Original, S) ->
{ atom, _, Atom } ->
translate_each({ Atom, Line, Args }, S);
_ ->
case handle_partials(Line, Original, S) of
case elixir_partials:handle(Original, S) of
error ->
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
{ {call, Line, TExpr, TArgs}, umergev(SE, SA) };
@@ -453,54 +416,27 @@ translate_each({ { '.', _, [Expr] }, Line, Args } = Original, S) ->
end
end;
%% Invalid calls
translate_each({ Invalid, Line, _Args }, S) ->
syntax_error(Line, S#elixir_scope.file, "unexpected parenthesis after ~ts",
['Elixir.Macro':to_binary(Invalid)]);
%% Literals
translate_each({ Left, Right }, S) ->
translate_each({ '{}', 0, [Left, Right]}, S);
translate_each([], S) ->
{ { nil, 0 }, S };
translate_each(Args, S) when is_list(Args) ->
[RTail|RArgs] = lists:reverse(Args),
case RTail of
{'|',_,[Left,Right]} ->
Exprs = [Left|RArgs],
{ Tail, ST } = translate_each(Right, S),
ListS = umergec(S, ST);
_ ->
Exprs = [RTail|RArgs],
Tail = { nil, 0 },
ST = S,
ListS = S
end,
{ FExprs, FS } = case S#elixir_scope.context of
assign ->
elixir_tree_helpers:build_reverse_list(fun translate_each/2, Exprs, 0, ListS, Tail);
_ ->
{ TArgs, { SC, SV } } =
elixir_tree_helpers:build_reverse_list(fun translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
{ TArgs, umergec(SV, SC) }
end,
{ FExprs, umergev(ST, FS) };
translate_each(Number, S) when is_integer(Number) ->
{ { integer, 0, Number }, S };
translate_each(Number, S) when is_float(Number) ->
{ { float, 0, Number }, S };
translate_each(Atom, S) when is_atom(Atom) ->
{ { atom, 0, Atom }, S };
translate_each(Bitstring, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
translate_each(Literal, S) ->
elixir_literal:translate(Literal, S).
%% Helpers
validate_opts(Line, Kind, Allowed, Opts, S) when is_list(Opts) ->
[begin
syntax_error(Line, 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]).
translate_fn(Line, Clauses, S) ->
Transformer = fun({ ArgsWithGuards, Expr }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_last_guards(ArgsWithGuards),
@@ -508,7 +444,14 @@ translate_fn(Line, Clauses, S) ->
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
{ { 'fun', Line, {clauses, TClauses} }, umergec(S, NS) }.
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
_ ->
syntax_error(Line, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
end.
translate_local(Line, Name, Args, #elixir_scope{local=nil} = S) ->
{ TArgs, NS } = translate_args(Args, S),
@@ -522,20 +465,23 @@ translate_local(Line, Name, Args, S) ->
},
{ { call, Line, Remote, TArgs }, NS }.
% Variables in arguments are not propagated from one
% argument to the other. For instance:
%
% x = 1
% foo(x = x + 2, x)
% x
%
% Should be the same as:
%
% foo(3, 1)
% 3
%
% However, notice that if we are doing an assignment,
% it behaves the same as translate.
%% Translate args
%% Variables in arguments are not propagated from one
%% argument to the other. For instance:
%%
%% x = 1
%% foo(x = x + 2, x)
%% x
%%
%% Should be the same as:
%%
%% foo(3, 1)
%% 3
%%
%% However, notice that if we are doing an assignment,
%% it behaves the same as translate.
translate_arg(Arg, { Acc, S }) ->
{ TArg, TAcc } = translate_each(Arg, Acc),
{ TArg, { umergec(S, TAcc), umergev(S, TAcc) } }.
@@ -547,17 +493,16 @@ translate_args(Args, S) ->
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
{ TArgs, umergec(SV, SC) }.
% Translate apply. Used by both apply and
% external function invocation macros.
%% Translate apply
%% Used by both apply and external function invocation macros.
translate_apply(Line, TLeft, TRight, Args, S, SL, SR) ->
Optimize = case (Args == []) orelse lists:last(Args) of
{ '|', _, _ } -> false;
_ ->
case { TLeft, TRight } of
{ { Kind, _, _ }, { atom, _, _ } } when Kind == var; Kind == tuple; Kind == atom ->
true;
_ ->
false
case TRight of
{ atom, _, _ } -> true;
_ -> false
end
end,
@@ -572,62 +517,7 @@ translate_apply(Line, TLeft, TRight, Args, S, SL, SR) ->
{ ?ELIXIR_WRAP_CALL(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
end.
%% Handle partials by automatically wrapping them in a function.
%% It also checks if we are in an assignment scope and does not
%% apply the function if this is the case.
handle_partials(_Line, _Original, #elixir_scope{context=assign}) ->
error;
handle_partials(Line, Original, S) ->
case convert_partials(Line, element(3, Original), S) of
{ Call, Def, SC } when Def /= [] ->
Final = validate_partials(Line, Def, SC),
Block = setelement(3, Original, Call),
translate_fn(Line, [{ Final, Block }], SC);
_ -> error
end.
validate_partials(Line, Def, S) ->
validate_partials(Line, lists:sort(Def), 1, S).
validate_partials(Line, [{ Pos, Item }|T], Pos, S) ->
[Item|validate_partials(Line, T, Pos + 1, S)];
validate_partials(Line, [{ Pos, _ }|_], Expected, S) ->
syntax_error(Line, S#elixir_scope.file, "partial variable &~w cannot be defined without &~w", [Pos, Expected]);
validate_partials(_Line, [], _Pos, _S) ->
[].
%% This function receives arguments and then checks
%% the args for partial application. It returns a tuple
%% with three elements where the first element is the list
%% of call args, the second the list of def args for the
%% function definition and the third one is the new scope.
convert_partials(Line, List, S) -> convert_partials(Line, List, S, [], []).
convert_partials(Line, [{'&', _, [Pos]}|T], S, CallAcc, DefAcc) ->
case lists:keyfind(Pos, 1, DefAcc) of
false ->
{ Var, SC } = elixir_scope:build_ex_var(Line, S),
convert_partials(Line, T, SC, [Var|CallAcc], [{Pos,Var}|DefAcc]);
{Pos,Var} ->
convert_partials(Line, T, S, [Var|CallAcc], DefAcc)
end;
convert_partials(Line, [H|T], S, CallAcc, DefAcc) ->
convert_partials(Line, T, S, [H|CallAcc], DefAcc);
convert_partials(_Line, [], S, CallAcc, DefAcc) ->
{ lists:reverse(CallAcc), lists:reverse(DefAcc), S }.
sequential_partials([{ '&', _, [Int] }|T], Int) ->
sequential_partials(T, Int + 1);
sequential_partials([], Int) when Int > 1 -> true;
sequential_partials(_, _Int) -> false.
%% Convert operators
%% __op__ helpers
convert_op('and') -> 'andalso';
convert_op('or') -> 'orelse';
@@ -654,7 +544,7 @@ rellocate_ambiguous_op({ Call, Line, [H|T] }, Var) ->
translate_comprehension(Line, Kind, Args, S) ->
case elixir_tree_helpers:split_last(Args) of
{ Cases, [{do,Expr}] } ->
{ TCases, SC } = lists:mapfoldl(fun translate_comprehension_clause/2, S, Cases),
{ 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) };
_ ->
@@ -670,20 +560,16 @@ translate_comprehension_do(Line, bc, _Expr, S) ->
translate_comprehension_do(_Line, _Kind, Expr, S) ->
translate_each(Expr, S).
translate_comprehension_clause({inbits, Line, [Left, Right]}, S) ->
translate_comprehension_clause(_Line, {inbits, Line, [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 };
translate_comprehension_clause({inlist, Line, [Left, Right]}, S) ->
translate_comprehension_clause(_Line, {inlist, Line, [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 };
translate_comprehension_clause(X, S) ->
translate_comprehension_clause(Line, X, S) ->
{ TX, TS } = translate_each(X, S),
Line = case X of
{ _, L, _ } -> L;
_ -> 0
end,
{ elixir_tree_helpers:convert_to_boolean(Line, TX, true, false), TS }.
elixir_tree_helpers:convert_to_boolean(Line, TX, true, false, TS).
+37 -90
View File
@@ -1,11 +1,12 @@
%% Convenience functions used around elixir source code
%% Convenience functions used throughout elixir source code
%% to generate erlang abstract format for basic structures
%% as binaries, lists, condition clauses, etc.
%% as lists, condition clauses, etc.
-module(elixir_tree_helpers).
-compile({parse_transform, elixir_transform}).
-export([abstract_syntax/1, split_last/1, cons_to_list/1,
convert_to_boolean/4, returns_boolean/1,
build_bitstr/4, build_list/4, build_list/5, build_simple_list/2,
convert_to_boolean/5, returns_boolean/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").
@@ -62,73 +63,6 @@ build_list_each(Fun, [H|T], Line, S, Acc) ->
{ Expr, NS } = Fun(H, S),
build_list_each(Fun, T, Line, NS, { cons, Line, Expr, Acc }).
% 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
% * 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_each(_Fun, [], _Line, 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 } = 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),
%% Just variables defined outside the binary can be accounted on subparts
case NS of
{ ES, _ } -> [];
ES -> []
end,
%% Assigns can be made in subparts
{ Int, Types } = extract_bin_values(Line, V, default, [], ES#elixir_scope{context=nil}),
Final = case Types of
[] -> default;
_ -> lists:reverse(Types)
end,
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Int, Final }|Acc]);
build_bitstr_each(Fun, [H|T], Line, S, Acc) ->
{ Expr, NS } = Fun(H, S),
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, default, default }|Acc]).
%% Extra binary specifiers
extract_bin_values(Line, { '-', _Line, [Left, Right] }, Int, Types, S) ->
{ LInt, LTypes } = extract_bin_values(Line, Left, Int, Types, S),
extract_bin_values(Line, Right, LInt, LTypes, S);
extract_bin_values(Line, Value, default, Types, _S) when is_integer(Value) ->
{ { integer, Line, Value }, Types };
extract_bin_values(_Line, { Value, _, Atom } = Expr, default, Types, S) when is_atom(Value), is_atom(Atom) ->
Translated = element(1, elixir_translator:translate_each(Expr, S)),
{ Translated, Types };
extract_bin_values(Line, Value, _Int, _Types, S) when is_integer(Value) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "duplicated size specifier ~p in <<>>", [Value]);
extract_bin_values(_Line, Value, Int, Types, _S) when is_atom(Value); is_tuple(Value), tuple_size(Value) == 2 ->
{ Int, [Value|Types] };
extract_bin_values(Line, _Value, _Int, _Types, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid specifier for <<>>").
%% Others
returns_boolean({ op, _, Op, _ }) when Op == 'not' -> true;
@@ -150,34 +84,47 @@ returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } },
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_] }) when
Fun == is_function -> true;
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_,_] }) when
Fun == function_exported -> true;
returns_boolean({ atom, _, Bool }) when Bool == true; Bool == false -> true;
returns_boolean({ 'case', _, _, Clauses }) ->
lists:all(fun
({clause,_,_,_,[Expr]}) -> returns_boolean(Expr);
(_) -> false
end, Clauses);
returns_boolean(_) -> false.
convert_to_boolean(Line, Expr, Bool, InGuard) ->
convert_to_boolean(Line, Expr, Bool, InGuard, S) ->
case { returns_boolean(Expr), Bool } of
{ true, true } -> Expr;
{ true, false } -> { op, Line, 'not', Expr };
_ -> do_convert_to_boolean(Line, Expr, Bool, InGuard)
{ true, true } -> { Expr, S };
{ true, false } -> { { op, Line, 'not', Expr }, S };
_ -> do_convert_to_boolean(Line, Expr, Bool, InGuard, S)
end.
do_convert_to_boolean(Line, Expr, Bool, false) ->
Any = [{var, Line, '_'}],
False = [{atom,Line,false}],
Nil = [{atom,Line,nil}],
%% Notice we use a temporary var and bundle nil
%% and false checks in the same clause since
%% it makes dialyzer happy.
do_convert_to_boolean(Line, Expr, Bool, false, S) ->
Any = { var, Line, '_' },
{ Var, TS } = elixir_scope:build_erl_var(Line, S),
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
FalseResult = [{atom,Line,not Bool}],
TrueResult = [{atom,Line,Bool}],
FalseResult = { atom,Line,not Bool },
TrueResult = { atom,Line,Bool },
{ 'case', Line, Expr, [
{ clause, Line, False, [], FalseResult },
{ clause, Line, Nil, [], FalseResult },
{ clause, Line, Any, [], TrueResult }
] };
{ { 'case', Line, Expr, [
{ clause, Line, [Var], [[OrElse]], [FalseResult] },
{ clause, Line, [Any], [], [TrueResult] }
] }, TS };
do_convert_to_boolean(Line, Expr, true, true) ->
do_guarded_convert_to_boolean(Line, Expr, 'andalso', '/=');
do_convert_to_boolean(Line, Expr, true, true, S) ->
{ do_guarded_convert_to_boolean(Line, Expr, 'andalso', '/='), S };
do_convert_to_boolean(Line, Expr, false, true) ->
do_guarded_convert_to_boolean(Line, Expr, 'orelse', '==').
do_convert_to_boolean(Line, Expr, false, true, S) ->
{ do_guarded_convert_to_boolean(Line, Expr, 'orelse', '=='), S }.
do_guarded_convert_to_boolean(Line, Expr, Op, Comp) ->
Left = { op, Line, Comp, Expr, { atom, Line, false } },
+18 -3
View File
@@ -1,5 +1,5 @@
-module(elixir_try).
-export([clauses/3]).
-export([clauses/3, format_error/1]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -86,6 +86,16 @@ normalize_rescue(_, { in, Line, [Left, Right] }, S) ->
end
end;
normalize_rescue(_, { '=', Line, [{ '__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 } }),
false;
normalize_rescue(_, { '=', Line, [{ 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 } }),
false;
normalize_rescue(Line, Condition, S) ->
case elixir_translator:translate_each(Condition, S#elixir_scope{context=assign}) of
{ { atom, _, Atom }, _ } ->
@@ -94,7 +104,6 @@ normalize_rescue(Line, Condition, S) ->
false
end.
%% Convert rescue clauses into guards.
rescue_guards(_, Var, nil, _) -> { Var, false };
@@ -240,7 +249,7 @@ validate_rescue_access(Line, { '=', _, [Left, Right] }, S) ->
validate_rescue_access(Line, Left, S),
validate_rescue_access(Line, Right, S);
validate_rescue_access(Line, { 'access', _, [Element, _] }, S) ->
validate_rescue_access(Line, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
case elixir_translator:translate_each(Element, S) of
{ { atom, _, Atom }, _ } ->
case lists:member(Atom, erlang_rescues()) of
@@ -254,6 +263,12 @@ validate_rescue_access(_, _, _) -> [].
%% Helpers
format_error({ rescue_no_match, Var, Alias }) ->
VarBinary = 'Elixir.Macro':to_binary(Var),
AliasBinary = 'Elixir.Macro':to_binary(Alias),
Message = "rescue clause (~s = ~s) can never match, maybe you meant to write: ~s in [~s] ?",
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
is_var(_) -> false.
+19 -3
View File
@@ -1,10 +1,16 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule Access.TupleTest do
defmodule AccessTest do
use ExUnit.Case, async: true
test :list do
assert [foo: :bar][:foo] == :bar
assert [foo: [bar: :baz]][:foo][:bar] == :baz
assert [foo: [bar: :baz]][:fuu][:bar] == nil
end
test :nil do
assert nil[:foo] == nil
end
test :function do
@@ -13,6 +19,16 @@ defmodule Access.TupleTest do
assert function[:bar] == false
end
# Test nil at compilation time does not fail
# and that @config[:foo] has proper precedence.
nil = @config[:foo]
@config [foo: :bar]
:bar = @config[:foo]
@mod :lists
[1,2,3] = @mod.flatten([1,[2],3])
test :atom do
exception = assert_raise RuntimeError, fn ->
foo = :foo
@@ -62,4 +78,4 @@ defmodule Access.RecordTest do
defp is_config(Config[]), do: true
defp is_config(_), do: false
end
end
+47
View File
@@ -0,0 +1,47 @@
Code.require_file "../test_helper.exs", __FILE__
defmodule BehaviourTest do
use ExUnit.Case, async: true
defmodule Sample do
use Behaviour
@doc "Foo"
defcallback foo(atom, binary) :: binary
@doc "Bar"
defcallback bar(External.hello, my_var :: binary) :: binary
end
test :docs do
docs = Enum.sort(Sample.__behaviour__(:docs))
assert docs == [
{{:bar,2},13,"Bar"},
{{:foo,2},10,"Foo"}
]
end
test :callbacks do
assert Sample.__behaviour__(:callbacks) == [foo: 2, bar: 2]
end
test :specs do
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 2
end
test :default_is_not_supported do
assert_raise ArgumentError, fn ->
defmodule WithDefault do
use Behaviour
defcallback hello(num // 0 :: integer) :: integer
end
end
assert_raise ArgumentError, fn ->
defmodule WithDefault do
use Behaviour
defcallback hello(num :: integer // 0) :: integer
end
end
end
end
+2 -2
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule Binary.Chars.AtomTest do
use ExUnit.Case, async: true
@@ -32,7 +32,7 @@ defmodule Binary.Chars.BitStringTest do
test :bitstring do
assert_raise FunctionClauseError, fn ->
to_binary(<<1|12-:integer-:signed>>)
to_binary(<<1 :: [size(12), integer, signed]>>)
end
end
+42 -8
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule Binary.Inspect.AtomTest do
use ExUnit.Case, async: true
@@ -50,7 +50,7 @@ defmodule Binary.Inspect.BitStringTest do
use ExUnit.Case, async: true
test :bitstring do
assert inspect(<<1|12-:integer-:signed>>) == "<<0,1|4>>"
assert inspect(<<1 :: [size(12), integer, signed]>>) == "<<0,1::size(4)>>"
end
test :binary do
@@ -68,7 +68,11 @@ defmodule Binary.Inspect.BitStringTest do
end
test :unprintable do
assert inspect(<<1>>) == "<<1>>"
assert inspect(<<193>>) == "<<193>>"
end
test :unprintable_with_opts do
assert inspect(<<193, 193, 193, 193>>, limit: 3) == "<<193,193,193,...>>"
end
end
@@ -80,9 +84,10 @@ defmodule Binary.Inspect.NumberTest do
end
test :float do
assert inspect(1.0) == "1.00000000000000000000e+00"
assert inspect(1.0e10) == "1.00000000000000000000e+10"
assert inspect(1.0e+10) == "1.00000000000000000000e+10"
assert inspect(1.0) == "1.0"
assert inspect(1.0e10) == "1.0e10"
assert inspect(1.0e+10) == "1.0e10"
assert inspect(1.0e-10) == "1.0e-10"
end
end
@@ -105,8 +110,14 @@ defmodule Binary.Inspect.TupleTest do
assert inspect({ List, 1 }) == "{List,1}"
end
test :with_record_like_pseudo_exception do
assert inspect({ Other, :__exception__, 1 }) == "{Other,:__exception__,1}"
end
defrecord Config, a: 1, b: []
test :with_record do
assert inspect(ExUnit.Server.Config.new) == "ExUnit.Server.Config[async_cases: [], options: [], sync_cases: []]"
assert inspect(Config.new) == "Binary.Inspect.TupleTest.Config[a: 1, b: []]"
end
test :with_tuple_matching_record_name_but_not_length do
@@ -120,6 +131,14 @@ defmodule Binary.Inspect.TupleTest do
test :empty do
assert inspect({}) == "{}"
end
test :with_limit do
assert inspect({ 1, 2, 3, 4 }, limit: 3) == "{1,2,3,...}"
end
test :with_raw do
assert inspect(Config.new, raw: true) == "{Binary.Inspect.TupleTest.Config,1,[]}"
end
end
defmodule Binary.Inspect.ListTest do
@@ -133,8 +152,19 @@ defmodule Binary.Inspect.ListTest do
assert inspect('abc') == "'abc'"
end
test :keyword do
assert inspect([a: 1]) == "[a: 1]"
assert inspect([a: 1, b: 2]) == "[a: 1, b: 2]"
assert inspect([a: 1, a: 2, b: 2]) == "[a: 1, a: 2, b: 2]"
assert inspect(["123": 1]) == %b(["123": 1])
end
test :non_keyword do
assert inspect([{ Regex, 1 }]) == "[{Regex,1}]"
end
test :non_printable do
assert inspect([{:a,1}]) == "[{:a,1}]"
assert inspect([{:b, 1},{:a,1}]) == "[b: 1, a: 1]"
end
test :unproper do
@@ -148,6 +178,10 @@ defmodule Binary.Inspect.ListTest do
test :empty do
assert inspect([]) == "[]"
end
test :with_limit do
assert inspect([ 1, 2, 3, 4 ], limit: 3) == "[1,2,3,...]"
end
end
defmodule Binary.Inspect.AnyTest do

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