Compare commits

...
352 Commits
Author SHA1 Message Date
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
215 changed files with 8007 additions and 3995 deletions
+1
View File
@@ -5,5 +5,6 @@ notifications:
recipients:
- jose.valim@plataformatec.com.br
otp_release:
- R15B02
- R15B01
- R15B
+91 -42
View File
@@ -1,52 +1,101 @@
# 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)
+14 -9
View File
@@ -1,9 +1,9 @@
REBAR:=$(shell which rebar || echo `pwd`/rebar)
REBAR:=$(shell echo `pwd`/rebar)
ELIXIRC:=bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC:=erlc -I lib/elixir/include
ERL:=erl -I lib/elixir/include -noshell -env ERL_LIBS $ERL_LIBS:lib
FULLFLAG:=.full
VERSION:=0.6.0
VERSION:=0.7.0
.PHONY: 1
.NOTPARALLEL: compile
@@ -21,7 +21,7 @@ lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs"
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
#==> Compilation tasks
@@ -38,7 +38,7 @@ erlang:
# 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
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
kernel: $(KERNEL)
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
@@ -55,11 +55,16 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
$(eval $(call TASK_TEMPLATE,ex_unit,ExUnit))
$(eval $(call TASK_TEMPLATE,eex,EEx))
$(eval $(call TASK_TEMPLATE,mix,Mix))
$(eval $(call TASK_TEMPLATE,iex,IEx))
clean:
@ rm -rf .full
@ rm -rf lib/*/ebin
@ cd lib/elixir && $(REBAR) clean
rm -rf .full
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/*)
@@ -93,11 +98,11 @@ 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";
+6 -4
View File
@@ -8,13 +8,15 @@ This document simply outlines the release process:
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 stable 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) After release, bump versions and add .dev back
## Places where version is mentioned
+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
+1 -1
View File
@@ -24,4 +24,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
"$SCRIPT_PATH"/elixir --compile "$@"
exec "$SCRIPT_PATH"/elixir --compile "$@"
+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 -e "IEx.cli" "$@"
+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
+3 -3
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
@@ -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 []
+4 -4
View File
@@ -18,10 +18,10 @@
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=[], %% a dict of all variables defined in a particular clause
quote_vars=[], %% a dict of all quoted variables
extra_guards=nil, %% extra guards from args expansion
counter=0, %% a counter for the variables defined
file=(<<"nofile">>), %% the current scope filename
+6 -7
View File
@@ -8,7 +8,7 @@ defprotocol Access do
invokes `Access.access` protocol.
This protocol is limited and is implemented only for the
following built-in types: keywords, tuples, atoms and
following built-in types: keywords, records, atoms and
functions.
"""
@@ -22,7 +22,7 @@ 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 +32,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 +43,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}"
+89
View File
@@ -0,0 +1,89 @@
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(arg)
@doc "Defines a default port"
defcallback default_port()
end
And then a specific protocol 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
Internally, Erlang call `behaviour_info(:callbacks)`
to obtain all functions that a behaviour should
implemented. Therefore, all this module does is
to define `behaviour_info(:callbacks)` with the
`defcallback` definitions.
"""
@doc """
Annotates the given function is a callback. `defcallback` is
slightly different than simple using `def` because, even if
`defcallback` contains default values, a default function
won't be generated, which would happen with `def`.
"""
defmacro defcallback(fun) do
{ name, args } = :elixir_clauses.extract_args(fun)
length = length(args)
{ args, defaults } = Enum.map_reduce(args, 0, function do
{ ://, _, [expr, _] }, acc ->
{ expr, acc + 1 }
expr, acc ->
{ expr, acc }
end)
attributes = Enum.map (length - defaults)..length, fn(i) ->
quote do
@__behaviour_callbacks { unquote(name), unquote(i) }
end
end
quote do
__block__ unquote(attributes)
def unquote(name)(unquote_splicing(args))
end
end
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :__behaviour_callbacks, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
end
end
@doc false
defmacro __before_compile__(_) do
quote do
@doc false
def behaviour_info(:callbacks) do
@__behaviour_callbacks
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
+81
View File
@@ -0,0 +1,81 @@
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 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
+238 -95
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,13 +114,14 @@ 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
@@ -51,7 +133,7 @@ defimpl Binary.Inspect, for: Atom do
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
":" <> Binary.escape(binary, ?")
":" <> escape(binary, ?")
end
end
@@ -66,7 +148,7 @@ defimpl Binary.Inspect, for: Atom do
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?-, h, t|:binary>>) when h in ?A..?Z do
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
@@ -75,7 +157,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 +168,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 +180,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 +192,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 +259,37 @@ 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), ?')
keywords?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
container_join(thing, "[", "]", opts)
end
end
## Helpers
## keywords?
defp keywords?([]), do: true
defp keywords?([{ key, _value } | rest]) when is_atom(key) do
keywords?(rest)
end
defp keywords?(_other), do: false
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 +307,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 +319,82 @@ 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
if name in [BitString, List, Tuple, Atom, Number, Any] do
record_inspect(tuple, opts)
else
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(exception, opts) when is_exception(exception) do
list = tuple_to_list(exception)
[name,_|tail] = list
[_|fields] = name.__record__(:fields)
record_join(name, fields, tail, opts)
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_inspect(record, opts) do
list = tuple_to_list(record)
[name|tail] = list
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
record_join(name, fields, tail, opts)
end
end
defp records_join([], [], acc, last) do
acc <> last
defp record_fields(name) do
try do
name.__record__(:fields)
rescue
_ -> nil
end
end
defp record_join(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
record_join(fields, tail, opts) <> "]"
end
defp record_join([f], [v], opts) do
atom_to_binary(f, :utf8) <> ": " <> 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 +402,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 +421,14 @@ 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(thing, _) do
"%r" <> Binary.Inspect.inspect(Regex.source(thing), []) <> Regex.opts(thing)
end
end
defimpl Binary.Inspect, for: Any do
@doc """
@moduledoc """
For all other terms not implemented, we use the default
Erlang representation.
@@ -295,7 +437,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
+100 -41
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,7 +40,15 @@ 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 """
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 """
@@ -49,7 +67,7 @@ defmodule Code do
#=> { 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:
the location which already returns both fields as a keyword list:
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
#=> { 3, [ {:a, 1}, {:b, 2} ] }
@@ -57,7 +75,7 @@ defmodule Code do
"""
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
@@ -81,16 +99,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 also pass the current __ENV__ and
the proper information will be extracted:
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.location)
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 +129,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 +150,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 +164,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 +246,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 +268,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 +288,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,9 +313,9 @@ 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
@@ -283,6 +329,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.
@@ -301,6 +355,7 @@ defmodule Code do
else
prefix = "#{file}.exs"
if File.regular?(prefix) do
IO.write "[WARNING] Passing a file without .exs extension to Code.load_file or Code.require_file is deprecated, please pass the full name instead\n#{Exception.formatted_stacktrace}"
prefix
else
raise ArgumentError, message: "could not load #{file}"
@@ -309,6 +364,10 @@ defmodule Code do
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
+107 -39
View File
@@ -1,12 +1,38 @@
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.
"""
@doc false
def behaviour_info(:callbacks) do
[ delete: 2, empty: 1, get: 3, has_key?: 2,
keys: 1, merge: 3, put: 3, size: 1, to_list: 1,
update: 3, update: 4, values: 1 ]
end
@doc """
Returns a list containing all dict's keys.
The keys are not guaranteed to be sorted, unless
@@ -14,41 +40,53 @@ 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)
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)
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)
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)
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 +94,15 @@ 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)
def get(dict, key, default // nil) do
elem(dict, 0).get(dict, key, default)
end
@doc """
Stores the given `value` under `key` in `dict`.
@@ -70,11 +110,14 @@ 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)
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 +125,34 @@ 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)
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)
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 +160,17 @@ 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)
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 +178,14 @@ 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)
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 +194,27 @@ 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)
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)
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
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
+268 -170
View File
@@ -52,45 +52,8 @@ defprotocol Enum.Iterator do
def count(collection)
end
defprotocol Enum.OrdIterator do
@moduledoc """
This protocol is invoked by some functions in Enum which
requires an ordered collection to function correctly. For
instance, `Enum.split_with/2`, `Enum.take_while` all rely
on this protocol.
An ordered collection does not mean the items are ordered
according to the Elixir ordering but simply that any two
distinct instances of the same collection with exactly
the same items always yield the same order when iterated.
"""
@only [List, Record]
@doc """
Must return a tuple under the same conditions as
`Enum.Iterator.iterator`.
"""
def iterator(collection)
@doc """
On each step, the iterator function returned by `iterator/1`
returns a tuple with two elements. This function receives
those two elements as a tuple and must return a list back.
This is used in order to quicky return a list from any point
during iteration. For example, consider the function `Enum.drop`.
`Enum.drop collection, 3` should drop 3 items and return a list
back. While we could loop over the remaining items to get a list
back, this function is invoked allowing us to get a result
back without a need to loop the remaining items.
"""
def to_list({ current, next }, iterator)
end
defmodule Enum do
require Enum.Iterator, as: I
require Enum.OrdIterator, as: O
alias Enum.Iterator, as: I
@moduledoc """
Provides a set of algorithms that enumerate over collections according to the
@@ -174,6 +137,33 @@ defmodule Enum do
end
end
@doc """
Finds the element at the given index (zero-based).
Raises out of bounds error in case the given position
is outside the range of the collection.
Expects an ordered collection.
## Examples
Enum.at! [2,4,6], 0 #=> 2
Enum.at! [2,4,6], 2 #=> 6
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
"""
def at!(collection, n) when is_list(collection) and n >= 0 do
do_at!(collection, n)
end
def at!(collection, n) when n >= 0 do
case I.iterator(collection) do
{ iterator, pointer } ->
do_at!(pointer, iterator, n)
list when is_list(list) ->
do_at!(list, n)
end
end
@doc """
Returns the collection size.
@@ -186,6 +176,22 @@ defmodule Enum do
I.count(collection)
end
@doc """
Counts for how many items the function returns true.
"""
def count(collection, fun) when is_list(collection) do
do_count(collection, fun)
end
def count(collection, fun) do
case I.iterator(collection) do
{ iterator, pointer } ->
do_count(pointer, iterator, fun)
list when is_list(list) ->
do_count(list, fun)
end
end
@doc """
Drops the first `count` items from the collection. Expects an ordered
collection.
@@ -198,7 +204,7 @@ defmodule Enum do
"""
def drop(collection, count) do
elem split(collection, count), 2
elem split(collection, count), 1
end
@doc """
@@ -215,10 +221,9 @@ defmodule Enum do
end
def drop_while(collection, fun) do
case O.iterator(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
module = O.__impl_for__!(collection)
do_drop_while(pointer, iterator, fun, module)
do_drop_while(pointer, iterator, fun)
list when is_list(list) ->
do_drop_while(list, fun)
end
@@ -263,7 +268,7 @@ defmodule Enum do
def empty?(collection) do
case I.iterator(collection) do
{ _iterator, pointer } -> pointer === :stop
{ _iterator, pointer } -> pointer == :stop
list when is_list(list) -> list == []
end
end
@@ -348,13 +353,13 @@ defmodule Enum do
Similar to find, but returns the value of the function
invocation instead of the element itself.
## Examples
## Examples
Enum.find_value [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> nil
Enum.find_value [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> nil
Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> true
Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> true
"""
def find_value(collection, ifnone // nil, fun)
@@ -373,30 +378,30 @@ defmodule Enum do
end
@doc """
Similar to find, but returns the index (count starts with 1)
Similar to find, but returns the index (count starts with 0)
of the item instead of the element itself.
Expects an ordered collection.
## Examples
## Examples
Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> nil
Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> nil
Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> 2
Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> 2
"""
def find_index(collection, fun) when is_list(collection) do
do_find_index(collection, 1, fun)
do_find_index(collection, 0, fun)
end
def find_index(collection, fun) do
case O.iterator(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
do_find_index(pointer, iterator, 1, fun)
do_find_index(pointer, iterator, 0, fun)
list when is_list(list) ->
do_find_index(list, 1, fun)
do_find_index(list, 0, fun)
end
end
@@ -413,7 +418,7 @@ defmodule Enum do
def first([h|_]), do: h
def first(collection) do
case O.iterator(collection) do
case I.iterator(collection) do
{ _iterator, { h, _ } } -> h
{ _iterator, :stop } -> nil
list when is_list(list) -> first(list)
@@ -547,32 +552,6 @@ defmodule Enum do
end
end
@doc """
Finds the element at the nth index. Returns nil in case
the given index is outside the range of the collection.
Expects an ordered collection.
## Examples
Enum.nth! [2,4,6], 1 #=> 2
Enum.nth! [2,4,6], 3 #=> 6
Enum.nth! [2,4,6], 5 #=> raises Enum.OutOfBoundsError
"""
def nth!(collection, n) when is_list(collection) and n > 0 do
do_nth!(collection, n)
end
def nth!(collection, n) when n > 0 do
case O.iterator(collection) do
{ iterator, pointer } ->
do_nth!(pointer, iterator, n)
list when is_list(list) ->
do_nth!(list, n)
end
end
@doc """
Partitions `collection` into two where the first one contains elements
for which `fun` returns a truthy value, and the second one -- for which `fun`
@@ -598,8 +577,8 @@ defmodule Enum do
end
@doc """
Invokes `fun` for each element in the collection passing the accumulator
`acc` and the element as arguments. The return value is stored in `acc`.
Invokes `fun` for each element in the collection passing that element and the
accumulator `acc` as arguments. `fun`'s return value is stored in `acc`.
Returns the accumulator.
## Examples
@@ -621,6 +600,28 @@ defmodule Enum do
end
end
@doc """
Reverses the collection.
Expects an ordered collection.
## Examples
Enum.reverse [1, 2, 3]
#=> [3, 2, 1]
"""
def reverse(collection) when is_list(collection) do
:lists.reverse(collection)
end
def reverse(collection) do
case I.iterator(collection) do
{ iterator, pointer } -> do_reverse(pointer, iterator, [])
list when is_list(list) -> reverse(list)
end
end
@doc """
Sorts the collection according to the quick sort algorithm.
@@ -644,13 +645,18 @@ defmodule Enum do
@doc """
Splits the enumerable into two collections, leaving `count` elements in the
first one. Expects an ordered collection.
first one. If `count` is a negative number, it starts couting from the back
to the beginning of the collection. Be aware that a negative `count`
implies in an iteration through the whole collection.
Expects an ordered collection.
## Examples
Enum.split [1,2,3], 2 #=> { [1,2], [3] }
Enum.split [1,2,3], 10 #=> { [1,2,3], [] }
Enum.split [1,2,3], 0 #=> { [], [1,2,3] }
Enum.split [1,2,3], -1 #=> { [1,2], [3] }
Enum.split [1,2,3], -5 #=> { [], [1,2,3] }
"""
def split(collection, count) when is_list(collection) and count >= 0 do
@@ -658,35 +664,46 @@ defmodule Enum do
end
def split(collection, count) when count >= 0 do
case O.iterator(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
module = O.__impl_for__!(collection)
do_split(pointer, iterator, count, [], module)
do_split(pointer, iterator, count, [])
list when is_list(list) ->
do_split(list, count, [])
end
end
def split(collection, count) when count < 0 do
reducer = fn(x, acc) -> {x, acc + 1} end
{ list , total_items } = Enum.map_reduce(collection, 0, reducer)
real_count = max(0, total_items - abs(count))
split(list, real_count)
end
@doc false
def split_with(collection, fun) do
IO.write "[WARNING] Enum.split_with is deprecated, please use split_while instead\n#{Exception.formatted_stacktrace}"
split_while(collection, fun)
end
@doc """
Splits `collection` at the first element, for which `fun` returns true.
Expects an ordered collection.
## Examples
Enum.split_with [1,2,3,4], fn x -> x == 2 end
Enum.split_while [1,2,3,4], fn x -> x == 2 end
#=> { [1], [2, 3, 4] }
"""
def split_with(collection, fun) when is_list(collection) do
do_split_with(collection, fun, [])
def split_while(collection, fun) when is_list(collection) do
do_split_while(collection, fun, [])
end
def split_with(collection, fun) do
case O.iterator(collection) do
def split_while(collection, fun) do
case I.iterator(collection) do
{ iterator, pointer } ->
module = O.__impl_for__!(collection)
do_split_with(pointer, iterator, fun, [], module)
do_split_while(pointer, iterator, fun, [])
list when is_list(list) ->
do_split_with(list, fun, [])
do_split_while(list, fun, [])
end
end
@@ -702,7 +719,7 @@ defmodule Enum do
"""
def take(collection, count) do
elem split(collection, count), 1
elem split(collection, count), 0
end
@doc """
@@ -720,7 +737,7 @@ defmodule Enum do
end
def take_while(collection, fun) do
case O.iterator(collection) do
case I.iterator(collection) do
{ iterator, pointer } ->
do_take_while(pointer, iterator, fun)
list when is_list(list) ->
@@ -729,15 +746,15 @@ defmodule Enum do
end
@doc """
Iterates the given function n times, passing values from 1
to n.
Iterates the given function n times, passing values
from 0 to n - 1.
## Examples
Enum.times 3, fn(x) -> IO.inspect x end
0
1
2
3
"""
def times(times, function) when times >= 0 do
@@ -765,6 +782,38 @@ defmodule Enum do
do_times_2(times, 1, function, acc)
end
@doc """
Zips corresponding elements from two collections into one list
of tuples. The number of elements in the resulting list is
dictated by the first enum. In case the second list is shorter,
values are filled with nil.
"""
def zip(coll1, coll2) when is_list(coll1) do
do_zip(coll1, iterator(coll2))
end
def zip(coll1, coll2) do
case I.iterator(coll1) do
{ iterator, pointer } ->
do_zip(pointer, iterator, iterator(coll2))
list when is_list(list) ->
do_zip(list, iterator(coll2))
end
end
## Helpers
defp iterator(collection) when is_list(collection), do: collection
defp iterator(collection), do: I.iterator(collection)
defp to_list({ h, next }, iterator) do
[h|to_list(iterator.(next), iterator)]
end
defp to_list(:stop, _) do
[]
end
## Implementations
## all?
@@ -819,6 +868,42 @@ defmodule Enum do
false
end
## at!
defp do_at!([h|_], 0), do: h
defp do_at!([_|t], n), do: do_at!(t, n - 1)
defp do_at!([], _), do: raise Enum.OutOfBoundsError
defp do_at!({ h, _next }, _iterator, 0), do: h
defp do_at!({ _, next }, iterator, n), do: do_at!(iterator.(next), iterator, n - 1)
defp do_at!(:stop, _iterator, _), do: raise Enum.OutOfBoundsError
## count
defp do_count([h|t], fun) do
if fun.(h) do
1 + do_count(t, fun)
else
do_count(t, fun)
end
end
defp do_count([], _) do
0
end
defp do_count({ h, next }, iterator, fun) do
if fun.(h) do
1 + do_count(iterator.(next), iterator, fun)
else
do_count(iterator.(next), iterator, fun)
end
end
defp do_count(:stop, _, _) do
0
end
## drop_while
defp do_drop_while([h|t], fun) do
@@ -833,15 +918,15 @@ defmodule Enum do
[]
end
defp do_drop_while({ h, next } = extra, iterator, fun, module) do
defp do_drop_while({ h, next } = extra, iterator, fun) do
if fun.(h) do
do_drop_while(iterator.(next), iterator, fun, module)
do_drop_while(iterator.(next), iterator, fun)
else
module.to_list(extra, iterator)
to_list(extra, iterator)
end
end
defp do_drop_while(:stop, _, _, _) do
defp do_drop_while(:stop, _, _) do
[]
end
@@ -954,16 +1039,6 @@ defmodule Enum do
[]
end
## nth
defp do_nth!([h|_], 1), do: h
defp do_nth!([_|t], n), do: do_nth!(t, n - 1)
defp do_nth!([], _), do: raise Enum.OutOfBoundsError
defp do_nth!({ h, _next }, _iterator, 1), do: h
defp do_nth!({ _, next }, iterator, n), do: do_nth!(iterator.(next), iterator, n - 1)
defp do_nth!(:stop, _iterator, _), do: raise Enum.OutOfBoundsError
## reduce
defp do_reduce({ h, next }, iterator, acc, fun) do
@@ -974,30 +1049,30 @@ defmodule Enum do
acc
end
## split_with
## split_while
defp do_split_with([h|t], fun, acc) do
defp do_split_while([h|t], fun, acc) do
if fun.(h) do
do_split_with(t, fun, [h|acc])
do_split_while(t, fun, [h|acc])
else
{ List.reverse(acc), [h|t] }
{ :lists.reverse(acc), [h|t] }
end
end
defp do_split_with([], _, acc) do
{ List.reverse(acc), [] }
defp do_split_while([], _, acc) do
{ :lists.reverse(acc), [] }
end
defp do_split_with({ h, next } = extra, iterator, fun, acc, module) do
defp do_split_while({ h, next } = extra, iterator, fun, acc) do
if fun.(h) do
do_split_with(iterator.(next), iterator, fun, [h|acc], module)
do_split_while(iterator.(next), iterator, fun, [h|acc])
else
{ List.reverse(acc), module.to_list(extra, iterator) }
{ :lists.reverse(acc), to_list(extra, iterator) }
end
end
defp do_split_with(:stop, _, _, acc, _module) do
{ List.reverse(acc), [] }
defp do_split_while(:stop, _, _, acc) do
{ :lists.reverse(acc), [] }
end
## join
@@ -1007,7 +1082,7 @@ defmodule Enum do
end
defp do_join([h|t], joiner, acc) do
acc = << acc | :binary, joiner | :binary, to_binary(h) | :binary >>
acc = << acc :: binary, joiner :: binary, to_binary(h) :: binary >>
do_join(t, joiner, acc)
end
@@ -1020,7 +1095,7 @@ defmodule Enum do
end
defp do_join({ h, next }, iterator, joiner, acc) do
acc = << acc | :binary, joiner | :binary, to_binary(h) | :binary >>
acc = << acc :: binary, joiner :: binary, to_binary(h) :: binary >>
do_join(iterator.(next), iterator, joiner, acc)
end
@@ -1035,7 +1110,7 @@ defmodule Enum do
end
defp do_map_join([h|t], mapper, joiner, acc) do
acc = << acc | :binary, joiner | :binary, to_binary(mapper.(h)) | :binary >>
acc = << acc :: binary, joiner :: binary, to_binary(mapper.(h)) :: binary >>
do_map_join(t, mapper, joiner, acc)
end
@@ -1048,7 +1123,7 @@ defmodule Enum do
end
defp do_map_join({ h, next }, iterator, mapper, joiner, acc) do
acc = << acc | :binary, joiner | :binary, to_binary(mapper.(h)) | :binary >>
acc = << acc :: binary, joiner :: binary, to_binary(mapper.(h)) :: binary >>
do_map_join(iterator.(next), iterator, mapper, joiner, acc)
end
@@ -1074,7 +1149,7 @@ defmodule Enum do
end
defp do_map_reduce(:stop, _, list_acc, acc, _f) do
{ List.reverse(list_acc), acc }
{ :lists.reverse(list_acc), acc }
end
## partition
@@ -1088,7 +1163,7 @@ defmodule Enum do
end
defp do_partition([], _, acc1, acc2) do
{ List.reverse(acc1), List.reverse(acc2) }
{ :lists.reverse(acc1), :lists.reverse(acc2) }
end
defp do_partition({ h, next }, iterator, fun, acc1, acc2) do
@@ -1100,7 +1175,17 @@ defmodule Enum do
end
defp do_partition(:stop, _, _, acc1, acc2) do
{ List.reverse(acc1), List.reverse(acc2) }
{ :lists.reverse(acc1), :lists.reverse(acc2) }
end
## reverse
defp do_reverse({ h, next }, iterator, acc) do
do_reverse(iterator.(next), iterator, [h|acc])
end
defp do_reverse(:stop, _, acc) do
acc
end
## qsort (lists)
@@ -1160,23 +1245,23 @@ defmodule Enum do
end
defp do_split(list, 0, acc) do
{ List.reverse(acc), list }
{ :lists.reverse(acc), list }
end
defp do_split([], _, acc) do
{ List.reverse(acc), [] }
{ :lists.reverse(acc), [] }
end
defp do_split({ h, next }, iterator, counter, acc, module) when counter > 0 do
do_split(iterator.(next), iterator, counter - 1, [h|acc], module)
defp do_split({ h, next }, iterator, counter, acc) when counter > 0 do
do_split(iterator.(next), iterator, counter - 1, [h|acc])
end
defp do_split(extra, iterator, 0, acc, module) do
{ List.reverse(acc), module.to_list(extra, iterator) }
defp do_split(extra, iterator, 0, acc) do
{ :lists.reverse(acc), to_list(extra, iterator) }
end
defp do_split(:stop, _, _, acc, _module) do
{ List.reverse(acc), [] }
defp do_split(:stop, _, _, acc) do
{ :lists.reverse(acc), [] }
end
## take_while
@@ -1207,7 +1292,7 @@ defmodule Enum do
## times
defp do_times_0(limit, counter, _function) when counter > limit do
defp do_times_0(limit, counter, _function) when counter >= limit do
end
defp do_times_0(limit, counter, function) do
@@ -1215,7 +1300,7 @@ defmodule Enum do
do_times_0(limit, 1 + counter, function)
end
defp do_times_1(limit, counter, _function) when counter > limit do
defp do_times_1(limit, counter, _function) when counter >= limit do
end
defp do_times_1(limit, counter, function) do
@@ -1223,7 +1308,7 @@ defmodule Enum do
do_times_1(limit, 1 + counter, function)
end
defp do_times_2(limit, counter, _function, acc) when counter > limit do
defp do_times_2(limit, counter, _function, acc) when counter >= limit do
acc
end
@@ -1231,25 +1316,52 @@ defmodule Enum do
new_acc = function.(counter, acc)
do_times_2(limit, 1 + counter, function, new_acc)
end
## zip
defp do_zip([h1|next1], other) do
{ h2, next2 } = do_zip_next(other)
[{ h1, h2 }|do_zip(next1, next2)]
end
defp do_zip([], _) do
[]
end
defp do_zip({ h1, next1 }, iterator, other) do
{ h2, next2 } = do_zip_next(other)
[{ h1, h2 }|do_zip(iterator.(next1), iterator, next2)]
end
defp do_zip(:stop, _, _) do
[]
end
defp do_zip_next([h|t]), do: { h, t }
defp do_zip_next([]), do: { nil, [] }
defp do_zip_next({ iterator, { h, next } }) do
{ h, { iterator, iterator.(next) } }
end
defp do_zip_next({ _iterator, :stop } = i) do
{ nil, i }
end
end
defimpl Enum.Iterator, for: List do
def iterator(list), do: list
def count(list), do: length(list)
end
defimpl Enum.OrdIterator, for: List do
def iterator(list), do: list
def to_list({ h, next }, _), do: [h|next]
def iterator(list), do: list
def count(list), do: length(list)
end
defimpl Enum.Iterator, for: Function do
def iterator(function) do
{ function, function.(:start) }
function.()
end
def count(function) do
do_count(function.(:start), function, 0)
{ function, first } = function.()
do_count(first, function, 0)
end
defp do_count({ _, next }, function, acc) do
@@ -1260,17 +1372,3 @@ defimpl Enum.Iterator, for: Function do
acc
end
end
defimpl Enum.OrdIterator, for: Function do
def iterator(function) do
{ function, function.(:start) }
end
def to_list({ h, next }, function) do
[h|to_list(function.(next), function)]
end
def to_list(:stop, _function) do
[]
end
end
+17 -4
View File
@@ -60,7 +60,7 @@ defmodule Exception do
@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,16 @@ defmodule Exception do
end
end
@doc """
Returns the stacktrace as a binary formatted as per `format_stacktrace/1`.
"""
def formatted_stacktrace(trace // System.stacktrace) do
case trace do
[] -> ""
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace(&1)) <> "\n"
end
end
@doc """
Formats each line in the stacktrace.
"""
@@ -89,7 +102,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))
@@ -210,4 +223,4 @@ defexception Keyword.KeyError, key: nil do
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.OutOfBoundsError, message: "out of bounds error"
+77 -60
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
@@ -85,11 +87,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 +392,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 +463,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 +494,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 +511,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 +735,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
@@ -1017,9 +1023,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
@@ -1146,7 +1152,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,19 +1164,30 @@ 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 ->
{ strip_ending(data), :ok }
end
end
{ function, function.(:start) }
end
end
defp strip_ending("\r\n"), do: <<>>
defp strip_ending("\n"), do: <<>>
defp strip_ending(""), do: <<>>
defp strip_ending(<< h, t :: binary >>) do
<< h, strip_ending(t) :: binary >>
end
@doc """
Opens the given `file` with the given `mode` and
returns its iterator. Fails for the same reasons
@@ -1209,7 +1226,7 @@ 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
-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
+88
View File
@@ -0,0 +1,88 @@
defmodule GenServer.Behavior do
@moduledoc false
defmacro __using__(_) do
IO.puts "Using GenServer.Behavior is deprecated, use GenServer.Behaviour instead"
quote do
use GenServer.Behaviour
end
end
end
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
+37 -21
View File
@@ -1,81 +1,97 @@
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 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
-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
+20 -17
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 is 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,7 +35,7 @@ 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 """
@@ -45,7 +53,7 @@ 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 """
@@ -66,12 +74,7 @@ defmodule IO do
"""
def write(device // :stdio, item) do
Erlang.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)
:io.put_chars map_dev(device), to_iodata(item)
end
@doc """
@@ -81,16 +84,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 +109,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 +125,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
+507 -212
View File
File diff suppressed because it is too large Load Diff
+12 -16
View File
@@ -4,17 +4,15 @@ defrecord Kernel.CLI.Config, commands: [], output: ".",
defmodule Kernel.CLI do
@moduledoc false
import Exception, only: [format_stacktrace: 1]
# 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)
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)
all_commands = Enum.reverse(config.commands)
try do
Enum.map all_commands, process_command(&1, config)
@@ -25,9 +23,9 @@ defmodule Kernel.CLI do
rescue
exception ->
at_exit(1)
stacktrace = System.stacktrace
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace(stacktrace)
IO.puts Exception.formatted_stacktrace(trace)
halt(1)
catch
:exit, reason when is_integer(reason) ->
@@ -38,9 +36,9 @@ defmodule Kernel.CLI do
halt(0)
kind, reason ->
at_exit(1)
stacktrace = System.stacktrace
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
print_stacktrace(stacktrace)
IO.puts Exception.formatted_stacktrace(trace)
halt(1)
end
end
@@ -48,18 +46,20 @@ defmodule Kernel.CLI do
## Private
defp at_exit(status) do
hooks = Erlang.gen_server.call(:elixir_code_server, :at_exit)
hooks = :gen_server.call(:elixir_code_server, :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
end
@@ -78,10 +78,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
@@ -202,7 +198,7 @@ defmodule Kernel.CLI do
# 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
+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
+10 -9
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.
@@ -43,13 +43,14 @@ defmodule Kernel.ParallelCompiler do
child = spawn_link fn ->
Process.put(:elixir_compiler_pid, parent)
Process.put(:elixir_ensure_compiled, true)
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 +81,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
+2 -2
View File
@@ -28,7 +28,7 @@ defmodule Kernel.ParallelRequire do
child = spawn_link fn ->
try do
new = Code.require_file(h)
result = new ++ result
result = (new || []) ++ result
callback.(h)
parent <- { :required, self }
catch
@@ -45,7 +45,7 @@ defmodule Kernel.ParallelRequire do
{ :required, child } ->
spawn_requires(files, List.delete(waiting, child), callback, result)
{ :failure, _child, kind, reason, stacktrace } ->
Erlang.erlang.raise(kind, reason, stacktrace)
:erlang.raise(kind, reason, stacktrace)
end
end
end
+37 -29
View File
@@ -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 functions or only
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
@@ -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)
@@ -432,7 +440,7 @@ defmodule Kernel.SpecialForms do
it belonged to another file.
quote location: :keep, do: 1
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
#=> { :__scope__, 1,[[file: "iex"], 1] }
Check `quote/1` for more information.
"""
+7 -6
View File
@@ -29,7 +29,7 @@ defmodule Kernel.Typespec do
to get such information.
"""
def get_types(module) do
Module.read_attribute(module, :type) ++ Module.read_attribute(module, :opaque)
Module.get_attribute(module, :type) ++ Module.get_attribute(module, :opaque)
end
@doc """
@@ -48,7 +48,7 @@ defmodule Kernel.Typespec do
# Handle unions
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
exprs = List.reverse(collect_union(exprs))
exprs = Enum.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line, :union, union }
end
@@ -79,7 +79,7 @@ defmodule Kernel.Typespec do
# 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
@@ -113,8 +113,8 @@ defmodule Kernel.Typespec do
# 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)
case Enum.reverse(arguments) do
[[{:do,h}]|t] -> fn_args(line, Enum.reverse(t), h, vars, caller)
[] -> []
_ -> [fn_args(line, arguments, vars, caller)]
end
@@ -220,6 +220,7 @@ defmodule Kernel.Typespec do
end
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
if is_atom(args), do: args = []
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
code = Macro.escape { {type, { name, length(args) }}, [spec] }
table = spec_table_for(caller.module)
@@ -232,7 +233,7 @@ defmodule Kernel.Typespec do
end
defp spec_table_for(module) do
table = list_to_atom Erlang.lists.concat([:s, module])
table = list_to_atom :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
+124 -64
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,22 +11,23 @@ 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.
"""
@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.
"""
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.
Returns an empty keyword list, i.e. an empty list.
"""
def new do
[]
@@ -44,7 +44,7 @@ defmodule Keyword do
"""
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,6 +58,7 @@ defmodule Keyword do
Keyword.new [:a, :b], fn x -> {x,x} end
#=> [a: :a, b: :b]
"""
def new(pairs, transform) do
Enum.reduce pairs, [], fn i, keywords ->
@@ -80,12 +81,14 @@ 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
def get(keywords, key, default // nil) 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 +98,14 @@ 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)
def get!(keywords, key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(Keyword.KeyError, key: key)
end
end
@doc """
Gets all values for a specific key.
@@ -110,13 +116,12 @@ 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: []
def get_values(keywords, 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
@@ -134,14 +139,15 @@ defmodule Keyword do
## Examples
Keyword.values [a: 1, b: 2] #=> [1,2]
"""
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 +155,11 @@ 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: []
def delete(keywords, key) do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
end
@doc """
Sets the given `value` under `key`.
@@ -161,45 +167,46 @@ 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
[{key, value}|delete(keywords, key)]
end
def put([], key, value) when is_atom(key) do
[{key, value}]
def put(list, key, value) do
[{key, value}|delete(list, key)]
end
@doc """
Merges two keywords lists into one. If they have duplicated
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
"""
def equal?(left, right) do
:lists.sort(left) == :lists.sort(right)
end
@doc """
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]
"""
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 +215,86 @@ 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)]
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 false
def key?(list, key) do
IO.write "[WARNING] Keyword.key? is deprecated, please use Keyword.has_key? instead\n#{Exception.formatted_stacktrace}"
has_key?(list, key)
end
@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
def has_key?(keywords, key) do
:lists.keymember(key, 1, keywords)
end
@doc """
Updates the key with the given function. If the key does
not exist, raises `Keyword.KeyError`.
## Examples
Keyword.update([a: 1], :a, &1 * 2)
#=> [a: 2]
Keyword.update([a: 1], :b, &1 * 2)
#=> Keyword.KeyError
"""
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(Keyword.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]
"""
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
+77 -67
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,20 +120,12 @@ defmodule List do
"""
def foldr(list, acc, function) when is_list(list) and is_function(function) do
Erlang.lists.foldr(function, acc, list)
: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]
"""
@doc false
def reverse(list) do
IO.write "[WARNING] List.reverse/1 is deprecated, please use Enum.reverse/1 instead\n#{Exception.formatted_stacktrace}"
:lists.reverse(list)
end
@@ -156,7 +163,7 @@ defmodule List do
"""
def member?(list, term) do
Erlang.lists.member(term, list)
:lists.member(term, list)
end
@doc """
@@ -166,18 +173,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 +194,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,18 +244,18 @@ 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 """
@@ -243,22 +279,22 @@ defmodule List do
def range(first, last, step) when is_integer(first) and is_integer(last) and first <= last do
step = 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
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
@@ -303,21 +339,6 @@ defmodule List do
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,21 +361,9 @@ 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}]
"""
@doc false
def zip(item1, item2) do
IO.write "[WARNING] List.zip/2 is deprecated, please use Enum.zip/2 instead\n#{Exception.formatted_stacktrace}"
do_zip(to_list(item1), to_list(item2), [])
end
@@ -370,6 +379,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,7 +406,7 @@ defmodule List do
# uniq
defp do_uniq([h|t], acc) do
case Erlang.lists.member(h, acc) do
case :lists.member(h, acc) do
true ->
do_uniq(t, acc)
false ->
@@ -415,7 +425,7 @@ defmodule List do
end
defp do_zip(_, _, acc) do
reverse acc
:lists.reverse acc
end
defp do_zip(list, acc) do
+132 -13
View File
@@ -56,6 +56,92 @@ 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.
## 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 method 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.
@@ -124,7 +210,7 @@ defmodule Macro do
# 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,12 +228,14 @@ 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?([_|_] = kw) do
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
end
defp is_kw_blocks?(_), do: false
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
@@ -162,7 +250,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
@@ -198,7 +286,7 @@ defmodule Macro do
<< case x == ?\n do
true -> replacement
false -> <<x>>
end | :binary >>
end :: binary >>
end
end
@@ -300,19 +388,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 +425,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 +441,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 +461,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
+28 -45
View File
@@ -1,90 +1,73 @@
# 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 """
Record.deffunctions([:module, :file, :line, :function,
:aliases, :context, :requires, :macros], __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 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
+143 -79
View File
@@ -1,5 +1,5 @@
defmodule Module do
require Erlang.ets, as: ETS
require :ets, as: ETS
@moduledoc """
This module provides many functions to deal with modules during
@@ -12,6 +12,15 @@ 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.
Raises an error if the module was already compiled.
@@ -36,7 +45,8 @@ defmodule Module do
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:
is useful to evalute the quoted contents inside an existing
environment (considering the environemnt module, line and file):
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
@@ -54,15 +64,48 @@ defmodule Module do
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__)
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 `defmodule`
blends into the scope it is invoked, allowing you
to access all variables, imports and requires from
the module. `Module.create`, on the other hand, creates
a new scope so imports, requires, etc are not inherited.
"""
def create(module, quoted, opts // [])
def create(module, quoted, Macro.Env[] = env) do
create(module, quoted, env.location)
end
def create(module, quoted, opts) when is_atom(module) do
line = opts[:line] || 1
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
end
@doc """
@@ -77,7 +120,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 +135,7 @@ defmodule Module do
"""
def concat(left, right) do
Erlang.elixir_aliases.concat([left, right])
:elixir_aliases.concat([left, right])
end
@doc """
@@ -111,7 +154,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 +173,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 +196,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, :defcallback] 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 +204,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,
merge_kind(old_kind, kind),
merge_signatures(old_sign, signature, 1),
doc || old_doc
})
:ok
_ ->
{ :error, :existing_doc }
@@ -198,7 +247,11 @@ 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_kind(:defcallback, new), do: new
defp merge_kind(old, :defcallback), do: old
defp merge_kind(_old, new), do: new
defp merge_signatures([h1|t1], [h2|t2], i) do
[merge_signature(h1, h2, i)|merge_signatures(t1, t2, i + 1)]
@@ -268,30 +321,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 +370,11 @@ defmodule Module do
[clause] ->
ETS.delete(table, tuple)
old = Module.read_attribute(module, :__overridable)
old = get_attribute(module, :__overridable)
new = [ { tuple, { 1, [clause] } } ]
merged = :orddict.merge(fn(_k, { count, v1 }, _v2) -> { count + 1, [clause|v1] } 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 +382,28 @@ 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
key = List.keyfind(get_attribute(module, :__overridable), tuple, 0)
match? { _, { _, [_|_] } }, key
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)
@@ -391,25 +421,31 @@ defmodule Module do
ETS.insert(table, { key, new })
end
@doc false
def add_attribute(module, key, value) when is_atom(key) do
IO.write "[WARNING] Module.add_attribute is deprecated, please use Module.put_attribute instead\n#{Exception.formatted_stacktrace}"
put_attribute(module, key, value)
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
@@ -420,13 +456,19 @@ defmodule Module do
end
end
@doc false
def read_attribute(module, key) when is_atom(key) do
IO.write "[WARNING] Module.read_attribute is deprecated, please use Module.get_attribute instead\n#{Exception.formatted_stacktrace}"
get_attribute(module, key)
end
@doc """
Deletes all attributes that matches the given key.
## 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 +496,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 +526,47 @@ 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(to_binary(module), "-"))
end
@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)
kind = if kind == :def and body == nil and module != Kernel, do: :defcallback, else: kind
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)
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
new =
@@ -525,8 +593,9 @@ defmodule Module do
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 +607,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
+2 -2
View File
@@ -85,7 +85,7 @@ defmodule OptionParser do
end
defp parse([], _, _, dict, args, true) do
{ dict, List.reverse(args) }
{ dict, Enum.reverse(args) }
end
defp parse([h|t], aliases, flags, dict, args, true) do
@@ -111,7 +111,7 @@ defmodule OptionParser do
Keyword.put dict, option, value
end
defp normalize_option(<<?-, option|:binary>>, aliases) do
defp normalize_option(<<?-, option :: binary>>, aliases) do
normalize_option(option, aliases)
end
@@ -1,84 +1,95 @@
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 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
+28 -3
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()
@@ -224,7 +224,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 +244,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 +290,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.
+71 -58
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,8 +21,7 @@ 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: [
defmacro: 1, defmacro: 2, defmacro: 4,
defp: 1, defp: 2, defp: 4,
@@ -40,8 +39,8 @@ defmodule Protocol do
# Define callbacks and meta information
{ conversions, fallback } = Protocol.conversions_for(__MODULE__, @only, @except)
Protocol.impl_for(__ENV__, conversions)
Protocol.meta(__ENV__, @functions, fallback)
Protocol.impl_for(conversions, fallback, __ENV__)
Protocol.meta(@functions, fallback, __ENV__)
end
end
end
@@ -51,7 +50,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,11 +67,10 @@ 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
@@ -73,7 +79,7 @@ defmodule Protocol do
if not loaded or not a protocol.
"""
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,29 +92,17 @@ 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
def meta(functions, fallback, env) do
contents = quote do
def __protocol__(:name), do: __MODULE__
def __protocol__(:functions), do: unquote(:lists.sort(functions))
@doc false
def behaviour_info(:callbacks), do: [{ :__impl__, 0 }|__protocol__(:functions)]
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
@@ -116,8 +110,9 @@ defmodule Protocol do
try do
target.__impl__
target
rescue
UndefinedFunctionError -> __fallback__
catch
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
__fallback__
end
other ->
other
@@ -143,12 +138,14 @@ defmodule Protocol do
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)
def impl_for(conversions, fallback, env) do
contents = lc kind inlist conversions do
each_impl_for(kind, if fallback, do: conversions)
end
# 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 !L.keyfind(Any, 1, conversions) && length(conversions) < 10 do
contents = contents ++ [quote do
defp __raw_impl__(_) do
nil
@@ -204,8 +201,8 @@ defmodule Protocol do
]
end
# Returns a quoted expression that allow to checks
# if a variable named first is built in or not.
# 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)
@@ -218,18 +215,30 @@ defmodule Protocol do
end
end
# Specially handle tuples as they can also be record.
# If this is the case, module.Record will be returned.
# Handle records when we don't have fallbacks.
# It simply gets the first element of the tuple.
# This case assumes that, whenever a tuple is given
# it is meant to be a record, so we don't need extra
# checks.
defp each_impl_for({ _, :is_record }, nil) do
quote do
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
__MODULE__.Record
end
end
end
# Specially handle records in the case we have fallbacks.
defp each_impl_for({ _, :is_record }, conversions) 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 -> __fallback__
false ->
case atom_to_list(first) do
'Elixir-' ++ _ -> __MODULE__.Record
_ -> __MODULE__.Tuple
_ -> __fallback__
end
end
end
@@ -259,21 +268,22 @@ 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
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)
# 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
# dependencies, so we use the <<>> instead.
args = lc i inlist :lists.seq(1, arity) do
generated = lc i inlist :lists.seq(1, arity) do
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
end
@@ -281,28 +291,31 @@ defmodule Protocol.DSL do
# Append new function to the list
@functions [unquote({name, arity})|@functions]
Kernel.def unquote(name).(unquote_splicing(args)) do
args = [unquote_splicing(args)]
# Generate a fake definition with the user
# signature that will be used by docs
Kernel.def unquote(name).(unquote_splicing(args))
Kernel.def unquote(name).(unquote_splicing(generated)) do
case __raw_impl__(xA) do
__MODULE__.Record ->
target = Module.concat(__MODULE__, :erlang.element(1, xA))
try do
target = Module.concat(__MODULE__, :erlang.element(1, xA))
apply target, unquote(name), args
rescue
UndefinedFunctionError ->
target.unquote(name)(unquote_splicing(generated))
catch
:error, :undef, [[{ ^target, unquote(name), args, _ }|_]|_] when length(args) == unquote(arity) ->
case __fallback__ do
nil ->
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
other ->
apply other, unquote(name), args
apply other, unquote(name), [unquote_splicing(generated)]
end
end
nil ->
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
other ->
apply other, unquote(name), args
apply other, unquote(name), [unquote_splicing(generated)]
end
end
end
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 }
+332 -101
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,150 @@ 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 `modules`.
This is invoked directly by `Kernel.defrecord`, so check it
for more information and documentation.
"""
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
Record.deffunctions(unquote(values), unquote(opts), __ENV__)
unquote(block)
end
end
end
@doc """
Defines record functions skipping the module definition.
This is called directly by `defrecord`. It expects the
module environment, the module values and a keyword list
of options.
## Examples
defmodule CustomRecord do
Record.deffunctions [:name, :age], __ENV__
end
"""
def deffunctions(values, opts // [], env) do
values = lc value inlist values, do: convert_value(value)
escaped = Macro.escape(values)
extensions = Keyword.get(opts, :extensions, Record.Extensions)
contents = [
reflection(escaped),
initializer(escaped),
indexes(escaped),
readers(values, 1, []),
conversions(values),
writers(values, 1, []),
updater(values),
extensions(values, 1, [], extensions)
]
# Special case for bootstraping purposes
if env == Macro.Env do
:elixir_module.eval_quoted(env, contents, [], [])
else
contents = [quote(do: @__record__ unquote(escaped))|contents]
Module.eval_quoted(env, contents)
end
end
@doc """
Defines three macros for reading and writing records values.
These macros are private to the current module and are
basically a simple mechanism for manipulating tuples when
there isn't an interest in exposing the record as a whole.
In some ways, it is similar to Erlang records, since it is
only available at compilation time.
## Examples
defmodule CustomModule do
Record.defmacros :_user, [:name, :age], __ENV__
def new(name, age) do
_user(name: name, age: age)
end
def name(user, name) do
_user(user, name: name)
end
def age(user) do
_user(user, :age)
end
def to_keywords(user) do
_user(user)
end
def name_and_age(user) do
_user(user, [:name, :age])
end
def age_and_name(user) do
_user(user, [:age, :name])
end
end
"""
def defmacros(name, values, env) do
escaped = lc value inlist values, do: Macro.escape(convert_value(value))
contents = quote do
defmacrop unquote(name).(record) when is_tuple(record) do
Record.to_keywords(__CALLER__, __MODULE__, unquote(escaped), record)
end
defmacrop unquote(name).(args) do
Record.access(__CALLER__, __MODULE__, unquote(escaped), args)
end
defmacrop unquote(name).(record, key) when is_atom(key) do
Record.get(__CALLER__, __MODULE__, unquote(escaped), record, key)
end
defmacrop unquote(name).(record, args) do
Record.dispatch(__CALLER__, __MODULE__, unquote(escaped), record, args)
end
end
Module.eval_quoted(env, contents)
end
defp convert_value(atom) when is_atom(atom) do
{ atom, nil }
end
defp convert_value(other), do: other
# 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(caller, atom, fields, keyword) do
unless is_orddict(keyword) 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 +182,92 @@ 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(caller, atom, fields, record, args) do
if is_keyword(args) do
update(caller, atom, fields, record, args)
else
to_list(caller, 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(caller, atom, fields, var, keyword) 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(_caller, 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(_caller, _atom, fields, record) do
Enum.map Keyword.from_enum(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(_caller, 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
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
# Define __record__/1 and __record__/2 as reflection functions
# that returns the record names and fields.
#
@@ -114,7 +284,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 +308,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 +323,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 +362,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
@@ -181,43 +380,85 @@ defmodule Record do
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
# Implement getters and setters for each attribute.
# For a declaration like:
# Implement readers. 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)
# 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)
defp readers([{ key, _default }|t], i, acc) do
contents = quote do
def unquote(key).(record) do
:erlang.element(unquote(i + 1), record)
end
end
readers(t, i + 1, [contents | acc])
end
defp getters_and_setters([], _i, acc, _), do: acc
defp readers([], _i, acc), do: acc
# Implement writers. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define four methods:
#
# def :atime.(value, record) do
# setelem(record, 1, value)
# end
#
# def :mtime.(record) do
# setelem(record, 2, value)
# end
#
defp writers([{ key, _default }|t], i, acc) do
contents = quote do
def unquote(key).(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
end
writers(t, i + 1, [contents | acc])
end
defp writers([], _i, acc), do: acc
# Defines update/2
defp updater(values) do
fields =
lc {key, _default} inlist values do
quote do
Keyword.get(keywords,
unquote(key),
elem(record, unquote(find_index(values, key, 1))))
end
end
contents = {:{}, 0, [(quote do: __MODULE__)|fields]}
quote do
def update(keywords, record) do
unquote(contents)
end
end
end
# Defines extra functions from the definition.
defp extensions([{ key, default }|t], i, acc, extensions) do
functions = extensions.functions_for(key, default, i)
extensions(t, i + 1, [functions | acc], extensions)
end
defp extensions([], _i, acc, _), do: acc
end
defmodule Record.Extractor do
@@ -228,7 +469,7 @@ defmodule Record.Extractor do
def retrieve(name, from: string) do
file = to_char_list(string)
case Erlang.code.where_is_file(file) do
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
end
@@ -239,19 +480,19 @@ defmodule Record.Extractor do
# 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
[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, Erlang.filename.join([libpath|path])
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, 1) do
if record = List.keyfind(records, name, 0) do
parse_record(record)
else
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
@@ -267,7 +508,7 @@ defmodule Record.Extractor do
# 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
case :epp_dodger.quick_parse_file(file) do
{ :ok, form } ->
form
other ->
@@ -282,7 +523,7 @@ defmodule Record.Extractor do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = Erlang.erl_eval.expr(cons, [])
{ :value, list, _ } = :erl_eval.expr(cons, [])
list
end
@@ -299,16 +540,14 @@ defmodule Record.Extractor do
end
end
defmodule Record.Definition do
defmodule Record.Extensions 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)
]
[ default_for(key, default, i),
extension_for(key, default, i) ]
end
# Skip the __exception__ for defexception.
@@ -322,17 +561,9 @@ defmodule Record.Definition do
update = binary_to_atom(bin_update)
quote do
def unquote(key).(record) do
:erlang.element(unquote(i), record)
end
def unquote(key).(value, record) do
:erlang.setelement(unquote(i), record, value)
end
def unquote(update).(function, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, function.(current))
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, function.(current))
end
end
end
@@ -345,13 +576,13 @@ defmodule Record.Definition do
quote do
def unquote(prepend).(value, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, value ++ current)
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))
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, Keyword.merge(current, value))
end
end
end
@@ -362,8 +593,8 @@ defmodule Record.Definition do
quote do
def unquote(increment).(value // 1, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, current + value)
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, current + value)
end
end
end
@@ -373,9 +604,9 @@ defmodule Record.Definition do
toggle = :"toggle_#{bin_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
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, not current)
end
end
end
+135 -59
View File
@@ -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:
@@ -53,10 +54,12 @@ 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
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, compiled } ->
{ :ok, { Regex, compiled, source, options } }
{ :ok, { Regex, compiled, source, options, groups } }
error ->
error
end
@@ -84,8 +87,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, compiled, _, _, _ }, string) do
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
end
@@ -100,8 +103,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, compiled, _, _, _ }, string) do
:re.run(string, compiled, [{ :capture, :none }]) == :match
end
@doc """
@@ -114,15 +117,43 @@ 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, compiled, _, _, 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 the given captures as a list of tuples.
## Examples
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
"""
def captures({ Regex, _, _, _, groups } = regex, string, options // []) do
unless captures = Keyword.get(options, :capture) do
captures = if groups do
List.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 a list with the match indexes in the given string.
The matches are tuples where the first element is the index
@@ -135,19 +166,18 @@ defmodule Regex do
Regex.indexes %r/e/, "abcd" #=> nil
"""
def indexes({ Regex, compiled, _, _ }, string) do
case Erlang.re.run(string, compiled, [{ :capture, :all, :index }]) do
:nomatch ->
nil
{ :match, results } ->
results
def indexes({ Regex, compiled, _, _, _ }, string) do
IO.write "[WARNING] Regex.indexes is deprecated, please use Regex.run with return: :index as option instead\n#{Exception.formatted_stacktrace}"
case :re.run(string, compiled, [{ :capture, :all, :index }]) do
:nomatch -> nil
{ :match, results } -> results
end
end
@doc """
Returns the underlying re_pattern in the regular expression.
"""
def re_pattern({ Regex, compiled, _, _ }) do
def re_pattern({ Regex, compiled, _, _, _ }) do
compiled
end
@@ -159,7 +189,7 @@ defmodule Regex do
Regex.source %r(foo) #=> "foo"
"""
def source({ Regex, _, source, _ }) do
def source({ Regex, _, source, _, _ }) do
source
end
@@ -171,10 +201,22 @@ defmodule Regex do
Regex.opts %r(foo)m #=> 'm'
"""
def opts({ Regex, _, _, opts }) do
def opts({ Regex, _, _, opts, _ }) do
opts
end
@doc """
Returns list of named groups in regex.
## Examples
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
"""
def groups({ Regex, _, _, _, groups }) do
groups
end
@doc """
Same as run, but scans the target several times collecting all matches of
the regular expression. A list is returned with each match. If the item in
@@ -188,35 +230,49 @@ 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, compiled, _, _, _ }, string, options) do
return = 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, string, options) when is_integer(options) or is_atom(options) do
IO.write "[WARNING] Passing an integer or atom to Regex.split/3 is deprecated, pass a :parts option instead\n#{Exception.formatted_stacktrace}"
split(regex, string, parts: options)
end
def split({ Regex, compiled, _, _, _ }, string, options) do
parts =
cond do
options[:global] == false -> 2
p = options[:parts] -> p
true -> :infinity
end
return = 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 +283,23 @@ 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) }])
def replace({ Regex, compiled, _, _, _ }, string, replacement, options // []) do
opts = if options[:global] != false, do: [:global], else: []
return = options[:return] || return_for(string)
opts = [{ :return, return }|opts]
:re.replace(string, compiled, replacement, opts)
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])
@doc false
def replace_all({ Regex, compiled, _, _, _ }, string, replacement) do
IO.write "[WARNING] Regex.replace_all is deprecated, simply use Regex.replace instead\n#{Exception.formatted_stacktrace}"
:re.replace(string, compiled, replacement, [{ :return, return_for(string) }, :global])
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 +312,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
+425
View File
@@ -0,0 +1,425 @@
defmodule String do
@moduledoc """
A string in Elixir is a utf-8 binary. This module
contains function to work with utf-8 data and its
codepoints.
For working with raw binaries, use Erlang's :binary
module.
"""
@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
"""
# 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"]
"""
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 upper case.
## Examples
String.upcase("abcd") #=> "ABCD"
String.upcase("ab 123 xpto") #=> "AB 123 XPTO"
String.upcase("josé") #=> "JOSÉ"
"""
def upcase(<<>>), do: <<>>
def upcase(<<195, c, t :: binary>>) when c in 160..191 do
<<195, c - 32, upcase(t) :: binary>>
end
def upcase(<<c, t :: binary>>) when c in ?a..?z do
<<c - 32, upcase(t) :: binary>>
end
def upcase(<<c, t :: binary>>) do
<<c , upcase(t) :: binary>>
end
@doc """
Convert all characters on the given string to down case.
## Examples
String.downcase("ABCD") #=> "abcd"
String.downcase("AB 123 XPTO") #=> "ab 123 xpto"
String.downcase("JOSÉ") #=> "josé"
"""
def downcase(<<>>), do: <<>>
def downcase(<<195, c, t :: binary>>) when c in 128..159 do
<<195, c + 32, downcase(t) :: binary>>
end
def downcase(<<c, t :: binary>>) when c in ?A..?Z do
<<c + 32, downcase(t) :: binary>>
end
def downcase(<<c, t :: binary>>) do
<<c , downcase(t) :: binary>>
end
@doc """
Returns a string where trailing char have been
removed. If no `char` is passed `space`is used.
## Examples
String.rstrip(" abc ") #=> " abc"
String.rstrip(" abc _", ?_) #=> " abc "
"""
def rstrip(string, char // ?\s)
# 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
rstrip(string, "", char)
else
string
end
end
defp rstrip(<<char, string :: binary>>, buffer, char) do
rstrip(string, <<char, buffer :: binary>>, char)
end
defp rstrip(<<char, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char, rstrip(string, "", another_char) :: binary>>
end
defp rstrip(<<>>, _, _) do
<<>>
end
@doc """
Returns a string where leading char have been
removed. If no `char` is passed `space`is used.
## Examples
String.lstrip(" abc ") #=> "abc "
String.lstrip("_ abc _", ?_) #=> " abc _"
"""
def lstrip(string, char // ?\s)
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 char have been
removed. If no `char` is passed `space`is used.
## Examples
String.strip(" abc ") #=> "abc"
String.strip("a abc a", ?a) #=> " abc "
"""
def strip(string, char // ?\s) 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"
"""
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"
"""
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("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
"""
def codepoints(string) do
do_codepoints(codepoint(string))
end
defp do_codepoints({char, rest}) do
[char|do_codepoints(codepoint(rest))]
end
defp do_codepoints(:no_codepoint), do: []
@doc """
Returns the first codepoint from an utf8 string.
## Examples
String.first("elixir") #=> "e"
String.first("եոգլի") #=> "ե"
"""
def first(string) do
case codepoint(string) do
{ char, _ } -> char
:no_codepoint -> ""
end
end
@doc """
Returns the last codepoint from an utf8 string.
## Examples
String.last("elixir") #=> "r"
String.last("եոգլի") #=> "ի"
"""
def last(string) do
do_last(codepoint(string), "")
end
defp do_last({char, rest}, _) do
do_last(codepoint(rest), char)
end
defp do_last(:no_codepoint, last_char), do: last_char
@doc """
Returns the number of codepoint in an utf8 string.
## Examples
String.length("elixir") #=> 6
String.length("եոգլի") #=> 5
"""
def length(string) do
do_length(codepoint(string))
end
defp do_length({_, rest}) do
1 + do_length(codepoint(rest))
end
defp do_length(:no_codepoint), do: 0
@doc """
Returns the codepoint in the `position` of the given utf8 `string`.
If `position` is greater than `string` length, than it returns `nil`.
## Examples
String.at("elixir", 0) #=> "1"
String.at("elixir", 1) #=> "l"
String.at("elixir", 10) #=> nil
String.at("elixir", -1) #=> "r"
String.at("elixir", -10) #=> "nil"
"""
def at(string, position) when position >= 0 do
do_at(codepoint(string), position, 0)
end
def at(string, position) when position < 0 do
real_pos = do_length(codepoint(string)) - abs(position)
case real_pos >= 0 do
true -> do_at(codepoint(string), real_pos, 0)
false -> ""
end
end
defp do_at({_ , rest}, desired_pos, current_pos) when desired_pos > current_pos do
do_at(codepoint(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_codepoint, _, _), do: ""
# Private implementation which returns the first codepoint
# of any given utf8 string and the rest of it
# If an empty string is given, :no_codepoint is returned.
defp codepoint(<<194, char, rest :: binary>>)
when char in 161..191,
do: { <<194, char>>, rest }
defp codepoint(<<first, char, rest :: binary>>)
when first in 195..223 and char in 128..191,
do: { <<first, char>>, rest }
defp codepoint(<<first, second, char, rest :: binary>>)
when first == 224 and second in 160..191 and char in 128..191,
do: { <<first, second, char>>, rest }
defp 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 }
defp codepoint(<<other, rest :: binary>>), do: { <<other>>, rest }
defp codepoint(<<>>), do: :no_codepoint
end
+11 -14
View File
@@ -5,12 +5,12 @@ 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
# 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
if :os.find_executable('git') do
data = :os.cmd('git rev-parse HEAD')
Regex.replace_all %r/\n/, to_binary(data), ""
data = :os.cmd('git describe --always --tags')
Regex.replace %r/\n/, to_binary(data), ""
else
""
end
@@ -24,23 +24,20 @@ defmodule System do
@doc """
Returns Elixir's version as binary.
"""
def version, do: "0.6.0"
def version, do: "0.7.0"
@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.
"""
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.
"""
def argv do
Erlang.gen_server.call(:elixir_code_server, :argv)
:gen_server.call(:elixir_code_server, :argv)
end
@doc """
@@ -138,7 +135,7 @@ defmodule System do
Get the stacktrace.
"""
def stacktrace do
filter_stacktrace Erlang.erlang.get_stacktrace
filter_stacktrace :erlang.get_stacktrace
end
## Helpers
@@ -151,6 +148,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
+39 -29
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,17 +67,17 @@ 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
@@ -84,11 +94,11 @@ defmodule URI do
@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 +108,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 +131,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)
@doc """
Responsible for returning the default port.
"""
defcallback default_port()
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
+32 -19
View File
@@ -1,12 +1,13 @@
-module(elixir).
-behaviour(application).
-export([start_cli/0, start_app/0,
-export([main/1, start_cli/0, start_app/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}).
% OTP APPLICATION API
-export([start/2, stop/1, config_change/3]).
@@ -23,6 +24,12 @@ stop(_S) ->
config_change(_Changed, _New, _Remove) ->
ok.
%% escript entry point
main(Args) ->
start_app(),
'Elixir.Kernel.CLI':process_argv(Args).
%% ELIXIR ENTRY POINTS
% Start the Elixir app. This is the proper way to boot Elixir from
@@ -43,14 +50,14 @@ start_cli() ->
%% EVAL HOOKS
scope_for_eval(Opts) ->
File = case orddict:find(file, Opts) of
{ ok, F } -> to_binary(F);
error -> <<"nofile">>
File = case lists:keyfind(file, 1, Opts) of
{ file, F } -> to_binary(F);
false -> <<"nofile">>
end,
Local = case orddict:find(delegate_locals_to, Opts) of
{ ok, L } -> L;
error -> nil
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, L } -> L;
false -> nil
end,
#elixir_scope{file=File,local=Local}.
@@ -60,15 +67,15 @@ scope_for_eval(Opts) ->
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 +83,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 +96,13 @@ 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=[],
quote_vars=[],
clause_vars=[],
counter=0
},
{ ParseTree, NewScope } = elixir_translator:translate(Tree, Scope),
case ParseTree of
[] -> { nil, Binding, NewScope };
@@ -103,8 +116,8 @@ 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,_}|T], Dict) -> binding_dict(T, orddict:store(H, H, Dict));
binding_dict([], Dict) -> Dict.
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
@@ -112,7 +125,7 @@ final_binding([{Var,_}|T], Acc, Binding, Vars) ->
case atom_to_list(Var) of
"_@" ++ _ -> final_binding(T, Acc, Binding, Vars);
_ ->
RealName = dict:fetch(Var, Vars),
RealName = orddict:fetch(Var, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
@@ -124,4 +137,4 @@ normalize_binding(Binding) ->
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_@MODULE', nil, Keyword)
end.
end.
+56 -51
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).
@@ -88,7 +88,7 @@ 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()},
S = RawS#elixir_scope{clause_vars=orddict:new()},
case DecoupledClauses of
[DecoupledClause] ->
@@ -106,7 +106,7 @@ 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]),
NewVars = lists:umerge([lists:sort(orddict:fetch_keys(X)) || X <- CV]),
case NewVars of
[] -> { TClauses, TS };
@@ -119,44 +119,44 @@ match(Line, DecoupledClauses, RawS) ->
{ FinalVars, FS } = lists:mapfoldl(fun normalize_vars/2, TS, NewVars),
% 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 +199,31 @@ 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, quoted }, S) ->
normalize_vars(Var, quoted, #elixir_scope.quote_vars, S);
FS = NS#elixir_scope{
vars=dict:store(Var, NewValue, Vars),
clause_vars=dict:store(Var, NewValue, ClauseVars)
},
normalize_vars({ Var, Kind }, S) ->
normalize_vars(Var, Kind, #elixir_scope.vars, S).
Expr = case dict:find(Var, Vars) of
normalize_vars(Var, Kind, Index, #elixir_scope{clause_vars=ClauseVars} = S) ->
Vars = element(Index, S),
{ { _, _, NewValue }, S1 } = elixir_scope:build_erl_var(0, S),
S2 = setelement(Index, S1, orddict:store(Var, NewValue, Vars)),
S3 = S2#elixir_scope{clause_vars=orddict:store({ Var, Kind }, NewValue, ClauseVars)},
Expr = case orddict:find(Var, Vars) of
{ ok, OldValue } -> { var, 0, OldValue };
error -> { atom, 0, nil }
end,
{ { Var, NewValue, Expr }, FS }.
{ { Var, Kind, NewValue, Expr }, S3 }.
% 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.
+33 -10
View File
@@ -16,27 +16,31 @@ init(_args) ->
process_flag(trap_exit, true),
{ ok, #elixir_code_server{} }.
handle_call({loaded, Path}, _From, Config) ->
handle_call({ acquire, 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]} }
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) ->
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 };
@@ -50,6 +54,25 @@ handle_call(compiler_options, _From, Config) ->
handle_call(_Request, _From, Config) ->
{ reply, undef, Config }.
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(_Request, Config) ->
{ noreply, Config }.
+15 -14
View File
@@ -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,7 +26,7 @@ string(Contents, File) when is_list(Contents), is_binary(File) ->
try
put(elixir_compiled, []),
Forms = elixir_translator:forms(Contents, 1, File),
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
eval_forms(Forms, 1, File, [], #elixir_scope{file=File}),
lists:reverse(get(elixir_compiled))
after
@@ -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).
@@ -192,9 +188,14 @@ core_file(File) ->
core_list() ->
[
"lib/elixir/lib/range.ex",
"lib/elixir/lib/behaviour.ex",
"lib/elixir/lib/uri/parser.ex",
"lib/elixir/lib/elixir/formatter.ex",
"lib/elixir/lib/dict.ex",
"lib/elixir/lib/dict/common.ex",
"lib/elixir/lib/access.ex",
"lib/elixir/lib/range.ex",
"lib/elixir/lib/*/*.ex",
"lib/elixir/lib/*.ex"
].
@@ -202,20 +203,20 @@ core_list() ->
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/macro.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/list.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/gen_server/behaviour.ex"
].
%% ERROR HANDLING
+39 -42
View File
@@ -63,71 +63,64 @@ 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),
Stack = TS#elixir_scope.macro,
%% 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)
end,
%% Store defaults
[store_each(false, Kind, File, Location, Stack, Table, 0,
function_for_default(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 +128,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),
@@ -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]);
+7 -6
View File
@@ -13,7 +13,7 @@ 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),
@@ -35,13 +35,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(["_@", 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.
+1 -1
View File
@@ -54,7 +54,7 @@ name(Module, Function) ->
name(_Module, { Name, _ } = Function, Overridable) ->
{ Count, _ } = orddict:fetch(Function, Overridable),
?ELIXIR_ATOM_CONCAT(["OVERRIDABLE-", Count, "-", Name]).
?ELIXIR_ATOM_CONCAT([Name, " (overridable ", Count, ")"]).
%% Store
+7 -6
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,7 +58,7 @@ 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);
elixir_macros:translate({ Name, Line, Args }, S);
{ ok, Receiver, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
end;
@@ -85,7 +85,7 @@ 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)
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;
@@ -276,6 +276,7 @@ in_erlang_functions() ->
{ integer_to_list, 2 },
{ iolist_size, 1 },
{ iolist_to_binary, 1 },
{ is_alive, 0 },
{ is_atom, 1 },
{ is_binary, 1 },
{ is_bitstring, 1 },
+23 -4
View File
@@ -89,8 +89,9 @@ 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}}) ->
@@ -144,7 +145,8 @@ 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]).
@@ -153,4 +155,21 @@ 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.
+60 -22
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) ->
@@ -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','*'}
].
].
+2 -2
View File
@@ -147,10 +147,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 };
+245
View File
@@ -0,0 +1,245 @@
-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]);
%% Deprecated syntax
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,
elixir_errors:deprecation(Line, ES#elixir_scope.file, "Old bitstring syntax with | is deprecated, use the new one with :: instead"),
%% 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 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].
%% Deprecated 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 <<>>").
+60 -52
View File
@@ -1,8 +1,8 @@
%% 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]).
@@ -13,18 +13,18 @@
%% 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 +32,33 @@ 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 };
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 };
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, [_] }, 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 +71,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 Name == typep; Name == type; Name == spec; Name == callback ->
case elixir_compiler:get_opt(internal) of
true -> { { nil, Line }, S };
false ->
@@ -86,7 +86,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 +98,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 +110,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 +125,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 +140,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
@@ -207,10 +207,10 @@ translate_macro({defmodule, Line, [Ref, KV]}, S) ->
{ elixir_module:translate(Line, FRef, Block, S), FS };
translate_macro({Kind, Line, [Call]}, S) when ?FUNS() ->
translate_macro({Kind, Line, [Call, skip_definition]}, S);
translate({Kind, Line, [Call]}, S) when ?FUNS() ->
translate({Kind, Line, [Call, nil]}, S);
translate_macro({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
assert_module_scope(Line, Kind, S),
assert_no_function_scope(Line, Kind, S),
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
@@ -221,35 +221,35 @@ translate_macro({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
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() ->
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
@@ -275,9 +275,17 @@ translate_in(Line, Left, Right, S) ->
{ op, Line, 'orelse', Acc, { op, Line, '==', Var, X } }
end, { op, Line, '==', Var, H }, T);
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
{ op, Line, 'andalso',
{ op, Line, '>=', Var, Start },
{ op, Line, '=<', Var, End }
{ op, Line, 'orelse',
{ op, Line, 'andalso',
{ op, Line, '=<', Start, End},
{ op, Line, 'andalso',
{ op, Line, '>=', Var, Start },
{ op, Line, '=<', Var, End } } },
{ op, Line, 'andalso',
{ op, Line, '<', End, Start},
{ op, Line, 'andalso',
{ op, Line, '=<', Var, Start },
{ op, Line, '>=', Var, End } } }
};
_ ->
syntax_error(Line, S#elixir_scope.file, "invalid args for operator in")
+64 -39
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),
{ Value, FinalBinding }.
scope_for_eval(Module, #elixir_scope{} = S) ->
S#elixir_scope{module=Module};
@@ -12,10 +25,11 @@ scope_for_eval(Module, Opts) ->
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]).
@@ -23,7 +37,7 @@ docs_table(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
@@ -33,7 +47,7 @@ translate(Line, Ref, Block, S) ->
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
MetaS = elixir_scope:serialize(S),
Vars = dict:fold(fun(Key, Value, { Acc, Counter }) ->
Vars = orddict:fold(fun(Key, Value, { Acc, Counter }) ->
{ { cons, Line, { tuple, Line, [
{ atom, Line, Key },
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
@@ -46,9 +60,7 @@ translate(Line, Ref, Block, S) ->
%% 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,
@@ -61,7 +73,7 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(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),
Forms2 = specs_form(Line, Module, Defmacro, Defmacrop, Forms1),
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
@@ -71,8 +83,8 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
{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)),
@@ -81,9 +93,13 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(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 = [{ X, Y } || { X, Y, _ } <- Vars],
S = elixir_scope:deserialize(RawS, Dict),
compile(Line, Module, Block, Vars, S).
%% Hook that builds both attribute and functions and set up common hooks.
@@ -98,11 +114,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
@@ -128,7 +149,7 @@ eval_form(Line, Module, Block, Vars, RawS) ->
S = scope_for_eval(Module, RawS),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Temp, Vars, S),
elixir_def_overridable:store_pending(Module),
eval_callbacks(Line, Module, 'before_compile', [Module], NewS),
eval_callbacks(Line, Module, before_compile, [Module], NewS),
Value.
%% Return the form with exports and function declarations.
@@ -167,9 +188,9 @@ attributes_form(Line, _File, Module, Current) ->
%% Specs
specs_form(Line, Module, Def, Defmacro, Defmacrop, Forms) ->
specs_form(Line, Module, Defmacro, Defmacrop, Forms) ->
{ Specs, Callbacks } = ets:foldl(fun(X, Acc) ->
translate_spec(X, Module, Def, Defmacro, Defmacrop, Acc)
translate_spec(X, Defmacro, Defmacrop, Acc)
end, { [], [] }, spec_table(Module)),
Temp = specs_attributes(Line, spec, Forms, Specs),
@@ -181,7 +202,7 @@ specs_attributes(Line, Type, Forms, Specs) ->
[{ attribute, Line, Type, { Tuple, proplists:append_values(Tuple, Specs) } }|Acc]
end, Forms, Keys).
translate_spec({ { spec, Spec }, Rest }, Module, Def, Defmacro, Defmacrop, { Specs, Callbacks } = Acc) ->
translate_spec({ { spec, Spec }, Rest }, Defmacro, Defmacrop, { Specs, Callbacks } = Acc) ->
case ordsets:is_element(Spec, Defmacrop) of
true -> Acc;
false ->
@@ -191,14 +212,11 @@ translate_spec({ { spec, Spec }, Rest }, Module, Def, Defmacro, Defmacrop, { Spe
New = { { ?ELIXIR_MACRO(Name), Arity + 1 }, [spec_for_macro(X) || X <- Rest] },
{ [New|Specs], Callbacks };
false ->
case (Module == 'Elixir.Kernel') andalso (not ordsets:is_element(Spec, Def)) of
true -> Acc;
false -> { [{ Spec, Rest }|Specs], Callbacks }
end
{ [{ Spec, Rest }|Specs], Callbacks }
end
end;
translate_spec({ { callback, Spec }, Rest }, _, _, _, _, { Specs, Callbacks }) ->
translate_spec({ { callback, Spec }, Rest }, _, _, { Specs, Callbacks }) ->
{ Specs, [{ Spec, Rest }|Callbacks] }.
spec_for_macro({ type, Line, 'fun', [{ type, _, product, Args }|T] }) ->
@@ -212,7 +230,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 +239,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) ->
@@ -249,7 +268,7 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
Docs = elixir_compiler:get_opt(docs, C),
Contents = { function, Line, '__info__', 1, [
functions_clause(Line, Def),
macros_clause(Line, Defmacro),
macros_clause(Line, Module, Def, Defmacro),
docs_clause(Line, Module, Docs),
moduledoc_clause(Line, Module, Docs),
compile_clause(Line),
@@ -259,16 +278,22 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
{ [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(Line, Def) ->
All = ordsets:add_element({'__info__',1}, Def),
{ clause, Line, [{ atom, Line, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
macros_clause(Line, Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
elixir_dispatch:in_erlang_macros()), Def);
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
self_clause(Line, Module) ->
{ clause, Line, [{ atom, Line, self }], [], [{ atom, Line, Module }] }.
macros_clause(Line, Defmacro) ->
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(Defmacro)] }.
docs_clause(Line, Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
@@ -277,7 +302,7 @@ docs_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, docs }], [], [{ atom, Line, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':read_attribute(Module, moduledoc),
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, Line, [{ atom, Line, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
moduledoc_clause(Line, _Module, _) ->
@@ -297,7 +322,7 @@ else_clause(Line) ->
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}) ->
+58 -47
View File
@@ -3,22 +3,21 @@
Nonterminals
grammar expr_list
expr block_expr fn_expr bracket_expr call_expr max_expr
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
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
open_paren close_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_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
@@ -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
@@ -87,11 +87,14 @@ 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,6 +140,7 @@ 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').
@@ -152,18 +156,23 @@ 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'.
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'.
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 ')' : build_block([]).
max_expr -> open_paren expr_list close_paren : build_block('$2').
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
bracket_at_expr -> at_op dot_bracket_identifier bracket_access : build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
bracket_at_expr -> at_op max_expr bracket_access : build_access(build_unary_op('$1', '$2'), '$3').
bracket_at_expr -> bracket_at_expr bracket_access : build_access('$1', '$2').
base_expr -> number : ?exprs('$1').
base_expr -> signed_number : { element(4, '$1'), ?line('$1'), ?exprs('$1') }.
base_expr -> atom : build_atom('$1').
@@ -186,8 +195,8 @@ 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']].
do_eol -> 'do' : '$1'.
do_eol -> 'do' eol : '$1'.
@@ -214,9 +223,6 @@ 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'.
@@ -288,11 +294,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 +377,63 @@ 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']).
base_comma_expr -> expr ',' : ['$1'].
base_comma_expr -> base_comma_expr expr ',' : ['$2'|'$1'].
comma_expr -> expr : ['$1'].
comma_expr -> comma_expr comma_separator expr : ['$3'|'$1'].
comma_expr -> kw_base : ['$1'].
comma_expr -> base_comma_expr : '$1'.
comma_expr -> base_comma_expr expr : ['$2'|'$1'].
comma_expr -> base_comma_expr kw_base : ['$2'|'$1'].
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']).
optional_comma_expr -> expr : '$1'.
optional_comma_expr -> 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 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_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 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 expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
% Bitstrings
@@ -528,9 +543,8 @@ 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_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 }) -> { binary_to_atom, Line, [{ '<<>>', Line, Args }, utf8] }.
%% Keywords
@@ -550,6 +564,3 @@ build_stab([H|T], Marker, Temp, Acc) ->
build_stab([], Marker, Temp, Acc) ->
H = { Marker, build_block(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.
+52 -27
View File
@@ -17,51 +17,65 @@ linify(_, Else) -> Else.
%% Translation
quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
quote({ 'unquote_splicing', Line, _ } = Expr, 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] }, _Line, [Expr] }, CallLine, Args }, Q, S) ->
Rewritten = { { '.', Line, [Left, { unquote, Line, [Expr] }] }, CallLine, Args },
do_quote(Rewritten, Q, S);
do_quote({ { '.', Line, [Left, unquote] }, _Line, [Expr] }, Q, S) ->
Rewritten = { { '.', Line, [Left, { unquote, Line, [Expr] }] }, Line, [] },
do_quote(Rewritten, Q, S);
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 +85,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 +97,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 +110,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.
+36 -27
View File
@@ -1,7 +1,7 @@
%% 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,
to_erl_env/1, to_ex_env/1, filename/1,
@@ -10,30 +10,36 @@
-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 };
_ ->
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, Vars), orddict:find(Name, TempVars) } of
{ true, { ok, Kind } } ->
{ {var, Line, orddict:fetch(Name, Vars) }, S };
{ 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),
ClauseVars = S#elixir_scope.clause_vars,
{ NewVar, NS#elixir_scope{
vars=orddict:store(Name, RealName, Vars),
temp_vars=orddict:store(Name, Kind, TempVars),
clause_vars=orddict:store({ Name, Kind }, RealName, ClauseVars)
} }
end;
_ ->
case orddict:is_key(Name, Vars) of
false -> elixir_translator:translate_each({Name, Line, []}, S);
true -> { {var, Line, orddict:fetch(Name, Vars) }, S }
end
end
end.
@@ -87,7 +93,7 @@ deserialize({ File, Functions, CheckClauses, Macro, Requires, Macros, Aliases, S
macros=Macros,
aliases=Aliases,
scheduled=Scheduled,
vars=dict:from_list(Vars),
vars=orddict:from_list(Vars),
counter=length(Vars)
}.
@@ -102,9 +108,9 @@ umergev(S1, S2) ->
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),
quote_vars=orddict:merge(fun var_merger/3, Q1, Q2),
clause_vars=orddict:merge(fun clause_var_merger/3, C1, C2)
}.
% Receives two scopes and return a new scope based on the first
@@ -121,13 +127,16 @@ umergec(S1, S2) ->
% Merge variables trying to find the most recently created.
clause_var_merger({ Var, _ }, K1, K2) ->
var_merger(Var, K1, K2).
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))),
V1 = var_number(atom_to_list(K1)),
V2 = var_number(atom_to_list(K2)),
if V1 > V2 -> K1;
true -> K2
end.
var_number([_,_|T]) -> T.
var_number([_,_|T]) -> list_to_integer(T).
+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
}
],
+235 -165
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, $\n, 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, $\n, 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,13 +694,13 @@ 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, T) -> [Left|T].
@@ -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'; S == 'fn_paren' ->
[{ 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,16 @@ terminator('{') -> '}';
terminator('<<') -> '>>'.
% Keywords check
keyword(Line, paren_identifier, fn, Tokens) ->
handle_keyword(Line, paren_identifier, fn, Tokens) ->
[{ 'fn_paren', Line }|Tokens];
keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifier == do_identifier ->
handle_keyword(Line, Identifier, Atom, Tokens) when Identifier == bracket_identifier; Identifier == paren_identifier ->
case keyword(Atom) of
true -> [{ Atom, Line }|Tokens];
_ -> [{ Identifier, Line, Atom }|Tokens]
end;
handle_keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifier == do_identifier ->
case keyword(Atom) of
true -> [{ Atom, Line }|Tokens];
op -> add_token_with_nl({ Atom, Line }, Tokens);
@@ -717,7 +787,7 @@ keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifi
false -> false
end;
keyword(_, _, _, _) -> false.
handle_keyword(_, _, _, _) -> false.
% Keywords
keyword('fn') -> true;
+155 -244
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) ->
@@ -54,7 +61,7 @@ translate_each({ '__block__', Line, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate(Args, S),
{ { block, Line, TArgs }, NS };
translate_each({ '__scope__', _Line, [[{file,File}],[{do,Expr}]] }, S) ->
translate_each({ '__scope__', _Line, [[{file,File}],Expr] }, S) ->
Old = S#elixir_scope.file,
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
{ TExpr, TS#elixir_scope{file=Old} };
@@ -72,7 +79,7 @@ translate_each({ '__op__', Line, [Op, Left, Right] }, S) when is_atom(Op) ->
translate_each({ '__ambiguousop__', Line, [Var, H|T] }, S) ->
{ Name, _, _ } = Var,
case dict:find(Name, S#elixir_scope.vars) of
case orddict:find(Name, 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 } ->
{ New, SF } = case lists:keyfind(as, 1, KV) of
Opt when Opt == { as, true }; Opt == false ->
{ elixir_aliases:last(Old), SR };
{ as, false } ->
{ Old, SR };
Opt when Opt == { ok, true }; Opt == error ->
{ elixir_aliases:last(Old), SR };
{ ok, Other } ->
{ 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,75 +238,89 @@ 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 } ->
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],[{do,Exprs}]] },
{ DefaultLine, WrappedExprs } = case lists:keyfind(location, 1, T) of
{ location, keep } ->
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],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);
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;
elixir_quote:quote(WrappedExprs, #elixir_quote{marker=Marker, line=TLine, unquote=Unquote}, SL);
translate_each({ quote, GivenLine, [_] }, S) ->
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote");
%% Functions
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,25 +328,23 @@ 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"
Dict = case Args of
nil -> S#elixir_scope.vars;
quoted -> S#elixir_scope.quote_vars;
_ ->
syntax_error(Line, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name])
end,
Result = case S#elixir_scope.context of
assign ->
case orddict:find(Name, Dict) of
error -> "unbound variable ^~s";
{ ok, Value } -> { {var, Line, Value}, S }
end;
false -> "cannot use ^ with expression at ^~s, ^ must be used only with variables"
_ -> "cannot access variable ^~s outside of assignment"
end,
case is_list(Result) of
@@ -373,7 +356,7 @@ translate_each({ '^', Line, [ { Name, _, Args } ] }, S) ->
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:translate_var(Line, Name, quoted, NewS),
{ TVar, VS#elixir_scope{
quote_vars=VS#elixir_scope.vars,
noname=S#elixir_scope.noname,
@@ -381,20 +364,25 @@ translate_each({ Name, Line, quoted }, S) when is_atom(Name) ->
} };
translate_each({ Name, Line, nil }, S) when is_atom(Name) ->
elixir_scope:translate_var(Line, Name, S);
elixir_scope:translate_var(Line, Name, nil, 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,23 +392,24 @@ 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' } ->
elixir_errors:deprecation(Line, S#elixir_scope.file, "Erlang. syntax is deprecated"),
case Args of
[] -> { { atom, Line, Right }, S };
_ ->
@@ -445,7 +434,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 +442,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),
@@ -522,20 +484,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,8 +512,9 @@ 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;
@@ -572,62 +538,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';
@@ -686,4 +597,4 @@ translate_comprehension_clause(X, S) ->
{ _, 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 }.
+4 -70
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,
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;
+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
+21
View File
@@ -0,0 +1,21 @@
Code.require_file "../test_helper.exs", __FILE__
defmodule BehaviourTest do
use ExUnit.Case, async: true
defmodule Sample do
use Behaviour
@doc "Doc"
defcallback foo(foo, bar // [], bat // [])
end
test :docs_are_defined do
assert [_, _] = Sample.__info__(:docs)
end
test :callbacks do
assert [{:foo,3},{:foo,2},{:foo,1}] = Sample.behaviour_info(:callbacks)
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
+34 -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,10 @@ defmodule Binary.Inspect.TupleTest do
assert inspect({ List, 1 }) == "{List,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 +127,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 +148,15 @@ 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_printable do
assert inspect([{:a,1}]) == "[{:a,1}]"
assert inspect([{:b, 1},{:a,1}]) == "[b: 1, a: 1]"
end
test :unproper do
@@ -148,6 +170,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
+35 -37
View File
@@ -1,6 +1,6 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule BinaryTest do
defmodule Binary.LiteralTest do
use ExUnit.Case, async: true
test :heredoc do
@@ -46,35 +46,6 @@ bar
assert x == "bar"
end
test :__B__ do
assert %B(foo) == "foo"
assert %B[foo] == "foo"
assert %B{foo} == "foo"
assert %B'foo' == "foo"
assert %B"foo" == "foo"
assert %B|foo| == "foo"
assert %B(f#{o}o) == "f\#{o}o"
assert %B(f\no) == "f\\no"
end
test :__b__ do
assert %b(foo) == "foo"
assert %b(f#{:o}o) == "foo"
assert %b(f\no) == "f\no"
end
test :__B__with_heredoc do
assert " f\#{o}o\\n\n" == %B"""
f#{o}o\n
"""
end
test :__b__with_heredoc do
assert " foo\n\n" == %b"""
f#{:o}o\n
"""
end
test :octals do
assert "\123" == "S"
assert "\128" == "\n8"
@@ -88,19 +59,46 @@ bar
test :pattern_match do
s = 16
assert <<a, b|s>> = "foo"
assert <<a, b :: size(s)>> = "foo"
end
test :partial_application do
assert (<< &1, 2 >>).(1) == << 1, 2 >>
assert (<< &1, &2 >>).(1, 2) == << 1, 2 >>
assert (<< &2, &1 >>).(2, 1) == << 1, 2 >>
end
test :bitsyntax_translation do
refb = "sample"
sec_data = "another"
<< size(refb) | 1 - :big - :signed - :integer - {:unit, 8},
refb | :binary,
size(sec_data) | 1 - :big - :signed - :integer - {:unit, 16},
sec_data|:binary >>
<< size(refb) :: [size(1), big, signed, integer, unit(8)],
refb :: binary,
size(sec_data) :: [size(1), big, signed, integer, unit(16)],
sec_data :: binary >>
end
defp is_match?(<<char, _|:binary>>, char) do
defmacrop signed_16 do
quote do
[big, signed, integer, unit(16)]
end
end
defmacrop refb_spec do
quote do
[size(1), big, signed, integer, unit(8)]
end
end
test :bitsyntax_macro do
refb = "sample"
sec_data = "another"
<< size(refb) :: refb_spec,
refb :: binary,
size(sec_data) :: [size(1) | signed_16],
sec_data :: binary >>
end
defp is_match?(<<char, _ :: binary>>, char) do
true
end
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule Bitwise.FunctionsTest do
use ExUnit.Case, async: true
+1 -18
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule CharListTest do
use ExUnit.Case, async: true
@@ -26,21 +26,4 @@ bar '''
assert '\128' == '\n8'
assert '\18' == [1, ?8]
end
test :__C__ do
assert %C(foo) == 'foo'
assert %C[foo] == 'foo'
assert %C{foo} == 'foo'
assert %C'foo' == 'foo'
assert %C"foo" == 'foo'
assert %C|foo| == 'foo'
assert %C(f#{o}o) == 'f\#{o}o'
assert %C(f\no) == 'f\\no'
end
test :__c__ do
assert %c(foo) == 'foo'
assert %c(f#{:o}o) == 'foo'
assert %c(f\no) == 'f\no'
end
end
+31 -2
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule CodeTest do
use ExUnit.Case, async: true
@@ -29,8 +29,22 @@ defmodule CodeTest do
end
test :require do
Code.require_file fixture_path("code_sample"), __FILE__
Code.require_file fixture_path("code_sample.exs")
assert fixture_path("code_sample.exs") in Code.loaded_files
assert Code.require_file(fixture_path("code_sample.exs")) == nil
Code.unload_files [fixture_path("code_sample.exs")]
refute fixture_path("code_sample.exs") in Code.loaded_files
assert Code.require_file(fixture_path("code_sample.exs")) != nil
end
test :path_manipulation do
path = File.expand_path("../binary", __FILE__)
Code.prepend_path path
assert binary_to_list(path) in :code.get_path
Code.delete_path path
refute binary_to_list(path) in :code.get_path
end
test :file do
@@ -42,6 +56,11 @@ defmodule CodeTest do
assert { :error, _ } = Code.string_to_ast("a.1")
end
test :string_to_ast_existing_atoms_only do
assert :badarg = catch_error(Code.string_to_ast(":thereisnosuchatom", existing_atoms_only: true))
assert :badarg = catch_error(Code.string_to_ast!(":thereisnosuchatom", existing_atoms_only: true))
end
test :string_to_ast! do
assert Code.string_to_ast!("1 + 2") == quote line: 1, do: 1 + 2
@@ -72,4 +91,14 @@ defmodule CodeTest do
compile = __MODULE__.__info__(:compile)
assert Keyword.get(compile, :source) != nil
end
test :ensure_loaded? do
assert Code.ensure_loaded?(__MODULE__)
refute Code.ensure_loaded?(Unknown.Module)
end
test :ensure_compiled? do
assert Code.ensure_compiled?(__MODULE__)
refute Code.ensure_compiled?(Unknown.Module)
end
end
+46 -19
View File
@@ -1,23 +1,29 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule DictTest.Common do
defmacro __using__(module) do
quote do
quote location: :keep do
use ExUnit.Case, async: true
test :access do
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
assert dict["first_key"] == 1
assert dict["third_key"] == nil
end
test :new_pairs do
dict = new_dict [{"first key", 1}, {"second key", 2}]
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
assert 2 == Dict.size dict
assert ["first key", "second key"] == List.sort Dict.keys dict
assert ["first_key", "second_key"] == List.sort Dict.keys dict
assert [1, 2] == List.sort Dict.values dict
end
test :new_pairs_with_transform do
dict = new_dict [{1}, {2}, {3}], fn {x} -> { {x}, x } end
dict = new_dict [{1}, {2}, {3}], fn {x} -> { <<x + 64>>, x } end
assert 3 == Dict.size dict
assert [{1}, {2}, {3}] == List.sort Dict.keys dict
assert ["A", "B", "C"] == List.sort Dict.keys dict
assert [1, 2, 3] == List.sort Dict.values dict
end
@@ -62,18 +68,26 @@ defmodule DictTest.Common do
assert dict == Dict.merge dict, dict
assert empty_dict == Dict.merge empty_dict, empty_dict
dict1 = new_dict List.zip ["a", "b", "c"], [1, 2, 3]
dict2 = new_dict List.zip ["a", "c", "d"], [3, :a, 0]
assert new_dict(List.zip ["a", "b", "c", "d"], [3, 2, :a, 0]) == Dict.merge(dict1, dict2)
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
dict2 = new_dict Enum.zip ["a", "c", "d"], [3, :a, 0]
final = new_dict Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0]
assert Dict.merge(dict1, dict2) == final
end
test :merge_with_enum do
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
dict2 = Enum.zip ["a", "c", "d"], [3, :a, 0]
final = new_dict(Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0])
assert Dict.merge(dict1, dict2) == final
end
test :merge_with_function do
dict1 = new_dict List.zip ["a", "b"], [1, 2]
dict2 = new_dict List.zip ["a", "d"], [3, 4]
dict1 = new_dict Enum.zip ["a", "b"], [1, 2]
dict2 = new_dict Enum.zip ["a", "d"], [3, 4]
result = Dict.merge dict1, dict2, fn _k, v1, v2 ->
v1 + v2
end
assert new_dict(List.zip ["a", "b", "d"], [4, 2, 4]) == result
assert new_dict(Enum.zip ["a", "b", "d"], [4, 2, 4]) == result
end
test :has_key do
@@ -100,11 +114,10 @@ defmodule DictTest.Common do
end
defp empty_dict, do: unquote(module).new
defp new_dict({k, v}), do: unquote(module).new {k, v}
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}])
defp new_dict(list), do: unquote(module).new list
defp new_dict(list, transform) when is_function(transform), do: unquote(module).new list, transform
defp new_dict(keys, values), do: unquote(module).new keys, values
defp new_dict(list, transform), do: unquote(module).new list, transform
end
end
end
@@ -113,14 +126,28 @@ defmodule DictTest do
use DictTest.Common, HashDict
test :new do
assert :dict.new == elem(HashDict.new, 2)
assert :dict.new == elem(new_dict([]), 1)
end
end
defmodule OrddictTest do
use DictTest.Common, Orddict
defmodule OrdDictTest do
use DictTest.Common, OrdDict
test :new do
assert [] == elem(Orddict.new, 2)
assert [] == elem(new_dict([]), 1)
end
end
defmodule Binary.DictTest do
use DictTest.Common, Binary.Dict
test :new do
assert [] == elem(new_dict([]), 1)
end
test :merge_mixed do
merged = Dict.merge(new_dict, new_dict([first_key: 13]))
assert merged[:first_key] == 13
assert merged["first_key"] == 13
end
end
+152 -116
View File
@@ -1,9 +1,9 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule EnumTest.Common do
defmodule EnumTest do
use ExUnit.Case, async: true
test :times_with_arity_0 do
test :times do
try do
Process.put(:times_with_arity, nil)
assert Enum.times(0, fn -> Process.put(:times_with_arity, :ok) end) == 0
@@ -15,17 +15,26 @@ defmodule EnumTest.Common do
end
end
test :times_with_arity_1 do
test :times_with_iterator do
try do
assert Enum.times(5, fn x -> Process.put(:times_with_arity, x) end) == 5
assert Process.get(:times_with_arity) == 5
assert Process.get(:times_with_arity) == 4
after
Process.delete(:times_with_arity)
end
end
test :times_with_arity_2 do
assert Enum.times(5, 0, fn acc, x -> acc + x end) == 15
test :times_with_iterator_and_acumulator do
assert Enum.times(5, 0, fn acc, x -> acc + x end) == 10
end
test :reverse do
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
[{ "baz", "bat" }, { "foo", "bar" }]
end
test :count do
assert Enum.count(URI.query_decoder("foo=bar&baz=bat")) == 2
end
end
@@ -52,11 +61,24 @@ defmodule EnumTest.List do
refute Enum.any?([])
end
test :at! do
assert Enum.at!([2,4,6], 0) == 2
assert Enum.at!([2,4,6], 2) == 6
assert_raise Enum.OutOfBoundsError, fn ->
Enum.at!([2,4,6], 4)
end
end
test :count do
assert Enum.count([1,2,3]) == 3
assert Enum.count([]) == 0
end
test :count_fun do
assert Enum.count([1,2,3], fn(x) -> rem(x, 2) == 0 end) == 1
assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
end
test :drop do
assert Enum.drop([1,2,3], 0) == [1,2,3]
assert Enum.drop([1,2,3], 1) == [2,3]
@@ -87,7 +109,7 @@ defmodule EnumTest.List do
test :find_index do
assert Enum.find_index([2,4,6], fn(x) -> rem(x, 2) == 1 end) == nil
assert Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end) == 2
assert Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end) == 1
end
test :empty? do
@@ -166,19 +188,16 @@ defmodule EnumTest.List do
assert Enum.map_reduce([1,2,3], 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2,4,6], 7 }
end
test :nth! do
assert Enum.nth!([2,4,6], 1) == 2
assert Enum.nth!([2,4,6], 3) == 6
assert_raise Enum.OutOfBoundsError, fn ->
Enum.nth!([2,4,6], 5)
end
end
test :partition do
assert Enum.partition([1,2,3], fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
assert Enum.partition([2,4,6], fn(x) -> rem(x, 2) == 0 end) == { [2,4,6], [] }
end
test :reverse do
assert Enum.reverse([]) == []
assert Enum.reverse([1,2,3]) == [3,2,1]
end
test :qsort do
assert Enum.qsort([5,3,2,4,1]) == [1,2,3,4,5]
end
@@ -190,15 +209,19 @@ defmodule EnumTest.List do
assert Enum.split([1,2,3], 3) == { [1,2,3], [] }
assert Enum.split([1,2,3], 4) == { [1,2,3], [] }
assert Enum.split([], 3) == { [], [] }
assert Enum.split([1,2,3], -1) == { [1,2], [3] }
assert Enum.split([1,2,3], -2) == { [1], [2,3] }
assert Enum.split([1,2,3], -3) == { [], [1,2,3] }
assert Enum.split([1,2,3], -10) == { [], [1,2,3] }
end
test :split_with do
assert Enum.split_with([1,2,3], fn(_) -> false end) == { [], [1,2,3] }
assert Enum.split_with([1,2,3], fn(_) -> true end) == { [1,2,3], [] }
assert Enum.split_with([1,2,3], fn(x) -> x > 2 end) == { [], [1,2,3] }
assert Enum.split_with([1,2,3], fn(x) -> x > 3 end) == { [], [1,2,3] }
assert Enum.split_with([1,2,3], fn(x) -> x < 3 end) == { [1,2], [3] }
assert Enum.split_with([], fn(_) -> true end) == { [], [] }
test :split_while do
assert Enum.split_while([1,2,3], fn(_) -> false end) == { [], [1,2,3] }
assert Enum.split_while([1,2,3], fn(_) -> true end) == { [1,2,3], [] }
assert Enum.split_while([1,2,3], fn(x) -> x > 2 end) == { [], [1,2,3] }
assert Enum.split_while([1,2,3], fn(x) -> x > 3 end) == { [], [1,2,3] }
assert Enum.split_while([1,2,3], fn(x) -> x < 3 end) == { [1,2], [3] }
assert Enum.split_while([], fn(_) -> true end) == { [], [] }
end
test :take do
@@ -216,6 +239,15 @@ defmodule EnumTest.List do
assert Enum.take_while([1,2,3], fn(x) -> x <= 3 end) == [1,2,3]
assert Enum.take_while([], fn(_) -> true end) == []
end
test :zip do
assert Enum.zip([:a, :b], [1, 2]) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b], [1, 2, 3, 4]) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b, :c, :d], [1, 2]) == [{:a, 1}, {:b, 2}, {:c, nil}, {:d, nil}]
assert Enum.zip([], [1]) == []
assert Enum.zip([1], []) == [{ 1, nil }]
assert Enum.zip([], []) == []
end
end
defmodule EnumTest.Dict.Common do
@@ -245,6 +277,12 @@ defmodule EnumTest.Dict.Common do
assert Enum.count(unquote(module).new) == 0
end
test :count_fun do
dict = unquote(module).new [{2,2}, {3,4}, {4,6}]
assert Enum.count(dict, fn({x,_}) -> rem(x, 2) == 0 end) == 2
assert Enum.count(unquote(module).new, fn(x) -> rem(x, 2) == 0 end) == 0
end
test :find do
dict = unquote(module).new [a: 1, b: 2, c: 3]
assert nil == Enum.find(dict, fn({_, v}) -> v == 0 end)
@@ -309,10 +347,10 @@ defmodule EnumTest.Dict.Common do
test :join do
dict = unquote(module).new [a: 1, b: 2, c: 3]
assert_raise UndefinedFunctionError, fn ->
assert_raise Protocol.UndefinedError, fn ->
Enum.join dict, ","
end
assert_raise UndefinedFunctionError, fn ->
assert_raise Protocol.UndefinedError, fn ->
Enum.join dict, ','
end
end
@@ -354,90 +392,48 @@ defmodule EnumTest.Dict.Common do
end
end
defmodule EnumTest.HashDict do
use EnumTest.Dict.Common, HashDict
defmodule EnumTest.OrdDict do
use EnumTest.Dict.Common, OrdDict
test :drop do
assert_raise Protocol.UndefinedError, fn ->
Enum.drop HashDict.new, 5
end
end
test :drop_while do
assert_raise Protocol.UndefinedError, fn ->
Enum.drop_while HashDict.new, fn(x) -> x end
end
end
test :first do
assert_raise Protocol.UndefinedError, fn ->
Enum.first HashDict.new
end
end
test :split do
assert_raise Protocol.UndefinedError, fn ->
Enum.split HashDict.new, 5
end
end
test :split_with do
assert_raise Protocol.UndefinedError, fn ->
Enum.split_with HashDict.new, fn(x) -> x end
end
end
test :take do
assert_raise Protocol.UndefinedError, fn ->
Enum.take HashDict.new, 5
end
end
test :take_while do
assert_raise Protocol.UndefinedError, fn ->
Enum.take_while HashDict.new, fn(x) -> x end
end
end
end
defmodule EnumTest.Orddict do
use EnumTest.Dict.Common, Orddict
test :drop do
dict = Orddict.new [a: 1, b: 2, c: 3]
dict = OrdDict.new [a: 1, b: 2, c: 3]
assert Enum.drop(dict, 2) == [c: 3]
assert Enum.drop(dict, 3) == []
assert Enum.drop(dict, 4) == []
end
test :drop_while do
dict = Orddict.new [a: 1, b: 2, c: 3]
dict = OrdDict.new [a: 1, b: 2, c: 3]
assert Enum.drop_while(dict, fn({_k, v}) -> v < 3 end) == [c: 3]
end
test :first do
dict = Orddict.new []
dict = OrdDict.new []
assert Enum.first(dict) == nil
dict = Orddict.new [a: 1, b: 2, c: 3]
dict = OrdDict.new [a: 1, b: 2, c: 3]
assert Enum.first(dict) == {:a, 1}
end
test :split do
dict = Orddict.new [a: 1, b: 2, c: 3]
dict = OrdDict.new [a: 1, b: 2, c: 3]
assert Enum.split(dict, 1) == { [a: 1], [b: 2, c: 3] }
assert Enum.split(dict, 0) == { [], [a: 1, b: 2, c: 3] }
assert Enum.split(dict, 3) == { [a: 1, b: 2, c: 3], [] }
assert Enum.split(dict, 4) == { [a: 1, b: 2, c: 3], [] }
assert Enum.split(dict, -1) == { [a: 1, b: 2], [c: 3] }
assert Enum.split(dict, -2) == { [a: 1], [b: 2, c: 3] }
assert Enum.split(dict, -3) == { [], [a: 1, b: 2, c: 3] }
assert Enum.split(dict, -10) == { [], [a: 1, b: 2, c: 3] }
end
test :split_with do
dict = Orddict.new [a: 1, b: 3, c: 2, d: 4]
assert Enum.split_with(dict, fn({_k, v}) -> rem(v, 2) == 1 end) == { [a: 1, b: 3], [c: 2, d: 4] }
test :split_while do
dict = OrdDict.new [a: 1, b: 3, c: 2, d: 4]
assert Enum.split_while(dict, fn({_k, v}) -> rem(v, 2) == 1 end) == { [a: 1, b: 3], [c: 2, d: 4] }
end
test :take do
dict = Orddict.new [a: 1, b: 2, c: 3, d: 4]
dict = OrdDict.new [a: 1, b: 2, c: 3, d: 4]
assert Enum.take(dict, 2) == [a: 1, b: 2]
assert Enum.take(dict, 0) == []
assert Enum.take(dict, 4) == [a: 1, b: 2, c: 3, d: 4]
@@ -445,7 +441,7 @@ defmodule EnumTest.Orddict do
end
test :take_while do
dict = Orddict.new [a: 1, b: 3, c: 2, d: 4]
dict = OrdDict.new [a: 1, b: 3, c: 2, d: 4]
assert Enum.take_while(dict, fn({_k, v}) -> rem(v, 2) == 1 end) == [a: 1, b: 3]
assert Enum.take_while(dict, fn({_k, v}) -> v < 10 end) == [a: 1, b: 3, c: 2, d: 4]
assert Enum.take_while(dict, fn({_k, v}) -> v < 1 end) == []
@@ -475,7 +471,22 @@ defmodule EnumTest.Range do
assert Enum.any?(range, &1 > 3)
range = Range.new(first: 1, last: 0)
refute Enum.any?(range)
assert Enum.any?(range)
end
test :at! do
assert Enum.at!(2..6, 0) == 2
assert Enum.at!(2..6, 4) == 6
assert Enum.at!(-2..-6, 0) == -2
assert Enum.at!(-2..-6, 4) == -6
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.at!(2..6, 8)
end
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.at!(-2..-6, 8)
end
end
test :count do
@@ -485,6 +496,13 @@ defmodule EnumTest.Range do
assert Enum.count(range) == 1
end
test :count_fun do
range = Range.new(first: 1, last: 5)
assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 2
range = Range.new(first: 1, last: 1)
assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 0
end
test :drop do
range = Range.new(first: 1, last: 3)
assert Enum.drop(range, 0) == [1,2,3]
@@ -506,7 +524,7 @@ defmodule EnumTest.Range do
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == []
range = Range.new(first: 1, last: 0)
assert Enum.drop_while(range, fn(_) -> false end) == []
assert Enum.drop_while(range, fn(_) -> false end) == [1,0]
end
test :find do
@@ -522,12 +540,12 @@ defmodule EnumTest.Range do
test :find_index do
range = Range.new(first: 2, last: 6)
assert Enum.find_index(range, fn(x) -> rem(x, 2) == 1 end) == 2
assert Enum.find_index(range, fn(x) -> rem(x, 2) == 1 end) == 1
end
test :empty? do
range = Range.new(first: 1, last: 0)
assert Enum.empty?(range)
refute Enum.empty?(range)
range = Range.new(first: 1, last: 2)
refute Enum.empty?(range)
@@ -535,7 +553,7 @@ defmodule EnumTest.Range do
test :first do
range = Range.new(first: 1, last: 0)
assert Enum.first(range) == nil
assert Enum.first(range) == 1
range = Range.new(first: 1, last: 2)
assert Enum.first(range) == 1
@@ -552,6 +570,15 @@ defmodule EnumTest.Range do
after
Process.delete(:enum_test_each)
end
try do
range = Range.new(first: -1, last: -3)
assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
assert Process.get(:enum_test_each) == -6
after
Process.delete(:enum_test_each)
end
end
test :filter do
@@ -579,7 +606,7 @@ defmodule EnumTest.Range do
test :reduce do
range = Range.new(first: 1, last: 0)
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 1
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 2
range = Range.new(first: 1, last: 3)
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 7
@@ -587,7 +614,7 @@ defmodule EnumTest.Range do
test :join_with_bin do
range = Range.new(first: 1, last: 0)
assert Enum.join(range, " = ") == ""
assert Enum.join(range, " = ") == "1 = 0"
range = Range.new(first: 1, last: 3)
assert Enum.join(range, " = ") == "1 = 2 = 3"
@@ -596,7 +623,7 @@ defmodule EnumTest.Range do
test :join_with_list do
range = Range.new(first: 1, last: 0)
assert Enum.join(range, ' = ') == ''
assert Enum.join(range, ' = ') == '1 = 0'
range = Range.new(first: 1, last: 3)
assert Enum.join(range, ' = ') == '1 = 2 = 3'
@@ -604,7 +631,7 @@ defmodule EnumTest.Range do
test :map_join_with_bin do
range = Range.new(first: 1, last: 0)
assert Enum.map_join(range, " = ", &1 * 2) == ""
assert Enum.map_join(range, " = ", &1 * 2) == "2 = 0"
range = Range.new(first: 1, last: 3)
assert Enum.map_join(range, " = ", &1 * 2) == "2 = 4 = 6"
@@ -613,37 +640,28 @@ defmodule EnumTest.Range do
test :map_join_with_list do
range = Range.new(first: 1, last: 0)
assert Enum.map_join(range, ' = ', &1 * 2) == ''
assert Enum.map_join(range, ' = ', &1 * 2) == '2 = 0'
range = Range.new(first: 1, last: 3)
assert Enum.map_join(range, ' = ', &1 * 2) == '2 = 4 = 6'
end
test :map do
range = Range.new(first: 1, last: 0)
assert Enum.map(range, fn x -> x * 2 end) == []
range = Range.new(first: 1, last: 3)
assert Enum.map(range, fn x -> x * 2 end) == [2,4,6]
range = Range.new(first: -1, last: -3)
assert Enum.map(range, fn x -> x * 2 end) == [-2,-4,-6]
end
test :map_reduce do
range = Range.new(first: 1, last: 0)
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [], 1 }
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 0], 2 }
range = Range.new(first: 1, last: 3)
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2,4,6], 7 }
end
test :nth! do
assert Enum.nth!(2..6, 1) == 2
assert Enum.nth!(2..6, 5) == 6
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.nth!(2..6, 9)
end
end
test :partition do
range = Range.new(first: 1, last: 3)
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
@@ -656,21 +674,25 @@ defmodule EnumTest.Range do
assert Enum.split(range, 2) == { [1,2], [3] }
assert Enum.split(range, 3) == { [1,2,3], [] }
assert Enum.split(range, 4) == { [1,2,3], [] }
assert Enum.split(range, -1) == { [1,2], [3] }
assert Enum.split(range, -2) == { [1], [2,3] }
assert Enum.split(range, -3) == { [], [1,2,3] }
assert Enum.split(range, -10) == { [], [1,2,3] }
range = Range.new(first: 1, last: 0)
assert Enum.split(range, 3) == { [], [] }
assert Enum.split(range, 3) == { [1,0], [] }
end
test :split_with do
test :split_while do
range = Range.new(first: 1, last: 3)
assert Enum.split_with(range, fn(_) -> false end) == { [], [1,2,3] }
assert Enum.split_with(range, fn(_) -> true end) == { [1,2,3], [] }
assert Enum.split_with(range, fn(x) -> x > 2 end) == { [], [1,2,3] }
assert Enum.split_with(range, fn(x) -> x > 3 end) == { [], [1,2,3] }
assert Enum.split_with(range, fn(x) -> x < 3 end) == { [1,2], [3] }
assert Enum.split_while(range, fn(_) -> false end) == { [], [1,2,3] }
assert Enum.split_while(range, fn(_) -> true end) == { [1,2,3], [] }
assert Enum.split_while(range, fn(x) -> x > 2 end) == { [], [1,2,3] }
assert Enum.split_while(range, fn(x) -> x > 3 end) == { [], [1,2,3] }
assert Enum.split_while(range, fn(x) -> x < 3 end) == { [1,2], [3] }
range = Range.new(first: 1, last: 0)
assert Enum.split_with(range, fn(_) -> true end) == { [], [] }
assert Enum.split_while(range, fn(_) -> true end) == { [1, 0], [] }
end
test :take do
@@ -682,7 +704,7 @@ defmodule EnumTest.Range do
assert Enum.take(range, 4) == [1,2,3]
range = Range.new(first: 1, last: 0)
assert Enum.take(range, 3) == []
assert Enum.take(range, 3) == [1,0]
end
test :take_while do
@@ -694,4 +716,18 @@ defmodule EnumTest.Range do
range = Range.new(first: 1, last: 0)
assert Enum.take_while([], fn(_) -> true end) == []
end
end
test :zip do
assert Enum.zip([:a, :b], 1..2) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b], 1..4) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b, :c, :d], 1..2) == [{:a, 1}, {:b, 2}, {:c, nil}, {:d, nil}]
assert Enum.zip(1..2, [:a, :b]) == [{1, :a}, {2, :b}]
assert Enum.zip(1..4, [:a, :b]) == [{1, :a}, {2, :b}, {3, nil}, {4, nil}]
assert Enum.zip(1..2, [:a, :b, :c, :d]) == [{1, :a}, {2, :b}]
assert Enum.zip(1..2, 1..2) == [{1, 1}, {2, 2}]
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}, {3, nil}, {4, nil}]
assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
end
end
+2 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule Kernel.ExceptionTest do
use ExUnit.Case, async: true
@@ -28,6 +28,7 @@ defmodule Kernel.ExceptionTest do
end
test :format_module_function_arity do
assert Exception.format_module_fun_arity Foo, nil, 1 == "Foo.nil/1"
assert Exception.format_module_fun_arity Foo, :bar, 1 == "Foo.bar/1"
assert Exception.format_module_fun_arity Foo, :bar, [] == "Foo.bar()"
assert Exception.format_module_fun_arity :foo, :bar, [1,2] == ":foo.bar(1, 2)"
+46 -4
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule FileTest do
import PathHelpers
@@ -8,6 +8,20 @@ defmodule FileTest do
defmodule Cp do
use ExUnit.Case
##
## cp_r/c is managed in setup/teardown because if it is stored in
## the repository, reltool can't build a release
##
def setup do
src = fixture_path("cp_r")
:file.make_symlink 'certainly/invalid', File.join([src, "c"])
end
def teardown do
src = fixture_path("cp_r")
File.rm File.join([src, "c"])
end
test :cp_with_src_file_and_dest_file do
src = fixture_path("foo.txt")
dest = tmp_path("sample.txt")
@@ -350,6 +364,20 @@ defmodule FileTest do
end
end
test :cp_preserves_mode do
src = fixture_path("cp_mode")
dest = tmp_path("tmp/cp_mode")
File.cp! src, dest
File.Stat[mode: src_mode] = File.stat! src
File.Stat[mode: dest_mode] = File.stat! dest
assert src_mode == dest_mode
# on overwrite
File.cp! src, dest, fn(_,_) -> true end
File.Stat[mode: src_mode] = File.stat! src
File.Stat[mode: dest_mode] = File.stat! dest
assert src_mode == dest_mode
end
end
defmodule Paths do
@@ -756,6 +784,20 @@ defmodule FileTest do
defmodule Rm do
use ExUnit.Case
##
## cp_r/c is managed in setup/teardown because if it is stored in
## the repository, reltool can't build a release
##
def setup do
src = fixture_path("cp_r")
:file.make_symlink 'certainly/invalid', File.join([src, "c"])
end
def teardown do
src = fixture_path("cp_r")
File.rm File.join([src, "c"])
end
test :rm_file do
fixture = tmp_path("tmp_test.txt")
File.write(fixture, "test")
@@ -938,7 +980,7 @@ defmodule FileTest do
iterator = File.iterator(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.write target, Regex.replace_all(%r/"/, line, "'")
IO.puts target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
@@ -955,7 +997,7 @@ defmodule FileTest do
{ :ok, iterator } = File.iterator(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.write target, Regex.replace_all(%r/"/, line, "'")
IO.puts target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
@@ -972,7 +1014,7 @@ defmodule FileTest do
iterator = File.iterator!(src)
File.open dest, [:write], fn(target) ->
Enum.each iterator, fn(line) ->
IO.write target, Regex.replace_all(%r/"/, line, "'")
IO.puts target, Regex.replace(%r/"/, line, "'")
end
end
assert File.read(dest) == { :ok, "FOO\n" }
View File
-1
View File
@@ -1 +0,0 @@
certainly/invalid
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
defmodule IOTest do
use ExUnit.Case, async: true
+2 -2
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule Kernel.AliasTest.Nested do
def value, do: 1
@@ -10,7 +10,7 @@ defmodule Kernel.AliasTest do
use ExUnit.Case, async: true
test :alias_erlang do
alias Erlang.lists, as: MyList
alias :lists, as: MyList
assert MyList.flatten([1,[2],3]) == [1,2,3]
assert Elixir.MyList.Bar == :"Elixir-MyList-Bar"
assert MyList.Bar == :"Elixir-lists-Bar"
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule Kernel.CaseTest do
use ExUnit.Case, async: true
+8 -8
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule Kernel.CLI.InitTest do
use ExUnit.Case, async: true
@@ -18,7 +18,7 @@ defmodule Kernel.CLI.OptionParsingTest do
test :path do
root = fixture_path("../../..") /> to_char_list
list = elixir('-e "IO.inspect Erlang.code.get_path" -pa "#{root}/*" -pz "#{root}/lib/*"')
list = elixir('-e "IO.inspect :code.get_path" -pa "#{root}/*" -pz "#{root}/lib/*"')
{ path, _ } = Code.eval list, []
# pa
@@ -52,8 +52,8 @@ defmodule Kernel.CLI.ErrorTest do
import PathHelpers
test :code_error do
assert Erlang.string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
assert Erlang.string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
assert :string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
assert :string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
# It does not catch exits with integers nor strings...
assert elixir('-e "exit 1"') == ''
@@ -66,9 +66,9 @@ defmodule Kernel.CLI.SyntaxErrorTest do
test :syntax_code_error do
message = '** (TokenMissingError) nofile:1: syntax error: expression is incomplete'
assert Erlang.string.str(message, elixir('-e "[1,2"')) == 0
assert :string.str(message, elixir('-e "[1,2"')) == 0
message = '** (SyntaxError) nofile:1: syntax error before: \'end\''
assert Erlang.string.str(message, elixir('-e "case 1 end"')) == 0
assert :string.str(message, elixir('-e "case 1 end"')) == 0
end
end
@@ -90,7 +90,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
test :compile_code do
output = elixirc('#{fixture_path("parallel_compiler")} -o #{tmp_path}')
assert Erlang.string.str(output, 'message_from_foo') > 0,
assert :string.str(output, 'message_from_foo') > 0,
"Expected #{inspect output} to contain 'message_from_foo'"
assert File.regular?(tmp_path "Elixir-Foo.beam")
assert File.regular?(tmp_path "Elixir-Bar.beam")
@@ -100,7 +100,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
output = elixirc('#{fixture_path("parallel_deadlock")} -o #{tmp_path}')
foo = '== Compilation error on file #{fixture_path "parallel_deadlock/foo.ex"} (undefined module Bar) =='
bar = '== Compilation error on file #{fixture_path "parallel_deadlock/bar.ex"} (undefined module Foo) =='
assert Erlang.string.str(output, foo) > 0 or Erlang.string.str(output, bar) > 0,
assert :string.str(output, foo) > 0 or :string.str(output, bar) > 0,
"Expected #{inspect output} to contain #{inspect foo} or #{inspect bar}"
end
end
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule Kernel.ComprehensionTest do
use ExUnit.Case, async: true
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule Kernel.DefClause do
defmacro defclause(expr, block) do
@@ -11,6 +11,12 @@ defmodule Kernel.ClausesTest do
import Kernel.DefClause
# This is just to ensure we won't have warnings
# for clauses with match after default.
def clause_with_match_after_default(x, y // nil, { z, w }) do
x + y + z + w
end
# This is simply testing the warning about
# function overriden consider the current stack
def foo(0), do: 0
+1 -1
View File
@@ -1,4 +1,4 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule Kernel.DocTest do
use ExUnit.Case

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