Compare commits

...
805 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
José Valim 01a575eb2c Release v0.6.0 2012-08-01 08:32:20 +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
José Valim 7040851a62 Do not use stderr in IEx.Helpers 2012-07-31 16:05:20 +02:00
José Valim d857b7b3f5 MixFile -> Mixfile 2012-07-31 15:37:04 +02:00
José Valim e88ef6343e update release_docs make task 2012-07-31 15:16:08 +02:00
José Valim 9ddf626d51 Support -S as an option, closes #415 2012-07-31 14:45:35 +02:00
José Valim 8dde5bee21 Fix build 2012-07-31 13:36:55 +02:00
José Valim 05e52f9437 Make mix an Elixir script 2012-07-31 13:29:34 +02:00
José Valim 30d9699153 Add supported use cases to IEx.Remsh docs 2012-07-31 13:18:16 +02:00
José Valim ca3cbf7e17 Merge pull request #423 from thomasc/fix/remote-iex-autocomplete
Fix IEx remsh autocompletion when the connecting node does not have Elix...
2012-07-31 03:41:55 -07:00
thomasc c94f5fded6 Use an Elixir module for the IEx remsh autocomplete helper 2012-07-31 12:25:20 +02:00
thomasc 7be4b95b5c Fix IEx remsh autocompletion when the connecting node does not have Elixir 2012-07-31 10:33:01 +02:00
José Valim f9fc540e2d Update Mix.SCM docs 2012-07-31 10:00:44 +02:00
José Valim f3bdcb49cb Ensure deps.get also updates locks 2012-07-31 09:43:09 +02:00
José Valim c229bd9239 Fix div and rem docs 2012-07-31 01:17:53 +02:00
José Valim ffc370e53d Pass some options down to erl, accept --erl as arg 2012-07-31 01:08:11 +02:00
José Valim 8d2feae582 Merge pull request #420 from thomasc/features/node-name-in-iex-prompt
Print node name in IEx prompt
2012-07-30 15:55:45 -07:00
José Valim 6d62e473aa Fix build, do not match on line 2012-07-30 23:30:10 +02:00
José Valim 188a7b23cc Accept tuples in bin specifiers 2012-07-30 23:05:03 +02:00
thomasc 0252a4f812 Print node name in IEx prompt 2012-07-30 18:44:07 +02:00
José Valim 1fe5cad980 Do not rely on bashisms 2012-07-30 17:06:52 +02:00
José Valim 7fa6576f38 Tidy up remsh support in iex via --remsh option, closes #368 2012-07-30 14:34:31 +02:00
José Valim 7685dbca06 Add Mix.loadpaths shortcut, closes #417 2012-07-30 12:52:50 +02:00
José Valim 7381c5152f Check if lock is out of date 2012-07-30 12:26:44 +02:00
José Valim 8b601c2e54 Refactor common logic to Mix.Local 2012-07-30 10:58:39 +02:00
José Valim c6d37b9b76 Allow :test_helper to be set to nil, closes #419 2012-07-30 10:47:56 +02:00
José Valim 2841137382 Be sure to expand rebar path before using it 2012-07-30 10:33:51 +02:00
José Valim a7a2178c6a Pass deps_dir by default to rebar, closes #414 2012-07-30 10:19:55 +02:00
José Valim f994bf46aa Support raw dependencies, closes #410 2012-07-30 10:15:22 +02:00
José Valim 4be70753c3 Allow a SCM to tell which options it consumes and configure it further 2012-07-30 09:44:45 +02:00
José Valim 6c319466f5 Allow custom SCM to be added, closes #411 2012-07-30 09:35:42 +02:00
José Valim 6c130d77ad Do not locate app files if there is no requirement, closes #409
If there is no requirement, we are not going to locale the .app file.
This allows C extensions and others to be added as dependencies.
We still do not support .app files at custom locations, but this
can be done at some later point.
2012-07-30 09:20:32 +02:00
José Valim 25c24a7ec3 Bring defdelegate back with new syntax, closes #412 2012-07-30 09:12:58 +02:00
José Valim 329b49e9ee Get rid of -i shortcut for ignore module conflict 2012-07-29 21:40:59 +02:00
José Valim b5331dd7c7 Initial work on remsh
This also ensures that we can start Elixir shells (remote or not)
from the Ctrl+G setting passing 'Elixir-IEx' as the shell name.
2012-07-29 17:27:55 +02:00
José Valim 8f520e9735 Fix the build
When first compiled, kernel does not have docs, so defmodule is not available
2012-07-29 15:28:42 +02:00
José Valim c611b397c0 There is no need to define macros using the atomic function name 2012-07-29 15:24:38 +02:00
José Valim bbd8f5ad3e Fix the build 2012-07-29 15:12:22 +02:00
José Valim cd5dacc612 Deprecate defdelegate 2012-07-29 14:36:25 +02:00
José Valim 9aba2c1260 Move some functions from Process to Node module 2012-07-29 14:13:41 +02:00
José Valim cfc5d012ba Add deps.unlock 2012-07-28 20:53:40 +02:00
José Valim d61c930d5f Pass no check when compiling subprojects 2012-07-28 20:41:36 +02:00
José Valim 042428098a Improve and move option parser to OptionParser root
Added support to flags, --no switches and --key=value switches.
2012-07-28 20:39:37 +02:00
José Valim 8881d5c545 Ensure repeatability in repos 2012-07-28 19:15:50 +02:00
José Valim 4a08bc8276 Initial work on mix.lock 2012-07-28 18:19:54 +02:00
José Valim 10eb8f4963 Split default template around 2012-07-28 14:38:24 +02:00
José Valim 1539de9d9a Support ! inside guards, closes #401 2012-07-28 14:18:14 +02:00
José Valim 104f64f8eb Better zip packaging 2012-07-28 12:00:32 +02:00
José Valim 83a078da08 Do not use tty on win32 fmaily 2012-07-28 11:32:29 +02:00
José Valim cf80551cc3 Escaping for bat files 2012-07-28 11:27:05 +02:00
José Valim a36816f1aa Quote all the paths in sh files 2012-07-28 11:21:01 +02:00
José Valim 0f1ac4ec54 Merge pull request #406 from k1complete/master
add mix in 'make release_erl'
2012-07-28 00:23:45 -07:00
k1complete 9936d93c74 include mix in make release_erl 2012-07-28 09:28:36 +09:00
José Valim cd009eaf32 Do not rely on Code.loaded_files, get the proper source 2012-07-27 13:33:00 +02:00
José Valim b731598d75 Merge pull request #400 from thomasc/features/code-reload
Add reload helper to iex
2012-07-27 04:21:22 -07:00
thomasc 578761428e Update the reload iex helper documentation 2012-07-27 11:55:50 +02:00
thomasc d9d642686d Add reload helper to iex 2012-07-27 11:46:33 +02:00
José Valim 73eabd8b21 IEx.Helpers improvements. Get the proper maximum size in m, remove m/1 (we need to format it better), improve docs 2012-07-27 10:12:29 +02:00
José Valim 47a82fc71c Properly convert fn and partial to binary 2012-07-27 09:26:22 +02:00
José Valim c698c1ef11 Update CHANGELOG 2012-07-27 09:26:22 +02:00
José Valim dffea0f299 Merge pull request #399 from thomasc/fix/erlang-alias-function-list-completion
Fix function list expansion for modules called using the Erlang alias
2012-07-27 00:08:19 -07:00
thomasc db32e550c4 Fix function list expansion for modules called using the Erlang alias 2012-07-27 08:04:32 +02:00
José Valim c6c3703053 Avoid whitelisting Elixir on Code.ensure_loaded
In practice, Erlang is going to throw warnings for
any module in upcase that we try to load but a downcased
version exists. So it makes no sense to special case
one specific module.
2012-07-26 22:53:01 +02:00
José Valim ec59659805 Do not show nested Elixir and Erlang modules 2012-07-26 22:50:55 +02:00
José Valim 336ff3ba8d Test that Elixir and Erlang appear on E autocomplete 2012-07-26 22:28:43 +02:00
José Valim b481d14732 Ensure we can autocomplete self, do not trigger errors on ensure_loaded(Elixir) 2012-07-26 22:28:43 +02:00
José Valim e344b6b44c Do not print functions on d() since we now have autocomplete 2012-07-26 22:28:43 +02:00
José Valim 120cbd4876 Autocomplete kernel calls 2012-07-26 22:28:42 +02:00
José Valim e730bd6d0a Allow autocomplete of Erlang. and Erlang.mod. 2012-07-26 22:28:42 +02:00
José Valim eadbf16fa9 Allow Elixir. autocompletion, try to force module loading 2012-07-26 22:28:42 +02:00
José Valim 1508222d85 Properly expand aliases 2012-07-26 22:28:42 +02:00
José Valim fb255e4f82 Ensure IEx.Helpers is loaded before getting funs 2012-07-26 22:28:42 +02:00
thomasc 63e954d32b Add macro support to IEx.Autocomplete 2012-07-26 22:28:42 +02:00
thomasc 5c357e5cff Add tests for IEx.Autocomplete 2012-07-26 22:28:42 +02:00
thomasc e5be7e0149 Cleanup function access in IEx.Autocomplete 2012-07-26 22:28:42 +02:00
thomasc 68fa9d818a Add Elixir autocompletion to iex 2012-07-26 22:28:42 +02:00
José Valim 2992669e17 Move common deps logic to Mix.Deps 2012-07-26 18:28:44 +02:00
José Valim 85df13db77 Add a Mix.Dep record 2012-07-26 18:25:57 +02:00
José Valim 814a2d525a Avoid compiling deps that were not retrieved 2012-07-26 18:11:54 +02:00
José Valim be2bffb320 Adjustments for release 2012-07-26 17:56:30 +02:00
José Valim 99965ab804 Improve docs for ExUnit 2012-07-26 13:25:16 +02:00
José Valim 30e86ccf07 Optimize if/cond clauses when possible, rely more on erl warnings 2012-07-26 11:21:06 +02:00
José Valim 852e846a47 Use if and || in Enum when possible 2012-07-26 10:01:19 +02:00
José Valim 1cd5613ac6 Optimize if and cond to avoid casting to boolean 2012-07-26 09:50:31 +02:00
José Valim 5f888b71ca No need to special case def clauses anymore 2012-07-26 09:28:26 +02:00
José Valim 05fb269b5b Add Mix to changelog 2012-07-26 09:02:23 +02:00
José Valim f69c2e80fb Add mix local.uninstall 2012-07-25 19:43:39 +02:00
José Valim e428227d7e Add mix local listing 2012-07-25 19:36:37 +02:00
José Valim 512ab80f81 Initial work on mix local.install 2012-07-25 19:04:50 +02:00
José Valim 138dc1d42a Solve conflicts in Makefile 2012-07-25 11:47:58 +02:00
José Valim c4c723f458 Add Regex.compile! besides Regex.compile 2012-07-25 11:39:59 +02:00
José Valim a9cf7a0215 Add a smoke test for mix new 2012-07-25 11:30:31 +02:00
José Valim b93adc78b8 Add unit tests for Mix.Generator 2012-07-25 11:23:42 +02:00
José Valim 72dd2507b0 Initial work on mix new 2012-07-25 10:34:44 +02:00
José Valim 895b50afb9 Small improvements to elixir_def 2012-07-25 08:58:55 +02:00
José Valim 3a722d58aa Add parse_head besides parse in options parser 2012-07-25 08:58:54 +02:00
José Valim e3c2368c7b Add camelize and underscore to mix 2012-07-25 08:58:54 +02:00
Rafael Mendonça França 754d8c4f27 Fix typo 2012-07-25 00:26:44 -03:00
José Valim 1aa4f2b31a Other small improvements to mix 2012-07-24 17:23:38 +02:00
José Valim c3e5380a98 Basic mix docs 2012-07-24 13:37:58 +02:00
José Valim 25aa76df5f Add mix do 2012-07-24 13:35:21 +02:00
José Valim 523127cde8 Improve more exceptions for unmatched tokens 2012-07-24 13:29:53 +02:00
José Valim 152c784a07 Add binary_part to the list of guard functions 2012-07-24 13:04:33 +02:00
José Valim 10d021f1cb Add mix clean and deps.clean 2012-07-24 12:05:47 +02:00
José Valim 14bf6c84f7 Improve assertion docs 2012-07-24 11:32:00 +02:00
José Valim 5c6bf0856c Clean up a couple more assertions 2012-07-24 11:16:57 +02:00
José Valim fa5c3a2dea Reduce mix help output, improve mix compile --list 2012-07-24 10:43:11 +02:00
José Valim fa449e842a Revert "Do not expand prepend and append paths"
This reverts commit 20d96a0d77.
2012-07-23 21:56:48 +02:00
José Valim bdbc9f38e0 Update all except unavailable 2012-07-23 21:25:43 +02:00
José Valim 20d96a0d77 Do not expand prepend and append paths 2012-07-23 21:02:04 +02:00
José Valim 4775d60d00 Add deps.update 2012-07-23 20:59:04 +02:00
José Valim 15defaab04 Show a warning instead of exception on module redefinition, closes #396 2012-07-23 18:21:12 +02:00
José Valim bcee404edb Expect aliases as part of many options 2012-07-23 13:31:25 +02:00
José Valim 576492ae48 Add Code.ensure_compiled 2012-07-23 09:51:15 +02:00
José Valim 4e83a15dee Cosmetic changes 2012-07-22 13:20:09 +02:00
José Valim 117d09509c Rename __MAIN__ to Elixir, closes #390 2012-07-22 12:49:36 +02:00
José Valim 7d1ef13f17 Rename Elixir.Builtin to Kernel, according to #390 2012-07-22 11:23:35 +02:00
José Valim 6a7602d974 Avoid nil conversion if expression is known to return a boolean 2012-07-22 10:30:16 +02:00
José Valim 1077e64126 Deprecate Module.function_defined? in favor of Module.defines? 2012-07-21 23:32:16 +02:00
José Valim 61a27d1be6 Remove the need for special <& &> tags, closes #393 2012-07-21 18:40:57 +02:00
José Valim 95e1faae56 Merge branch 'uniq_fn' 2012-07-21 13:44:58 +02:00
José Valim d35d0f41c8 Clean up fn implementation 2012-07-21 13:40:02 +02:00
José Valim 661ef4377b Properly wrap fn/do end in tokenizer 2012-07-21 13:30:18 +02:00
José Valim 4d715a3a41 Improve coverage and implementaiton of function 2012-07-21 12:41:29 +02:00
José Valim 175e90d6af Initial work on removing fn ambiguity 2012-07-21 12:31:03 +02:00
José Valim af167fbd11 Revert "Use more explicit naming in module functions"
This reverts commit 390f194b24.
2012-07-21 10:27:19 +02:00
José Valim cc99da19dc Simplify EEx.Tokenizer 2012-07-20 23:04:05 +02:00
José Valim 94d5c92d34 Support backreferences in regex 2012-07-20 21:05:35 +02:00
José Valim efd161d145 Add File.iterator 2012-07-20 20:23:28 +02:00
José Valim 9a6d15af53 Warn when unsupported macro is invoked inside guards 2012-07-20 17:53:34 +02:00
José Valim 42c1915ad3 Improve error message when calling local inside guards 2012-07-20 16:09:43 +02:00
José Valim 49501ef59e Improve error messages with terminators 2012-07-20 13:04:33 +02:00
José Valim f6d869cb96 Support terminators in tokenizer 2012-07-20 12:07:45 +02:00
José Valim 90650b334e Improve error messages for unterminated strings, sigils and heredocs 2012-07-20 11:38:49 +02:00
José Valim d984f4b545 Typespecs now support macros, closes #385 2012-07-20 10:29:55 +02:00
José Valim 7d98ae18f9 Simply use private macros for defining dicts 2012-07-20 09:42:46 +02:00
José Valim 390f194b24 Use more explicit naming in module functions 2012-07-20 09:31:25 +02:00
José Valim ec3c485f93 No need to define location 2012-07-19 21:41:00 +02:00
José Valim 504dd24934 No need to make location part of the behavior, we can get it from compile info 2012-07-19 21:25:48 +02:00
José Valim ea1b07e81e Do not automatically forward tasks, move some logic to Mix.SCM 2012-07-19 18:18:12 +02:00
José Valim 4880dfe424 Make dependencies available when compiling other dependencies 2012-07-19 17:51:49 +02:00
José Valim db89ecfc7d Use the project stack instead of shelling out 2012-07-19 17:48:23 +02:00
José Valim b46f45b940 Fix builds 2012-07-19 17:33:20 +02:00
José Valim b74e67dd37 Another attempt to fix build 2012-07-19 16:18:37 +02:00
José Valim 117f0b0ea8 Try to get more info on the build failure 2012-07-19 16:13:23 +02:00
José Valim c9756b55f2 Add run and deps.loadpaths to mix 2012-07-19 16:00:31 +02:00
José Valim 42c39ed93c Improve docs for assert_received 2012-07-19 14:03:13 +02:00
José Valim ea01976063 Add deps.compile 2012-07-19 14:00:39 +02:00
José Valim bc817b511b Add deps.get 2012-07-19 13:22:36 +02:00
José Valim 3e1bd2bfaa Add deps.check 2012-07-19 11:46:19 +02:00
José Valim 8736dc61eb Initial work on dependencies 2012-07-19 10:49:20 +02:00
José Valim 845628df66 Generated .app should also include applications key 2012-07-19 08:55:51 +02:00
José Valim ba17e8ac78 Use mix to generate .app files 2012-07-19 08:38:26 +02:00
José Valim f80ec44205 Rename Mix.Project.config -> Mix.project, Mix.Shell.Test -> Mix.Shell.Process 2012-07-19 08:28:02 +02:00
José Valim 9c4c9e8bb0 Ensure there is no ambiguity in tuples 2012-07-18 20:57:07 +02:00
José Valim f3f39927ac Ensure default task works 2012-07-18 20:41:46 +02:00
José Valim bfe81a1d9e Specs for app generation 2012-07-18 14:19:19 +02:00
José Valim e9c4ea2617 Initial version of compile.app 2012-07-18 13:28:11 +02:00
José Valim 742486d112 Do not convert underscores to defined arg 2012-07-18 12:58:23 +02:00
José Valim a421a73abf Clean up exunit server and exception docs 2012-07-18 11:54:27 +02:00
José Valim f7bfdf9ac7 Use a Mix.Server instead of storing info in the application env 2012-07-18 11:42:35 +02:00
José Valim 5a9e55f6e5 Rely less on slow mix cli tests 2012-07-18 11:14:53 +02:00
José Valim bfddbd726a Add mix shell to avoid noise in tests 2012-07-18 10:59:28 +02:00
José Valim c524f43860 Add assert_received to assertions 2012-07-18 10:57:44 +02:00
José Valim 4b960a2114 Add compile meta task, move logic to compile.elixir 2012-07-18 10:20:28 +02:00
José Valim c3d5611cc2 Improve docs for functions that return both binaries and char lists 2012-07-17 14:02:59 +02:00
José Valim fea9455eb7 Add self() back since it is allowed in guard clauses 2012-07-17 13:49:34 +02:00
José Valim 806e7bdd4d Merge branch 'mix'
This merge introduces many convenient helpers inside the File module
and the initial implementation for mix ported from elixir-lang/mix.
2012-07-16 15:35:19 +02:00
José Valim fc3aa85d81 Add mix help 2012-07-16 15:29:29 +02:00
José Valim 6c5b3134af Add function_exported?, improve warnings for behaviors 2012-07-16 15:29:29 +02:00
José Valim 3fed236607 Add IEx task 2012-07-16 15:29:29 +02:00
José Valim 29fe257442 Improve docs for d, make it easy to query builtin functions 2012-07-16 15:29:29 +02:00
José Valim b15394ce22 More improvements to printed docs 2012-07-16 15:29:29 +02:00
José Valim e3f13c6732 Add loadpaths before running tests 2012-07-16 15:29:29 +02:00
José Valim dca16fe966 Allow macros to be used in rescue clauses 2012-07-16 15:29:29 +02:00
José Valim 7bef3ae021 Register shortdoc and hidden as attributes 2012-07-16 15:29:29 +02:00
José Valim c186bb19df Add d to print docs in IEx console, compile by default with docs 2012-07-16 15:29:29 +02:00
José Valim ad50731395 Add a test task 2012-07-16 15:29:29 +02:00
José Valim cfe9b52485 Provide a parallel require that runs require in parallel 2012-07-16 15:29:28 +02:00
José Valim 0fdb57799c Tasks are invoked only once unless reenabled 2012-07-16 15:29:28 +02:00
José Valim f8f6b8eb4c Refactor Mix.Task and Mix.Utils 2012-07-16 15:29:28 +02:00
José Valim 2c396b9cc1 Make the default for ExUnit case to be sync 2012-07-16 15:29:28 +02:00
José Valim cdbfeaf67e Add compile task and tests 2012-07-16 15:29:28 +02:00
José Valim 15fa33a6dc Add Mix.Project.config and Mix.Project.current 2012-07-16 15:29:28 +02:00
José Valim 98631ada7d Make sure cp_rf also handles symlinks too 2012-07-16 15:29:28 +02:00
José Valim b8c2705ea1 Ensure rm_rf does not follow symlinks 2012-07-16 15:29:28 +02:00
José Valim 35c8a17e8d Avoid unix specific calls in tests 2012-07-16 15:29:28 +02:00
José Valim 23cd4ce28c Add File.rm_rf 2012-07-16 15:29:28 +02:00
José Valim bbd7960d78 Add rmdir, use path consistently in File 2012-07-16 15:29:28 +02:00
José Valim 7d5be8d093 Do not depend on the filesystem order 2012-07-16 15:29:27 +02:00
José Valim d2f2d4ba20 Introduce File.CopyError, File.cp! and File.cp_r! 2012-07-16 15:29:27 +02:00
José Valim 7be9ba8585 Add File.cp and reorganize test cases a bit 2012-07-16 15:29:27 +02:00
José Valim 28ecab3dde Support File.cp_r 2012-07-16 15:29:27 +02:00
José Valim f756ce6077 Fix tests that were not using absolute paths 2012-07-16 15:29:27 +02:00
José Valim f491ebb976 Support File.copy 2012-07-16 15:29:27 +02:00
José Valim a5a2025daf Add mix file executables 2012-07-16 15:29:27 +02:00
José Valim 92fa624892 Support File.touch(!) and File.write_stat(!) 2012-07-16 15:29:27 +02:00
José Valim b1be0d1ebe We are now able to run tasks 2012-07-16 15:29:27 +02:00
José Valim e4176725ab Add File.cwd/0, File.chdir/1 and File.chdir/2 2012-07-16 15:29:27 +02:00
José Valim cea5a61ea2 Use a main fixture_path helper 2012-07-16 15:29:27 +02:00
José Valim a458e0e43c Allow File.open with a function 2012-07-16 15:29:27 +02:00
José Valim 4c099d6fc7 Document File.open 2012-07-16 15:29:27 +02:00
José Valim 72b3548c86 Fix makefile dependency 2012-07-16 15:29:26 +02:00
José Valim 4f650b90df Provide proper setup and teardown hooks 2012-07-16 15:29:26 +02:00
José Valim ab504ce9fe Add setup and teardown callbacks to ExUnit 2012-07-16 15:29:26 +02:00
José Valim b5022828ff Allow mixfile module to be set 2012-07-16 15:29:26 +02:00
José Valim 59c38454c7 Refactor code now that we have local macros 2012-07-16 15:29:26 +02:00
José Valim 74851be5f9 Initial structure for mix 2012-07-16 15:29:26 +02:00
José Valim aa8bd9e021 Update repo info 2012-07-14 12:37:03 +02:00
José Valim af6c593898 Refactor previous commit by moving the fix to cons_to_list 2012-07-13 08:50:55 +02:00
José Valim 09cd916ad4 Merge pull request #391 from yrashk/in-guard-bugfix
Fixes a bug that broke the correct behavior of the `in` guard
2012-07-12 23:44:38 -07:00
Yurii Rashkovskii bb6562391e Fixes a bug that broke the correct behavior of the in guard
The correct behavior is:

> [] in [1,2,3] == false

However, recently introduced bug made this work like this:

> [] in [1,2,3] == true
2012-07-12 17:29:13 -07:00
José Valim 139b203da2 Ops, fix types on function definitions 2012-07-12 16:28:37 +02:00
José Valim 770ac3249e Document __block__, __scope__ and __aliases__ special forms 2012-07-12 16:25:43 +02:00
José Valim e80286ce57 Remove remaining __MAIN__ from erlang code 2012-07-12 12:29:52 +02:00
José Valim 1b4603160c Add elixir's parse transform to be used with erlang code 2012-07-12 11:29:46 +02:00
José Valim 2208998f50 Update LEGAL to point to the proper files 2012-07-11 21:43:16 +02:00
José Valim a75b572134 Update master 2012-07-11 21:27:09 +03:00
José Valim adcb8855d0 Provide Enum.nth! for now since we want to ensure we are on the proper bound 2012-07-11 19:51:22 +02:00
José Valim d216dbbc1d Ensure we are on master for docs generation 2012-07-11 13:02:50 +02:00
José Valim ae4bbed5d0 Add Enum.nth, change IEx code to rely on it 2012-07-11 12:42:17 +02:00
José Valim 0252cd8b85 Break IEx into many files, move it to the root namespace 2012-07-11 12:11:51 +02:00
José Valim 01009507ab Allow IEx to take control even when started without -noinput 2012-07-11 11:51:55 +02:00
José Valim df2254f8a3 Pipeline op must be right associative 2012-07-11 11:48:38 +02:00
José Valim a123ea29f7 Merge pull request #389 from yrashk/shell-history
Add very basic shell history support
2012-07-10 12:45:50 -07:00
Yurii Rashkovskii 87ea150b81 Add very basic shell history support 2012-07-10 12:40:46 -07:00
José Valim daf731dfe1 Merge pull request #388 from yrashk/typespec-list_to_bitstring
Fixes specification for list_to_bitstring/1
2012-07-10 11:58:36 -07:00
Yurii Rashkovskii 4507f61cbc Fixes specification for list_to_bitstring/1 2012-07-10 10:53:02 -07:00
José Valim 697e7ea5ac Fix small doc type 2012-07-10 19:43:55 +02:00
José Valim 439971e5b1 Small fixes to typespecs 2012-07-10 19:39:43 +02:00
José Valim b432e19149 Actually provide an implementation for erlang proxied functions
This makes three things possible:

1) @spec declarations are actually available in the Builtin module

2) Dynamic dispatch to Elixir.Builtin works as expected

3) At compilation time, the functions that are not dispatched
   are still proxied to avoid an extra dispatch
2012-07-10 18:45:07 +02:00
José Valim a4369dcbbb Raise an error when not a function call is given to :/> 2012-07-10 15:24:55 +02:00
José Valim 28de003aab Add quote location: :keep
This allows functions defined in macro to point to the file
that define them instead of keeping the injection point location.
2012-07-10 14:40:18 +02:00
José Valim 40cdb6494d Refactor unpack_ast code 2012-07-10 14:40:18 +02:00
José Valim 3642a2a799 Merge pull request #387 from yrashk/typespec-builtin
Adds function type specifications to Elixir.Builtin
2012-07-10 03:24:15 -07:00
Yurii Rashkovskii 978c512a3f Adds function type specifications to Elixir.Builtin
This commit also fixes a few minor documentation/naming mistakes in the
same module
2012-07-10 03:03:56 -07:00
José Valim b74615e559 Add Code.string_to_ast(!) 2012-07-10 10:45:22 +02:00
José Valim 7728f3d630 Deprecate loop, used named functions to recur instead
The reason for deprecating loops is two fold:

1) It reduces the number of special forms. Special forms usually
   allow a variable number of arguments and have special scoping
   rules and they cannot be overridden or removed;

2) Anonymous looping is highly uncommon in Erlang and slower
   than if we loop using named functions;

loop may be added back in the future as a non special form.
2012-07-10 10:10:40 +02:00
José Valim 5ea95d3048 Tidy up README 2012-07-09 23:04:21 +02:00
José Valim d59f2f9e16 Allow env as a @file argument 2012-07-09 23:04:21 +02:00
José Valim a4ab7ee75c Normalize file attribute in the expected place 2012-07-09 23:04:21 +02:00
José Valim f20854e00e Fix rebar invocation on Makefile, thanks to hirsch_ 2012-07-09 11:36:07 +03:00
José Valim 1e80f6d06e Update CHANGELOG 2012-07-08 21:20:53 +02:00
José Valim acc4b8e6fe Introduce regex match operator (=~) 2012-07-08 21:17:10 +02:00
José Valim 95ff052517 Remove extended semantics from the access protocol
The semantics of the access protocol is now reduced to a
key based access in data structures. This suits better
Erlang's philosophy of not overloading operators with
different semantics.
2012-07-08 20:49:01 +02:00
José Valim cb51b92e2a Dispatch the access protocol in atoms to __access__/2 2012-07-08 18:30:55 +02:00
José Valim b7bd24c30e rebar_elixir_plugin compat 2012-07-08 17:44:21 +02:00
José Valim 0f96f406f4 Add rebar.config so elixir can be easily used as a rebar dependency 2012-07-08 17:06:19 +02:00
José Valim 7c6f78206e Allow elixir to work as a rebar dependency
rebar expects its dependencies to have a file at `src/elixir.app.src`.
This is a work around a rebar limitation since it should support a way
to have an umbrella application, as OTP.
2012-07-08 16:50:54 +02:00
José Valim 8e20f6b701 Merge pull request #379 from bellthoven/record_toggle_field
Added the ability to toggle bolean fields in Records
2012-07-08 06:23:05 -07:00
Gustavo Dutra 7250be7f1a Add the ability to toggle bolean fields in Records 2012-07-07 20:32:29 -03:00
José Valim 7e068aca85 Introduce pipeline op 2012-07-06 11:56:04 +02:00
José Valim b2b3d3a97e Fix build 2012-07-06 11:55:56 +02:00
José Valim 062813c8f3 Add bitwise operators 2012-07-06 11:21:22 +02:00
José Valim 3f82624d0f Introduce new operators: <<<, >>>, &&&, |||, ^^^, ~~~ 2012-07-06 10:58:39 +02:00
José Valim b292d94739 Add Enum.first 2012-07-06 10:28:03 +02:00
José Valim 8d973d08ed Fixes for .bat files 2012-07-05 21:22:34 +02:00
José Valim d5031b6347 Move all code to erlang style apps 2012-07-05 20:20:16 +02:00
José Valim ebe7f6d226 Split eex from kernel 2012-07-04 22:13:00 +02:00
José Valim fe6bcad330 Split ex_unit from kernel 2012-07-04 21:40:06 +02:00
José Valim c2ebb620ff Try to make travis happy 2012-07-04 18:54:47 +02:00
José Valim 77d018758b Move Elixir's kernel to lib/kernel 2012-07-04 18:52:40 +02:00
José Valim 6182602f12 More explicit docs for Macro.expand 2012-07-04 15:19:15 +02:00
José Valim e216b8a073 Update version 2012-07-04 13:20:51 +02:00
José Valim e21f9f144c Small improvements to %shorcuts 2012-07-04 12:14:59 +02:00
José Valim a377687a90 Fix operator precedence in Macro.to_binary, closes #377 2012-07-04 10:04:50 +02:00
José Valim 4ed3f28285 Ensure ends are correctly nested in Macro.to_binary, closes #376 2012-07-03 22:25:27 +02:00
José Valim 8bca0ba86d Handle kw blocks on Macro.to_binary 2012-07-03 20:05:56 +02:00
José Valim fa6f13ce0c Functions generated through default are not documented 2012-07-03 18:17:28 +02:00
José Valim 97c7e1282f Add operator handling to Macro.to_binary 2012-07-03 12:37:53 +02:00
José Valim f913e58080 Convert a tree expression to binary 2012-07-03 12:22:37 +02:00
José Valim b0a430e93c Update CHANGELOG 2012-07-02 20:46:30 +02:00
José Valim cf3ba5a669 Add signatures to docs information 2012-07-02 20:44:09 +02:00
José Valim b74447b58a Reorganize makefile 2012-07-02 20:43:36 +02:00
José Valim ff1c2c0c46 Fix typo on defaults error message 2012-07-02 14:10:00 +02:00
José Valim ac3a697889 Invoke to_binary on IO module arguments if they are not an iodata
Closes #367
2012-07-02 12:36:00 +02:00
José Valim 93cee4d85a Raise nice error message if attempting to define a module that is being defined, closes #373 2012-06-28 17:00:53 +02:00
José Valim a241169d81 defmodule shares the variable scope, closes #374 2012-06-28 15:56:51 +02:00
José Valim 4aefd13ad3 Merge pull request #372 from yrashk/tuple-print
Fixes mismatched length record-like tuple printing
2012-06-23 09:26:58 -07:00
Yurii Rashkovskii 2305e65a63 Fixes mismatched length record-like tuple printing 2012-06-22 14:41:36 -07:00
José Valim 503b0fe9da Allow new line before binary (but not unary) ops 2012-06-19 22:06:45 +02:00
José Valim 4a3efad86b Remove Range.new/2 api for now 2012-06-18 23:57:51 +02:00
José Valim 868e44daca Update CHANGELOG 2012-06-18 23:53:04 +02:00
José Valim bcf8bbb37b Allow ranges to be iterated 2012-06-18 23:49:18 +02:00
José Valim b62ab9d541 Add some in range assertions 2012-06-18 21:58:26 +02:00
José Valim ab61e6f95c Provide ranges and support them in the in operator 2012-06-18 21:49:38 +02:00
José Valim 3b15228414 Fix failing build 2012-06-18 12:38:25 +02:00
José Valim 119a87b2ab Move away from packages since they don't work as expected with embedded mode 2012-06-18 12:28:09 +02:00
José Valim fb07d5e93c Merge pull request #366 from alco/map_dev
Map device name in runtime to save some typing
2012-06-15 03:29:51 -07:00
Alexei Sholik 0872515373 Map device name in runtime to save some typing 2012-06-13 23:17:18 +03:00
José Valim c79caec547 Fix regression on access macro 2012-06-13 12:09:48 +02:00
José Valim e271da2d82 Update files and grammar in LEGAL 2012-06-13 11:21:31 +02:00
José Valim 6ba4154d40 Support Enum.count 2012-06-12 14:00:43 +02:00
José Valim 539f8252f8 Add support to @after_compile 2012-06-11 12:33:29 +02:00
José Valim 86372c11c4 Deprecate add_compile_callback in favor of the attributes API 2012-06-11 12:19:23 +02:00
José Valim d20d282fae Update CHANGELOG 2012-06-11 11:58:38 +02:00
José Valim 5fb2f0135e Improve inspect of atoms, closes #361 2012-06-11 11:57:12 +02:00
José Valim 610d34c314 Document line option for quote, support line: :keep, closes #364 2012-06-11 11:43:45 +02:00
José Valim 2244d2baf2 Improve docs and add CHANGELOG for access macro changes 2012-06-11 10:23:20 +02:00
José Valim ae1bf939a6 Merge pull request #363 from yrashk/record_underscore_syntax
Adds record '_' syntax to match with Erlang syntax
2012-06-11 00:53:07 -07:00
Yurii Rashkovskii 363d8f3dc0 Adds record '_' syntax to match with Erlang syntax 2012-06-10 16:00:39 -07:00
José Valim c919b3fa00 Allow extra space in heredocs ending 2012-06-09 19:35:25 +02:00
José Valim 7fc69c1433 Optimize in operator 2012-06-09 12:24:52 +02:00
José Valim d8dd801ec8 Solve inconsistency with in not allowed inside some exprs 2012-06-09 12:24:52 +02:00
José Valim 272956067b Fix precedence of inlist and inbits generators with match 2012-06-09 12:24:52 +02:00
Rafael Mendonça França 27c0c489e3 Merge pull request #359 from benmills/patch-1
Fix type in lib/ex_unit.ex docs.
2012-06-08 18:17:11 -07:00
Ben Mills 0a1058515c Fix type in lib/ex_unit.ex docs. 2012-06-08 16:48:01 -07:00
José Valim 764f6623f3 Merge pull request #357 from yrashk/typespec-callback
Adds support for @callback type specification attribute
2012-06-08 13:22:59 -07:00
Yurii Rashkovskii 1f049dbac0 Adds support for @callback type specification attribute 2012-06-08 13:13:35 -07:00
José Valim 17b6edbe51 Improve docs and tests for Elixir.Typespec 2012-06-08 17:32:15 +02:00
José Valim 3b066b8563 Protect type, typep, export_type and spec attributes 2012-06-08 16:17:41 +02:00
José Valim 037492b7a2 Move Typespec to Elixir.Typespec, ensure it works with @spec and friends 2012-06-08 16:06:37 +02:00
José Valim 32622fd6a0 docs was renamed to full 2012-06-08 14:18:40 +02:00
José Valim 192098ce3c Make assert smarted deprecating unecessary counter-parts 2012-06-08 14:16:17 +02:00
José Valim f7652b88e3 Initial support to @spec, @typep and @type attributes 2012-06-08 13:53:36 +02:00
José Valim 3a744f1377 Add a full flag for release 2012-06-08 13:52:57 +02:00
José Valim b52a7ff37d Refactor Typespec by grouping functions, enforce 80 chars column limit when possible 2012-06-08 12:58:22 +02:00
José Valim d4b8156a11 Refactor Typespec: use Macro.escape instead of quote/unquote 2012-06-08 12:11:31 +02:00
José Valim c79b08b626 Update CHANGELOG 2012-06-08 11:12:36 +02:00
José Valim f3df54a913 Document IEx, split helpers into its own module, update CHANGELOG 2012-06-08 11:00:04 +02:00
José Valim 11885babbe Merge pull request #353 from yrashk/shell-helpers
Adds m/0 and m/1 helpers to iex (similar to their equivalents in erlang'...
2012-06-08 01:47:23 -07:00
José Valim d67d6eec9c Merge pull request #355 from yrashk/exunit_start_link_fix
Matching the return value in ExUnit.Server.start_link is too early
2012-06-08 01:19:33 -07:00
Yurii Rashkovskii 40c42d9569 Matching the return value in ExUnit.Server.start_link is too early 2012-06-08 00:36:13 -07:00
Yurii Rashkovskii fca3b47dc1 Adds m/0 and m/1 helpers to iex (similar to their equivalents in erlang's shell) 2012-06-07 14:16:37 -07:00
José Valim cd0fb66096 Merge pull request #352 from yrashk/typespec-range-op
Adds support for the range operator (..) in typespecs
2012-06-07 13:44:44 -07:00
Yurii Rashkovskii 4a2685a165 Adds support for the range operator (..) in typespecs 2012-06-07 13:26:46 -07:00
José Valim 6bcd52ded0 Merge pull request #351 from yrashk/typespec-opaque
Adds support for `opaque` option in deftype
2012-06-07 13:12:57 -07:00
Yurii Rashkovskii b3dea620b1 Adds support for opaque option in deftype 2012-06-07 13:03:13 -07:00
José Valim 32c0c5dd8a Merge pull request #344 from yrashk/typespec-improvements
Changes function type syntax in type specification adjusted according to...
2012-06-07 12:28:55 -07:00
Yurii Rashkovskii 8c40d27725 Changes returns: to do: in defspec 2012-06-07 10:04:14 -07:00
Yurii Rashkovskii b4324ce9fd Changes deftype <type>, as: syntax to deftype <type> :: 2012-06-07 10:01:47 -07:00
Yurii Rashkovskii 3c66c4597b Typespecs now make use of the ... token 2012-06-07 09:42:18 -07:00
Yurii Rashkovskii bc4ad4e4da Changes function type syntax in type specification adjusted according to the plan outlined in #333 2012-06-07 09:33:56 -07:00
José Valim 6917db44b6 Update CHANGELOG 2012-06-07 17:03:41 +02:00
José Valim 3e5cccb74b Allow dict and orddict to use the faster interation functions 2012-06-07 17:01:53 +02:00
José Valim d0960ae517 Optimizes remaining enum functions 2012-06-07 16:48:17 +02:00
José Valim bc71395042 Optimizes find, find_value, find_index 2012-06-07 16:25:26 +02:00
José Valim 837a15363d Optimizes split, drop_while and friends 2012-06-07 16:14:39 +02:00
José Valim 058f0f6696 Provide faster versions for Enum#any? and Enum#all? 2012-06-07 16:04:18 +02:00
José Valim b19244ba28 Update CHANGELOG 2012-06-07 15:17:12 +02:00
José Valim a714b5ff1e Do not use read_port, add System.find_executable 2012-06-07 15:14:12 +02:00
José Valim 56061b9d93 Improve docs for bitwise 2012-06-07 14:22:26 +02:00
José Valim 1e8aed1da1 Allow OP: syntax 2012-06-07 13:37:02 +02:00
José Valim 6cf976be99 Filename -> File 2012-06-07 12:51:52 +02:00
José Valim bafe7be1dc Provide a file to tokenizer 2012-06-07 12:45:25 +02:00
José Valim 7fbe263ba0 Remove limitation use cannot be called outside modules 2012-06-07 12:28:12 +02:00
José Valim 9facc5a286 Reorganize tokenize clauses so the compiler can optimize better 2012-06-07 12:23:32 +02:00
José Valim 080beffc2c Add range op and ... identifier 2012-06-07 11:55:50 +02:00
José Valim 1781d5a141 Refactor token handling 2012-06-07 11:55:49 +02:00
José Valim 925641c3f2 Merge pull request #345 from yrashk/rel-erl_libs-fix
Allows use of externally supplied ERL_LIBS in release-packaged elixir
2012-06-07 01:08:33 -07:00
José Valim edfd64d853 Fix typos 2012-06-07 09:20:21 +02:00
José Valim bd9a72f748 Allow quoted calls 2012-06-07 09:05:25 +02:00
Yurii Rashkovskii 6c7d9ba3af Allows use of externally supplied ERL_LIBS in release-packaged elixir 2012-06-06 23:48:47 -07:00
José Valim 9866c0200d Allow ambiguousop with many values 2012-06-07 08:30:31 +02:00
José Valim 7260bc84c7 Ensure filename is a binary 2012-06-06 16:31:32 +02:00
José Valim 3e64b7ddeb Solve ambiguity with a +1 when a exists, closes #342 2012-06-06 13:35:57 +02:00
José Valim e51bc9e5aa Change comprehensions syntax to be more compatible with Erlang behavior, closes #335 2012-06-06 12:27:28 +02:00
José Valim 08ebd47582 Merge pull request #340 from yrashk/record-access-protocol-regression
Fixes regression in record access protocol handling introduced in ...
2012-06-05 23:34:45 -07:00
Yurii Rashkovskii 893f012ffe Fixes regression in record access protocol handling introduced in 088eff4c19
It was mistreating nil values for undefined keys
2012-06-05 17:29:59 -07:00
José Valim 67a906083e Better exceptions in cond, closes #337 2012-06-05 20:16:44 +02:00
José Valim 8602486c6d Document the macro @ and allow attributes to be read inside functions 2012-06-05 19:41:19 +02:00
José Valim c01cedb822 Allow values @ to be read inside functions 2012-06-05 19:41:16 +02:00
José Valim 9b3568aba9 Remove incorrect conversion to binary 2012-06-05 19:41:16 +02:00
José Valim 6440c2dacf Merge pull request #338 from elixir-lang/file_open
Add File.open and File.close
2012-06-05 07:20:31 -07:00
Rafael Mendonça França 1c637792dc Use File.open and File.close 2012-06-05 11:16:09 -03:00
Rafael Mendonça França bb196c9c7c Add File.close/1 2012-06-05 11:11:39 -03:00
Rafael Mendonça França e4ec917d39 Add File.open/2 2012-06-05 11:06:27 -03:00
José Valim a7a6dd327b Update changelog, add callbacks to the list 2012-06-05 14:50:39 +02:00
José Valim 8f8fd13f3b Unify data and attributes 2012-06-05 14:12:36 +02:00
José Valim e8bcb0ebea Improve docs for comprehensions 2012-06-05 11:01:58 +02:00
José Valim 4d6920a470 Add IO.read, IO.write and IO.readline 2012-06-04 20:08:08 +02:00
Rafael Mendonça França 225b24ea4f Fix examples indentation in the File docs 2012-06-04 14:03:55 -03:00
José Valim 5b2751e094 Merge pull request #334 from elixir-lang/file_io
Add IO.gets and IO.getb
2012-06-04 09:30:55 -07:00
José Valim daea0b44ca Merge pull request #331 from philss/file-delete
Add File.delete
2012-06-04 09:29:28 -07:00
Rafael Mendonça França 763771d023 Add IO.gets to read a line from the IO device 2012-06-04 13:04:14 -03:00
Rafael Mendonça França 9327689ebe Add IO.getb to read bytes from a IO device. 2012-06-04 13:04:14 -03:00
Philip Sampaio e3996d2c43 Fixing File.delete doc and renaming to File.rm 2012-06-04 12:57:37 -03:00
José Valim 4393c1a41c Use Macro.expand instead of Macro.expand_aliases 2012-06-04 16:19:02 +02:00
José Valim fdd469c6df Merge pull request #320 from yrashk/typespecs
Initial implementation of type specifications
2012-06-04 07:00:27 -07:00
José Valim c8d2a0b878 Ensure refute can also show nice messages, closes #319 2012-06-04 15:55:27 +02:00
José Valim b959e1d117 Raise an error if an invalid alias is given, closes #316 2012-06-04 12:19:30 +02:00
José Valim c4cbc5a7f3 Support identifier?:, closes #322 2012-06-04 11:29:07 +02:00
José Valim 5156bfa47d Support Macro.expand 2012-06-04 11:15:11 +02:00
José Valim 28de080999 Expose expand_import and expand_require functions 2012-06-04 10:39:03 +02:00
José Valim a74c6fe8cb initial work on exposing Macro.expand 2012-06-04 10:18:37 +02:00
Philip Sampaio 4399e21ce3 Add File.delete 2012-06-04 01:07:14 -03:00
José Valim 68f1de4911 Share the same bin scripts 2012-06-03 23:17:13 +02:00
José Valim 544e49d5a1 Merge pull request #327 from yrashk/rel
Adds `rel` makefile target that builds a self-contained elixir distribution
2012-06-03 13:46:22 -07:00
Yurii Rashkovskii 1b32a87431 Actually boot the release we're building for Elixir 2012-06-03 11:46:06 -07:00
José Valim 9afc59d084 Merge pull request #329 from yrashk/shell-output
Fixes the problem with skewed error logger's output in IEx
2012-06-02 23:53:47 -07:00
Yurii Rashkovskii 44fda96939 Fixes the problem with skewed error logger's output in IEx 2012-06-02 18:39:59 -07:00
Yurii Rashkovskii c78ce512be Adds rel makefile target that builds self-contained elixir 2012-06-02 17:45:37 -07:00
José Valim 8ef0ef782c Merge pull request #325 from yrashk/defdelegate-handle
Adds :append_first boolean option to defdelegate
2012-06-01 12:10:57 -07:00
Yurii Rashkovskii 8deff9dac3 Renames defdelegate's append_handle option to append_first 2012-06-01 12:05:14 -07:00
José Valim a9a5b4e086 Merge pull request #326 from carlosgaldino/iterator
Add tests for custom range iterator
2012-06-01 12:01:09 -07:00
Carlos Galdino 91c7ab72d5 Add tests for custom range iterator 2012-06-01 15:35:49 -03:00
Yurii Rashkovskii ec19e2b009 Adds :append_handle boolean option to defdelegate 2012-06-01 11:35:23 -07:00
Yurii Rashkovskii 7428ccb9ca Typespecs should use of an actual ArgumentError exception instead of badarg 2012-06-01 11:26:11 -07:00
Yurii Rashkovskii 97d9db2a8f Changes = operator to :: for typespec annotations 2012-06-01 11:03:44 -07:00
Yurii Rashkovskii adf7df4f3b Adds support for multiple defspec clauses for the same function 2012-06-01 11:02:47 -07:00
Yurii Rashkovskii 33e290d197 Improves indentation in typespec_test.exs 2012-06-01 11:02:47 -07:00
Yurii Rashkovskii 87c69433dc Improves indentation in typespec.ex 2012-06-01 11:02:47 -07:00
Yurii Rashkovskii ca67bfe64c Replaces or with | in typespecs 2012-06-01 11:02:47 -07:00
Yurii Rashkovskii 10adf310bc Typespec should use Macro.expand_aliases/2 instead of Code.eval_quoted/1 2012-06-01 11:02:47 -07:00
Yurii Rashkovskii 90929bd08e Initial implementation of deftype, deftypep and defspec 2012-06-01 11:02:47 -07:00
José Valim f6e139dc18 Add :: as an operator 2012-06-01 19:59:33 +02:00
José Valim 8cefa744b8 Decouple macro expansion from macro translation 2012-06-01 19:53:05 +02:00
José Valim b15727dd42 Update lib/module.ex [ci skip] 2012-06-01 20:52:56 +03:00
José Valim 37ea3965da Update lib/module.ex docs [ci skip] 2012-06-01 18:05:10 +03:00
José Valim 9220637a3c Ensure nice stack trace on compiled callbacks, closes #314 2012-06-01 12:33:21 +02:00
José Valim 9d596a1f39 Add tests to ensure proper stacktrace on macro calls 2012-06-01 11:48:39 +02:00
José Valim 646ee5f125 Compile regexes at compilation time, speeds up considerably runtime 2012-06-01 11:23:00 +02:00
Rafael Mendonça França fc0e327625 Merge pull request #317 from carlosgaldino/refute
Use refute instead of negative assert
2012-05-31 09:30:10 -07:00
Carlos Galdino b02f19dc81 Use refute instead of negative assert 2012-05-31 13:27:17 -03:00
José Valim 1fb409fdd0 Merge pull request #313 from carlosgaldino/file-write
Add File.write and File.write_file
2012-05-31 08:06:44 -07:00
Carlos Galdino 3352b07275 Add File.write 2012-05-31 11:22:30 -03:00
José Valim c926b7f943 Ensure aliases can be overriden 2012-05-31 13:19:21 +02:00
José Valim 9947f6a405 Simply let unhandled -> fail, closes #315 2012-05-31 13:02:01 +02:00
José Valim 1acad9e5fc Use filelib:ensure_dir 2012-05-31 10:57:27 +02:00
José Valim 5710630e2f Regex is not multiline by default, closes #310 2012-05-30 21:50:02 +02:00
José Valim 2973c34224 Support :as in defdelegate, closes #311 2012-05-30 09:31:27 +02:00
José Valim 198a3f6291 Generate keywords at compile time, so we don't need to sort nor loop at runtime 2012-05-30 08:54:13 +02:00
José Valim 9eb040d49b Force rebar on R15 2012-05-30 08:27:55 +02:00
José Valim 524720666e Merge pull request #312 from yrashk/record-to-keyword
Adds `to_keyword(record)` function to all records
2012-05-29 23:27:41 -07:00
Yurii Rashkovskii 27ad9423f8 Renames record's to_keyword function to_keywords 2012-05-29 19:26:04 -07:00
Yurii Rashkovskii 8ecf7b718d Minor improvement to Record.to_keyword implementation 2012-05-29 14:53:34 -07:00
Yurii Rashkovskii 243a32f2d1 Adds to_keyword(record) function to all records
This function can be used to convert any record to a Keyword:

    iex> defrecord A, a: 1, b: 2, c: nil
    nil
    iex> A.new
    A[a: 1, b: 2, c: nil]
    iex> A.new.to_keyword
    [{:a,1},{:b,2},{:c,nil}]
    iex> A.new(c: "a").to_keyword
    [{:a,1},{:b,2},{:c,"a"}]
2012-05-29 14:32:00 -07:00
Rafael Mendonça França 821e668361 Test File.read with a utf8 file 2012-05-29 17:42:11 -03:00
José Valim fbaa46618c Merge pull request #309 from elixir-lang/file_improvements
Add mkdir and mkdir_p
2012-05-29 08:03:37 -07:00
Rafael Mendonça França 1b811d3b72 Use the new in guard syntax 2012-05-29 12:00:13 -03:00
Rafael Mendonça França 488066d4cd Add mkdir_p 2012-05-29 11:58:45 -03:00
Rafael Mendonça França 16843c7c39 Copy edit documentation [ci skip] 2012-05-29 11:33:57 -03:00
Rafael Mendonça França a1cb84d2cc Add File.mkdir 2012-05-29 11:33:52 -03:00
José Valim 8b814ded29 Be more strict on inspect since we cannot be sure of the list encoding 2012-05-29 13:35:34 +02:00
José Valim fabfab8964 Update CHANGELOG 2012-05-28 14:26:53 +02:00
José Valim fb8b6f53f2 Deprecate refer in favor of alias, use atom aliases throughout source code 2012-05-28 14:24:47 +02:00
José Valim aaf6c91e0e Rewrite use in Elixir, force it to rely on __CALLER__ 2012-05-28 13:23:04 +02:00
José Valim 088eff4c19 Rewrite access macro in Elixir \o/ 2012-05-28 12:00:18 +02:00
José Valim 2714642b1e Add Macro.expand_aliases 2012-05-28 11:05:49 +02:00
José Valim c0e804d37f Support on R15B01 on travis-ci <3 2012-05-28 10:23:18 +02:00
José Valim b3e49a183d Deprecate in_guard special form in favor of the more flexbible __CALLER__.in_guard? 2012-05-28 10:20:30 +02:00
José Valim 1fb81138be Get rid of assign and guard in favor of context in #elixir_scope 2012-05-28 10:09:59 +02:00
José Valim b08cdc8ec7 Simpler mechanism for references 2012-05-27 22:03:01 +02:00
José Valim 9af9a797f7 Update CHANGELOG 2012-05-27 18:27:24 +02:00
José Valim cb18ab0a5d Add support to __CALLER__ inside macros 2012-05-27 18:19:03 +02:00
José Valim b685ded72d Fix a bug where nested super calls did not propagate properly 2012-05-27 17:58:31 +02:00
José Valim 40ca580d37 Rename internal variables from _EX to _@ since the first can also be user defined 2012-05-27 17:10:56 +02:00
José Valim 0048852114 Initial spike with __ENV__ 2012-05-27 12:11:56 +02:00
José Valim e59199907a Convert default values to nil for better interop with elixir 2012-05-27 09:44:34 +02:00
José Valim 2d3386cedf Add support to %R 2012-05-26 11:43:03 +02:00
José Valim 9e675c85cd Refactor duplicated logic in elixir_def_local 2012-05-25 13:11:08 +02:00
José Valim 3006bae683 Improvements to release process 2012-05-24 21:42:51 +02:00
José Valim 67f6e1c9b5 Get rid of the watcher 2012-05-24 21:35:17 +02:00
José Valim 377006540f Have a script to generate the package file 2012-05-24 21:34:58 +02:00
356 changed files with 27253 additions and 12627 deletions
+12 -10
View File
@@ -1,11 +1,13 @@
/erl_crash.dump
/src/*_lexer.erl
/src/*_parser.erl
/test/ebin
/test/tmp
/test/elixir/tmp
/ebin/*
/exbin/*
/src/*rl.old
/.eunit/*
/deps/*
/.full
/lib/*/ebin/*
/lib/*/tmp
/lib/*/test/tmp
/lib/elixir/src/elixir.app.src
/lib/elixir/src/*_lexer.erl
/lib/elixir/src/*_parser.erl
/lib/elixir/test/ebin
/deps/*
/ebin
/rel/elixir
erl_crash.dump
+3 -1
View File
@@ -1,8 +1,10 @@
language: erlang
script: "make compile docs test"
script: "make compile && make .full test"
notifications:
irc: "irc.freenode.org#elixir-lang"
recipients:
- jose.valim@plataformatec.com.br
otp_release:
- R15B02
- R15B01
- R15B
+99
View File
@@ -1,3 +1,102 @@
# 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] 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
* [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
* [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)
* First official release
+6 -6
View File
@@ -1,10 +1,10 @@
LEGAL NOTICE INFORMATION
------------------------
All the files in this distribution are covered under either the Elixir's
license (see the file LICENSE) except some files mentioned below that contains
sections that are under Erlang's License (EPL):
All the files in this distribution are covered under either Elixir's
license (see the file LICENSE) except the files mentioned below that
contains sections that are under Erlang's License (EPL):
lib/elixir/src/elixir_glob.erl
lib/elixir/src/elixir_parser.erl (generated by build scripts)
src/elixir_glob.erl
src/eex_lexer.erl (generated by build scripts)
src/elixir_parser.erl (generated by build scripts)
+91 -31
View File
@@ -1,48 +1,108 @@
REBAR:=$(shell which rebar || echo ./rebar)
ERLC=erlc -I include
ERL=erl -I include -noshell -pa ebin
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.7.0
.PHONY: ebin docs
.PHONY: 1
.NOTPARALLEL: compile
compile: ebin ebin/__MAIN__ ebin/elixir.app
#==> Templates
define TASK_TEMPLATE
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
ebin/elixir.app:
@ $(REBAR) compile
lib/$(1)/ebin/$(1).app:
@ cd lib/$(1) && ../../bin/elixir ../../bin/mix compile.app
ebin:
@ $(REBAR) compile
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex) $$(FORCE)
@ echo "==> $(1) (compile)"
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
ebin/__MAIN__: lib/*.ex lib/*/*.ex
@ rm -rf ebin/__MAIN__
$(ERL) -s elixir_compiler core -s erlang halt
@ rm -rf ebin/elixir.app
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
#==> Compilation tasks
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
compile: lib/elixir/src/elixir.app.src erlang elixir
lib/elixir/src/elixir.app.src: src/elixir.app.src
@ rm -rf lib/elixir/src/elixir.app.src
@ cp src/elixir.app.src lib/elixir/src/elixir.app.src
erlang:
@ cd lib/elixir && $(REBAR) compile
# We need to compile only EEx (without the app)
# file so we can compile Mix
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
kernel: $(KERNEL)
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
@ if [ -f $(KERNEL) ]; then \
echo "==> kernel (compile)"; \
$(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin; \
else \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@ rm -rf lib/elixir/ebin/elixir.app
@ cd lib/elixir && $(REBAR) compile
$(eval $(call TASK_TEMPLATE,ex_unit,ExUnit))
$(eval $(call TASK_TEMPLATE,eex,EEx))
$(eval $(call TASK_TEMPLATE,mix,Mix))
$(eval $(call TASK_TEMPLATE,iex,IEx))
clean:
@ $(REBAR) clean
@ 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
docs: compile
@ bin/elixirc "lib/**/*.ex" --ignore-module-conflict --docs -o for_docs
@ rm -rf ebin/__MAIN__
@ mv for_docs/__MAIN__ ebin/__MAIN__
@ rm -rf for_docs
#==> Release tasks
$(FULLFLAG): $(wildcard lib/*/ebin/*)
make ELIXIRC_OPTS="--debug-info" FORCE=1
touch $(FULLFLAG)
zip: $(FULLFLAG)
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
docs: $(FULLFLAG)
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../exdoc/bin/exdoc
rm -rf ebin
release_docs: docs
bin/elixir ../exdoc/bin/exdoc
rm -rf ../elixir-lang.github.com/docs
mv output ../elixir-lang.github.com/docs
cd ../elixir-lang.github.com && git checkout master
rm -rf ../elixir-lang.github.com/docs/master
mv output ../elixir-lang.github.com/docs/master
release_erl: $(FULLFLAG)
@ rm -rf rel/elixir
@ cd rel && ../rebar generate
#==> Tests tasks
test: test_erlang test_elixir
test_erlang: compile
@ echo "==> erlang (eunit)"
@ mkdir -p test/ebin
@ # Compile test files
@ $(ERLC) -o test/ebin test/erlang/*.erl
@ # Look and execute each file
@ time $(ERL) -pa test/ebin -s test_helper test -s erlang halt
@ 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;
@ echo
test_elixir: compile
@ echo "==> elixir (exunit)"
@ time bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"
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";
+17 -22
View File
@@ -1,18 +1,12 @@
![Elixir](https://github.com/elixir-lang/elixir/raw/master/logo.png)
![Elixir](https://github.com/elixir-lang/elixir-lang.github.com/raw/master/images/logo/logo.png)
=========
[![Build Status](https://secure.travis-ci.org/elixir-lang/elixir.png?branch=master "Build Status")](http://travis-ci.org/elixir-lang/elixir)
Elixir is a programming language built on top of the Erlang VM. As Erlang, it is a functional language built to support distributed, fault-tolerant, non-stop applications with hot code swapping.
Elixir is also dynamic typed but, differently from Erlang, it is also homoiconic, allowing meta-programming via macros. Elixir also supports polymorphism via protocols (similar to Clojure's), dynamic records and provides a reference mechanism.
Finally, Elixir and Erlang share the same bytecode and data types. This means you can invoke Erlang code from Elixir (and vice-versa) without any conversion or performance hit. This allows a developer to mix the expressiveness of Elixir with the robustness and performance of Erlang.
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
# Usage
You can run and learn about Elixir in the [Getting Started guide][1].
But if you just want to try it out, clone this repository to your machine, compile and test it:
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
$ git clone https://github.com/elixir-lang/elixir.git
$ cd elixir
@@ -26,32 +20,33 @@ However, if tests fail, it is likely you have an outdated Erlang version (Elixir
If you have the correct version and tests still fail, feel free to [open an issue][2].
# Contributing & Roadmap
# Contributing
If you want to contribute, the code is organized as follows:
If you want to contribute, Elixir code is divided in applications inside the `lib` folder:
* `include`, `src` - Both directories contain part of the source code written in Erlang. `yecc` is used as the parser;
* `elixir` - Contains Elixir's kernel and stdlib;
* `lib` - Contains Elixir's STDLIB, written in Elixir;
* `eex` - Template engine that allows you to embed Elixir;
* `test/elixir` - Tests for Elixir's STDLIB, written in Elixir. For this purpose, Elixir ships with a small unit test library called `ExUnit`;
* `test/erlang` - Contains tests for Elixir, written in Erlang. Usually, just internal stuff is tested here. The preferred way to test is in Elixir itself.
* `ex_unit` - Simple test framework that ships with Elixir;
We usually keep a list of features and bugs [in the issue tracker][2].
# Important links
* #elixir-lang on freenode IRC
* [Getting Started Guide][1]
* [Mailing list](http://groups.google.com/group/elixir-lang-core)
* [Website][1]
* [Issue tracker][2]
* [Textmate Bundle for Elixir](https://github.com/elixir-lang/elixir-tmbundle)
* [Vim Elixir](https://github.com/elixir-lang/vim-elixir)
* [Mailing list][3]
[1]: http://elixir-lang.org/getting_started/1.html
[1]: http://elixir-lang.org
[2]: https://github.com/elixir-lang/elixir/issues
[3]: http://groups.google.com/group/elixir-lang-core
# License
Copyright (c) 2012 Plataformatec. See LICENSE file.
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
Check LEGAL and LICENSE files for more information.
+13 -5
View File
@@ -2,18 +2,26 @@
This document simply outlines the release process:
1) Run `make clean test` to ensure all tests pass from scratch
1) Remove .dev extension from current versions
2) Remove .dev extension from current versions
2) Run `make clean test` to ensure all tests pass from scratch
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) After release, bump versions and add .dev back
5) Create tag from stable branch
6) Release new docs, update elixir-lang.org
7) Push new zip to Elixir's downloads page
8) After release, bump versions and add .dev back
## Places where version is mentioned
* src/elixir.app.src
* lib/system.ex
* lib/elixir/lib/system.ex
* rel/reltool.config
* Makefile
* CHANGELOG
+58 -14
View File
@@ -5,29 +5,73 @@ if [ $# -eq 0 ]; then
-v Prints version and exit
-e \"command\" Evaluates the given command (*)
-r \"file\" Requires the given files/patterns (*)
-S \"script\"   Finds and executes the given script (*)
-pr \"file\" Requires the given files/patterns in parallel (*)
-pa \"path\" Prepend the given path to Erlang code path (*)
-pz \"path\" Append the given path to Erlang code path (*)
--no-halt Do not halt the Erlang VM after execution
-pa \"path\" Prepends the given path to Erlang code path (*)
-pz \"path\" Appends the given path to Erlang code path (*)
--erl \"switches\" Switches to be passed down to erlang
--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;
** Options given after the .exs file or -- are passed down to the executed code;
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS." >&2
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
exit 1
fi
readlink_f () {
cd `dirname $1` > /dev/null
local filename=`basename $1`
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f `readlink $filename`
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
SELF=`readlink_f $0`
SCRIPT_PATH=`dirname $SELF`
erl -pa $SCRIPT_PATH/../ebin -noshell $ELIXIR_ERL_OPTS -s elixir start_cli -extra "$@"
ERL=""
I=1
while [ $I -le $# ]; do
S=1
eval "PEEK=\${$I}"
case "$PEEK" in
-v|--compile|--no-halt)
;;
-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}"
ERL="$ERL `echo $PEEK | cut -c 2-` "$VAL""
;;
--erl)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL "$VAL""
;;
*)
break
;;
esac
I=$(expr $I + $S)
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
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
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
fi
+4 -6
View File
@@ -15,10 +15,8 @@ echo -pa path Prepend the given path to Erlang code path (*)
echo -pz path Append the given path to Erlang code path (*)
echo --no-halt Do not halt the Erlang VM after execution
echo.
echo ** Options marked with (*) can be given more than once;
echo.
echo ** Options given after the .exs file or -- are passed down to the executed code;
echo.
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
:run
erl -pa %~dp0\..\ebin -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
+10 -16
View File
@@ -1,33 +1,27 @@
#!/bin/sh
if [ $# -eq 0 ]; then
echo "Usage: `basename $0` [switches] [.ex files]
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
-v Prints version and exit
-o The directory to output compiled files
-pa \"path\" Prepend the given path to Erlang code path (*)
-pz \"path\" Append the given path to Erlang code path (*)
--docs Attach documentation to compiled modules
--no-docs Do not attach documentation with compiled code
--debug-info Attach debug info to compiled modules
--ignore-module-conflict
** Options marked with (*) can be given more than once;
** Options given after -- are passed down to the executed code;
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS." >&2
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
exit 1
fi
readlink_f () {
cd `dirname $1` > /dev/null
local filename=`basename $1`
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f `readlink $filename`
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
SELF=`readlink_f $0`
SCRIPT_PATH=`dirname $SELF`
$SCRIPT_PATH/elixir +compile "$@"
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --compile "$@"
+5 -7
View File
@@ -11,14 +11,12 @@ echo -v Prints version and exit
echo -o The directory to output compiled files
echo -pa path Prepend the given path to Erlang code path (*)
echo -pz path Append the given path to Erlang code path (*)
echo --docs Attach documentation to compiled modules
echo --no-docs Do not attach documentation with compiled code
echo --debug-info Attach debug info to compiled modules
echo --ignore-module-conflict
echo.
echo ** Options marked with (*) can be given more than once;
echo.
echo ** Options given after -- are passed down to the executed code;
echo.
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS." >&2
echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
:run
call %~dp0\elixir.bat +compile %*
call "%~dp0\elixir.bat" --compile %*
+6 -6
View File
@@ -1,14 +1,14 @@
#!/bin/sh
readlink_f () {
cd `dirname $1` > /dev/null
local filename=`basename $1`
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f `readlink $filename`
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
SELF=`readlink_f $0`
SCRIPT_PATH=`dirname $SELF`
erl -pa $SCRIPT_PATH/../ebin -noinput $ELIXIR_ERL_OPTS -s elixir start_cli -extra --no-halt -e "Elixir.IEx.start" "$@"
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
erl -pa %~dp0\..\ebin -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra --no-halt -e "Elixir.IEx.start" %*
call "%~dp0\elixir.bat" --no-halt -e "IEx.cli" %*
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env elixir
Mix.start
Mix.CLI.run
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %*
-178
View File
@@ -1,178 +0,0 @@
import Elixir.Builtin, except: [access: 2]
defprotocol Access do
@moduledoc """
The Access protocol is the underlying protocol invoked
when the brackets syntax is used. For instance, `foo[bar]`
is translated to `access foo, bar` which, by default,
invokes `Access.access` protocol.
This protocol is implemented by default for most builtin
types, like tuples, atoms, functions, etc.
"""
@only [List, BitString, Record, Tuple, Atom, Function]
@doc """
Receives the element being accessed and the access item.
"""
def access(element, qualifier)
end
defimpl Access, for: Tuple do
@doc """
Access the tuple via an integer. Negative indexes
performs an inverted lookup, for example, -1 can be
used to retrieve the last item in the tuple. Returns
nil if an out of bounds access occurs.
## Examples
tuple = { :a, :b, :c }
tuple[-1] #=> :c
"""
def access(tuple, integer) when is_integer(integer) and integer > 0 and integer <= tuple_size(tuple) do
:erlang.element(integer, tuple)
end
def access(tuple, integer) when is_integer(integer) and integer < 0 do
size = tuple_size(tuple)
position = integer + size + 1
if position > size or position < 1,
do: nil, else: :erlang.element(position, tuple)
end
def access(_tuple, integer) when is_integer(integer) do
nil
end
end
defimpl Access, for: List do
@doc """
Access the list via a predicate.
If a regular expression, it returns a list with the
matched contents.
If an atom, assumes the list is a keywords list and
access the key in the keywords equals to the given
atom.
Notice this protocol does not implement an integer
lookup. This is intentional since doing an index
based access on lists is usually undesired.
## Examples
list = 'sample'
list[%r/a/] #=> 'a'
keywords = [a: 1, b: 2]
keywords[:a] #=> 1
"""
## Atom
def access(list, atom) when is_atom(atom) do
atom_access(list, atom)
end
## Regex
def access(list, re) when is_regex(re) do
case Erlang.re.run(list, Regex.re_pattern(re), [{ :capture, :first, :list }]) do
:nomatch -> nil
{ :match, [result] } -> result
end
end
## Helpers
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: BitString do
@doc """
Access the binary via a predicate.
If a regular expression, it returns a binary with the
matched contents.
## Examples
binary = "abc"
Binary.access binary, %r(a) #=> "a"
"""
## Regex
def access(binary, re) when is_binary(binary) and is_regex(re) do
case Erlang.re.run(binary, Regex.re_pattern(re), [{ :capture, :first, :binary }]) do
:nomatch -> nil
{ :match, [result] } -> result
end
end
end
defimpl Access, for: Atom do
@doc """
Access the atom via keywords. Different from other
implementations, the Access protocol for atoms is
special cased during compilation time to provide
faster read and write access for records.
This pattern can also be used in guards, which is
a very useful way to extract information from a
record.
## Examples
def increment(State[counter: counter] = state) do
state.counter(counter + 1)
end
In the example above, we use the Access protocol on atoms
to match the counter field in the record State. Considering
the record definition is as follows:
defrecord State, counter: 0, other: nil
The clause above is translated to:
def increment({ State, counter, _ } = state) do
state.counter(counter + 1)
end
Which is a very convenient way to pattern match and have
faster read times. The same pattern can be used to create
a new record:
def new_state(counter) do
State[counter: counter]
end
All fields not specified on creation defaults to their
default value.
"""
def access(atom, keywords) when is_list(keywords) do
atom.new(keywords)
end
end
defimpl Access, for: Function do
@doc """
The Access protocol for functions simply invokes
the function passing the item as argument. This
is useful because it allows a function to be
passed as argument in places a dict would also fit.
"""
def access(function, item) do
function.(item)
end
end
-221
View File
@@ -1,221 +0,0 @@
defmodule Binary do
@moduledoc """
Functions for working with binaries.
"""
@doc """
Simply invokes the Access protocol for the given binary.
Check `Access.BitString` for more information.
"""
def access(binary, access) when is_binary(binary) do
Access.BitString.access(binary, access)
end
@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 >= ?\s and c <= ?~ do
printable?(t)
end
# From 16#A0 to 16#BF
def printable?(<<194, c, t|:binary>>) when c >= 160 and c <= 191 do
printable?(t)
end
# From 16#C0 to 16#7FF
def printable?(<<m, o1, t|:binary>>) when m >= 195 and m <= 223 and o1 >= 128 and o1 < 192 do
printable?(t)
end
# From 16#800 to 16#CFFF
def printable?(<<m, o1, o2, t|:binary>>) when m >= 224 and m <= 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 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
printable?(t)
end
# From 16#F000 to 16#FFFD
def printable?(<<239, o1, o2, t|:binary>>) when
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
printable?(t)
end
# From 16#10000 to 16#3FFFF
def printable?(<<240, o1, o2, o3, t|:binary>>) when
o1 >= 144 and o1 < 192 and o2 >= 128 and o2 < 192 and o3 >= 128 and o3 < 192 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 >= 241 and m <= 244 and
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 and o3 >= 128 and o3 < 192 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.
## 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 `Elixir::Builtin.__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
-257
View File
@@ -1,257 +0,0 @@
import Elixir.Builtin, 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.
"""
@only [BitString, List, Record, Tuple, Atom, Number, Any]
def inspect(thing)
end
defimpl Binary.Inspect, for: Atom do
@doc """
Represents the atom as an Elixir term.
The atoms false, true and nil are simply
quoted. Modules are properly represented
as modules using the dot notation.
## Examples
inspect(:foo) #=> ":foo"
inspect(nil) #=> "nil"
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(atom) do
binary = atom_to_binary(atom)
cond do
valid_identifier?(binary) == <<>> ->
":" <> binary
valid_ref_identifier?(binary) == <<>> ->
"__MAIN__." <> rest = binary
rest
true ->
":" <> Binary.escape(binary, ?")
end
end
# Detect if atom is a module reference (__MAIN__.Foo.Bar.Baz)
defp valid_ref_identifier?("__MAIN__" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(rest) do
rest
end
defp valid_ref_piece?(<<?., h, t|:binary>>) when h >= ?A and h <= ?Z do
valid_ref_piece? valid_identifier?(t)
end
defp valid_ref_piece?(other), do: other
# Detect if atom is :letter_or_underscore
defp valid_identifier?(<<h, t|:binary>>) \
when h >= ?a and h <= ?z \
when h >= ?A and h <= ?Z \
when h == ?_ do
valid_identifier? t
end
defp valid_identifier?(other), do: other
end
defimpl Binary.Inspect, for: BitString do
@doc %B"""
Represents the string as itself escaping
all necessary characters.
## Examples
inspect("bar") #=> "bar"
inspect("f\"oo") #=> "f\"oo"
"""
def inspect(thing) when is_binary(thing) do
if Binary.printable?(thing) do
Binary.escape(thing, ?")
else
as_bitstring(thing)
end
end
def inspect(thing) do
as_bitstring(thing)
end
## Helpers
defp as_bitstring(thing) do
erlang = Erlang.io_lib.format('~p', [thing])
list_to_binary List.reverse(replace(erlang, []))
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
end
defimpl Binary.Inspect, for: List do
@doc %B"""
Represents a list checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used.
## Examples
inspect('bar') #=> 'bar'
inspect([0|'bar']) #=> "[0,98,97,114]"
inspect([:foo,:bar]) #=> "[:foo, :bar]"
"""
def inspect([]), do: "[]"
def inspect(thing) do
if Erlang.io_lib.printable_list(thing) do
Binary.escape(list_to_binary(thing), ?')
else
container_join(thing, "[", "]")
end
end
## Helpers
def container_join([h], acc, last) do
acc <> Binary.Inspect.inspect(h) <> last
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
end
end
defimpl Binary.Inspect, for: Tuple do
@doc """
Inspect tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
inspect({1,2,3}) #=> "{1,2,3}"
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, _ } in name.__record__(:fields), do: field
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
end
def inspect(thing) do
list = tuple_to_list(thing)
[name|tail] = list
if is_record?(name) do
fields = lc { field, _ } in name.__record__(:fields), do: field
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
else
Binary.Inspect.List.container_join(list, "{", "}")
end
end
## Helpers
defp is_record?(name) do
is_atom(name) and match?("__MAIN__." <> _, atom_to_binary(name, :utf8)) and
:erlang.function_exported(name, :__record__, 1)
end
defp records_join([f], [v], acc, last) do
acc <> atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v) <> last
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)
end
defp records_join([], [], acc, last) do
acc <> last
end
end
defimpl Binary.Inspect, for: Number do
@doc """
Represents the number as a binary.
## Examples
inspect(1) #=> "1"
"""
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)
end
end
defimpl Binary.Inspect, for: Regex do
@doc %B"""
Represents the Regex using the `%r""` syntax.
## Examples
inspect(%r/foo/m) #=> "%r\"foo\"m"
"""
def inspect(thing) do
"%r" <> Binary.Inspect.inspect(Regex.source(thing)) <> Regex.opts(thing)
end
end
defimpl Binary.Inspect, for: Any do
@doc """
For all other terms not implemented, we use the default
Erlang representation.
## Examples
inspect Process.self #=> "<0.35.0>"
"""
def inspect(thing) do
iolist_to_binary Erlang.io_lib.format('~p', [thing])
end
end
-48
View File
@@ -1,48 +0,0 @@
defmodule Bitwise do
@moduledoc """
This module provide macros for bitwise operators
provided by Erlang. These macros can be used in guards.
"""
@doc """
Bitwise not.
"""
defmacro bnot(expr) do
quote do: __op__ :bnot, unquote(expr)
end
@doc """
Bitwise and.
"""
defmacro band(left, right) do
quote do: __op__ :band, unquote(left), unquote(right)
end
@doc """
Bitwise or.
"""
defmacro bor(left, right) do
quote do: __op__ :bor, unquote(left), unquote(right)
end
@doc """
Bitwise xor.
"""
defmacro bxor(left, right) do
quote do: __op__ :bxor, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift left.
"""
defmacro bsl(left, right) do
quote do: __op__ :bsl, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift right.
"""
defmacro bsr(left, right) do
quote do: __op__ :bsr, unquote(left), unquote(right)
end
end
-185
View File
@@ -1,185 +0,0 @@
defmodule Code do
@moduledoc """
The Code module is responsible to manage code compilation,
code evaluation and code loading.
"""
@doc """
Returns all the loaded files.
"""
def loaded_files do
server_call :loaded
end
@doc """
Appends a path to Erlang VM code path.
"""
def append_path(path) do
Erlang.code.add_pathz(to_char_list(path))
end
@doc """
Prepends a path to Erlang VM code path.
"""
def prepend_path(path) do
Erlang.code.add_patha(to_char_list(path))
end
@doc """
Evalutes the contents given by string. The second argument is the binding
(which should be a Keyword) followed by a keyword list of options. The
options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
otherwise functions are evaluated inside Erlang's default scope.
## Examples
Code.eval "a + b", [a: 1, b: 2], file: __FILE__, line: __LINE__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval(string, binding // [], opts // []) do
{ value, binding, _scope } =
Erlang.elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding }
end
@doc """
Evalutes the quoted contents.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
or by the __FILE__ macro in case there is an error.
* `:line` - The line to be used when expanding __LINE__
macros and expressions inside the quote.
## Examples
contents = quote hygiene: false, do: a + b
Code.eval_quoted contents, [a: 1, b: 2], file: __FILE__, line: __LINE__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval_quoted(quoted, binding // [], opts // []) do
{ value, binding, _scope } =
Erlang.elixir.eval_quoted [quoted], binding, opts
{ value, binding }
end
@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.
When loading a file, you may skip passing .exs as extension as Elixir
automatically adds it for you.
"""
def load_file(file, relative_to // nil) do
file = find_file(file, relative_to)
server_call { :loaded, file }
Erlang.elixir_compiler.file to_char_list(file)
file
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.
When requiring a file, you may skip passing .exs as extension as
Elixir automatically adds it for you.
"""
def require_file(file, relative_to // nil) do
file = find_file(file, relative_to)
case server_call({ :loaded, file }) do
:ok ->
Erlang.elixir_compiler.file to_char_list(file)
file
:duplicated -> nil
end
end
@doc """
Loads the compilation options from the code server.
Check compiler_options/1 for more information.
"""
def compiler_options do
server_call :compiler_options
end
@doc """
Sets compilation options. Those options are global
since they are stored by Elixir's Code Server.
Available options are:
* docs - when true, retain documentation in the compiled module;
* debug_info - when true, retain debug information in the compiled module.
Notice debug information can be used to reconstruct the source code;
* ignore_module_conflict - when true, override modules that were already defined;
"""
def compiler_options(opts) do
server_call { :compiler_options, opts }
end
@doc """
Compiles the given string and returns a list of tuples where
the first element is the module name and the second one is its
binary.
For compiling many files at once, check `Elixir.ParallelCompiler`.
"""
def compile_string(string, file // 'nofile') do
Erlang.elixir_compiler.string :unicode.characters_to_list(string), to_char_list(file)
end
@doc """
Ensure if the given module is loaded. If the module is already loaded,
it works as no-op. If the module was not loaded yet, it tries to load it.
If it succeeds loading the module anyhow, it returns `{ :module, module }`.
If not, returns `{ :error, reason }` with the error reason.
"""
def ensure_loaded(module) when is_atom(module) do
Erlang.code.ensure_loaded(module)
end
## Helpers
# Finds the file given the relative_to path.
# If the file is found, returns its path in binary, fails otherwise.
defp find_file(file, relative_to) do
file = to_binary(file)
file = if relative_to do
File.expand_path(file, relative_to)
else
File.expand_path(file)
end
if File.regular?(file) do
file
else
prefix = "#{file}.exs"
if File.regular?(prefix) do
prefix
else
raise ArgumentError, message: "could not load #{file}"
end
end
end
defp server_call(args) do
Erlang.gen_server.call(:elixir_code_server, args)
end
end
-152
View File
@@ -1,152 +0,0 @@
defprotocol Dict do
@only [Record]
@moduledoc """
This module provides the Dict protocol
with the goal of being a common API
to work with dictionaries.
"""
@doc """
Returns a list containing all dict's keys.
The keys are not guaranteed to be sorted, unless
the underlying dict implementation defines so.
## Examples
Dict.keys [a: 1, b: 2] #=> [:a,:b]
"""
def keys(dict)
@doc """
Returns a list containing all dict's values.
## Examples
Dict.values [a: 1, b: 2] #=> [1,2]
"""
def values(dict)
@doc """
Returns the number of elements in `dict`.
## Examples
Dict.size [a: 1, b: 2] #=> 2
"""
def size(dict)
@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
"""
def has_key?(dict, key)
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, returns `default` (or nil if not provided).
## Examples
Dict.get [a: 1], :a #=> 1
Dict.get [a: 1], :b #=> nil
Dict.get [a: 1], :b, 3 #=> 3
"""
def get(dict, key)
def get(dict, key, default)
@doc """
Stores the given `value` under `key` in `dict`.
If `dict` already has `key`, the stored value is replaced by the new one.
## Examples
Dict.put [a: 1, b: 2], :a, 3
#=> [a: 3, b: 2]
"""
def put(dict, key, val)
@doc """
Removes the entry stored under the given key from `dict`.
If `dict` does not contain `key`, returns the dictionary unchanged.
## Examples
Dict.delete [a: 1, b: 2], :a #=> [b: 2]
Dict.delete [b: 2], :a #=> [b: 2]
"""
def delete(dict, key)
@doc """
Merges two dicts into one. If the dicts have duplicated entries, the one
given as second argument wins.
## Examples
Dict.merge [a: 1, b: 2], [a: 3, d: 4]
#=> [a:3, b:2, d: 4]
"""
def merge(dict1, dict2)
@doc """
Merges two dicts into one. If the dicts have duplicated entries, the given
function is invoked to solve conflicts.
## Examples
Dict.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
v1 + v2
end
#=> [a: 4, b: 2, d: 4]
"""
def merge(dict1, dict2, fun)
@doc """
Update a value in `dict` by calling `fun` on the value to get a new
value. An exception is generated if `key` is not present in the dict.
## Examples
Dict.update [a: 1, b: 2], :a, fn val -> -val end
#=> [a: -1, b: 2]
"""
def update(dict, key, fun)
@doc """
Update a value in `dict` by calling `fun` on the value to get a new value. If
`key` is not present in `dict` then `initial` will be stored as the first
value.
## Examples
Dict.update [a: 1, b: 2], :c, 3, fn val -> -val end
#=> [a: 1, b: 2, c: 3]
"""
def update(dict, key, initial, fun)
@doc """
Returns an empty dict of the same type as `dict`.
"""
def empty(dict)
@doc """
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
"""
def to_list(dict)
end
+9 -20
View File
@@ -40,15 +40,16 @@ defmodule EEx do
`EEx.SmartEngine` supports the following tags:
<% Elixir expression - inline with output %>
<& Elxir matching expression - not printed &>
<%= 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
**must** use the equals sign (`=`). Since everything in
Elixir is a macro, there are no exceptions for this rule.
For example, while some template languages would special-
case `if` clauses, they are treated the same in EEx and
also requires `=` in order to have their result printed:
also require `=` in order to have their result printed:
<%= if true do %>
It is obviously true
@@ -56,27 +57,14 @@ defmodule EEx do
This will never appear
<% end %>
The `<& ... &>` expression is only used in matching clauses.
For example, the `cond` macro would be written as:
<%= cond do %>
<& false -> &> Never printed
<& true -> &> always printed
<% end %>
Notice that different engines may have different rules
for each tag. Other tags may be added in future versions.
### Macros
`EEx.SmartEngine` also adds two macros to your template.
The first one is the `for` macro, which allows you to easily
loop a variable:
EEx.eval_string "<%= for x in [1,2,3] do %><%= x %>\n<% end %>", []
#=> "1\n2\n3\n"
It also adds defines a macro named `@` that allows easy access:
`EEx.SmartEngine` also adds some macros to your template.
An example is the `@` macro which allows easy data access
in a template:
EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
#=> 1
@@ -105,8 +93,9 @@ defmodule EEx do
"""
defmacro function_from_string(kind, name, source, args // [], options // []) do
info = [file: __CALLER__.file, line: __CALLER__.line + 1]
quote do
info = Keyword.merge unquote(options), [file: __FILE__, line: __LINE__]
info = Keyword.merge unquote(info), unquote(options)
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
unquote(args), EEx.compile_string(unquote(source), info), info)
end
@@ -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
@@ -75,7 +75,7 @@ defmodule EEx.Compiler do
# Check if the syntax node represents an empty string
defp is_empty?(bin) when is_binary(bin) do
bc(<<c>> in bin when not c in [?\s,?\t,?\r,?\n], do: <<c>>) == ""
bc(<<c>> inbits bin, not c in [?\s,?\t,?\r,?\n], do: <<c>>) == ""
end
defp is_empty?({ :<>, _, [left, right] }) do
@@ -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
@@ -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
@@ -6,12 +6,12 @@ defmodule EEx.TransformerEngine do
overridable directive that allows a developer to
customize the expression returned by the engine.
Check `EEx.AssignsEngine`, `EEx.ForEngine` and
`EEx.SmartEngine` for examples of using this module.
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
examples of using this module.
"""
@doc false
defmacro __using__(_, _) do
defmacro __using__(_) do
quote do
@behavior EEx.Engine
@@ -32,7 +32,7 @@ defmodule EEx.TransformerEngine do
end
defp transform(list) when is_list(list) do
lc i in list, do: transform(i)
lc i inlist list, do: transform(i)
end
defp transform(other) do
@@ -70,48 +70,16 @@ defmodule EEx.AssignsEngine do
"""
@doc false
defmacro __using__(_, _) do
quote [unquote: false] do
defmacro __using__(_) do
quote unquote: false do
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
quote(do: Keyword.get var!(assigns), unquote(name))
end
defp transform(_), do: super
defoverridable [transform: 1]
end
end
end
defmodule EEx.ForEngine do
@moduledoc %B"""
An abstract engine that, when used with the
`TransformerEngine`, allows a developer to easily loop
using `for`.
This engine is included by default on the SmartEngine.
## Examples
defmodule MyEngine do
use EEx.TransformerEngine
use EEx.ForEngine
defp transform(_) do
super
end
EEx.eval_string("<%= for x in [1,2,3] do %><%= x %>\n<% end %>", assigns: [foo: 1])
#=> "1\n2\n3\n"
"""
@doc false
defmacro __using__(_, _) do
quote [unquote: false] do
defp transform({ :for, line, [{ :in, _, [var, collection] }, opts] }) do
quote do
Enum.map_join(unquote(collection), "", fn(unquote(var), unquote(opts)))
end
end
defp transform(_), do: super
defoverridable [transform: 1]
end
end
@@ -120,13 +88,10 @@ end
defmodule EEx.SmartEngine do
use EEx.TransformerEngine
use EEx.AssignsEngine
use EEx.ForEngine
@moduledoc """
An engine meant for end-user usage that includes both
`AssignsEngine` and `ForEngine` conveniences. Therefore,
a developer can easily access assigns via `@` and loop
using `for`. Read `EEx.AssignsEngine` and `EEx.ForEngine`
for examples.
An engine meant for end-user usage that includes
`AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
+159
View File
@@ -0,0 +1,159 @@
defmodule EEx.Tokenizer do
@moduledoc false
@doc """
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
* { :text, line, contents }
* { :expr, line, marker, contents }
* { :start_expr, line, marker, contents }
* { :end_expr, line, marker, contents }
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(binary_to_list(bin), line)
end
def tokenize(list, line) do
Enum.reverse(tokenize(list, line, line, [], []))
end
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
{ buffer, new_line, rest } = tokenize_expr t, line, [?%,?<|buffer]
tokenize rest, current_line, new_line, [?>,?%|buffer], acc
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, Enum.reverse(expr) }
tokenize rest, new_line, new_line, [], [final | acc]
end
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
tokenize t, current_line, line + 1, [?\n|buffer], acc
end
defp tokenize([h|t], current_line, line, buffer, acc) do
tokenize t, current_line, line, [h|buffer], acc
end
defp tokenize([], current_line, _line, buffer, acc) do
tokenize_text(current_line, buffer, acc)
end
# Retrieve marker for <%
defp retrieve_marker('=' ++ t) do
{ '=', t }
end
defp retrieve_marker(t) do
{ '', t }
end
# Tokenize an expression until we find %>
defp tokenize_expr([?%,?>|t], line, buffer) do
{ buffer, line, t }
end
defp tokenize_expr('\n' ++ t, line, buffer) do
tokenize_expr t, line + 1, [?\n|buffer]
end
defp tokenize_expr([h|t], line, buffer) do
tokenize_expr t, line, [h|buffer]
end
defp tokenize_expr([], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>"
end
# Receive an expression content and check
# if it is a start, middle or an end token.
#
# Start tokens finish with `do` and `fn ->`
# Middle tokens are marked with `->` or keywords
# End tokens contain only the end word
defp token_name([h|t]) when h in [?\s, ?\t] do
token_name(t)
end
defp token_name('od' ++ [h|_]) when h in [?\s, ?\t, ?)] do
:start_expr
end
defp token_name('>-' ++ rest) do
rest = Enum.reverse(rest)
# 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 = Enum.reverse(tokens)
fn_index = fn_index(tokens)
if fn_index && end_index(tokens) > fn_index do
:start_expr
else
:middle_expr
end
error ->
:middle_expr
end
end
defp token_name('esle' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('retfa' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('hctac' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('eucser' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('dne' ++ t), do: check_spaces(t, :end_expr)
defp token_name(_) do
:expr
end
defp fn_index(tokens) do
Enum.find_index(tokens, function do
{ :fn_paren, _ } -> true
{ :fn, _ } -> true
_ -> false
end)
end
defp end_index(tokens) do
Enum.find_index(tokens, match?({ :end, _ }, &1)) || :infinity
end
defp check_spaces(string, token) do
if only_spaces?(string), do: token, else: :expr
end
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
defp only_spaces?(other), do: other == []
# Tokenize the buffered text by appending
# it to the given accumulator.
defp tokenize_text(_line, [], acc) do
acc
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, list_to_binary(Enum.reverse(buffer)) } | acc]
end
end
+7
View File
@@ -0,0 +1,7 @@
defmodule EEx.Mixfile do
use Mix.Project
def project do
[app: :eex, version: System.version]
end
end
@@ -1,7 +1,7 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule EEx.SmartEngineTest do
use ExUnit.Case
use ExUnit.Case, async: true
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
@@ -12,7 +12,7 @@ defmodule EEx.SmartEngineTest do
end
test "evaluates with loops" do
assert_eval "1\n2\n3\n", "<%= for x in [1,2,3] do %><%= x %>\n<% end %>"
assert_eval "1\n2\n3\n", "<%= lc x inlist [1,2,3] do %><%= x %>\n<% end %>"
end
defp assert_eval(expected, actual, binding // []) do
@@ -1,7 +1,7 @@
Code.require_file "../../test_helper", __FILE__
Code.require_file "../../test_helper.exs", __FILE__
defmodule EEx.TokenizerTest do
use ExUnit.Case
use ExUnit.Case, async: true
require EEx.Tokenizer, as: T
test "simple chars lists" do
@@ -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 " },
@@ -51,7 +75,7 @@ baz %>
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><& false -> &>bar<& true -> &>baz<% end %>', 1) == [
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' cond do ' },
{ :middle_expr, 1, '', ' false -> ' },
@@ -1,13 +1,13 @@
Code.require_file "../test_helper", __FILE__
Code.require_file "../test_helper.exs", __FILE__
require EEx
defmodule EExText.Compiled do
def before_compile do
fill_in_stacktrace
{ __LINE__, hd(tl(System.stacktrace)) }
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
{ :erlang, 1, 2 }.tuple_to_list
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
filename = File.expand_path("../fixtures/eex_template_with_bindings.eex", __FILE__)
@@ -16,18 +16,24 @@ defmodule EExText.Compiled do
def after_compile do
fill_in_stacktrace
{ __LINE__, hd(tl(System.stacktrace)) }
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
@file "unknown"
def unknown do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
@file __ENV__
def other do
fill_in_stacktrace
{ __LINE__, hd(tl(System.stacktrace)) }
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
defp fill_in_stacktrace do
try do
Erlang.erlang.error "failed"
:erlang.error "failed"
catch
:error, _, stack -> stack
end
@@ -43,7 +49,7 @@ defmodule Clause do
end
defmodule EExTest do
use ExUnit.Case
use ExUnit.Case, async: true
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
@@ -134,7 +140,7 @@ foo
string = """
foo
<%= __LINE__ %>
<%= __ENV__.line %>
"""
assert_eval expected, string
@@ -152,9 +158,9 @@ foo
string = """
foo
<%= if true do %>
<%= __LINE__ %>
<%= __ENV__.line %>
<% end %>
<%= __LINE__ %>
<%= __ENV__.line %>
"""
assert_eval expected, string
@@ -171,10 +177,10 @@ true
string = """
foo
<%= if __LINE__ == 2 do %>
<%= if __ENV__.line == 2 do %>
<%= true %>
<% end %>
<%= __LINE__ %>
<%= __ENV__.line %>
"""
assert_eval expected, string
@@ -192,11 +198,11 @@ true
string = """
foo
<%= cond do %>
<& false -> &> false
<& __LINE__ == 4 -> &>
<% false -> %> false
<% __ENV__.line == 4 -> %>
<%= true %>
<% end %>
<%= __LINE__ %>
<%= __ENV__.line %>
"""
assert_eval expected, string
@@ -214,11 +220,51 @@ foo
string = """
foo
<%= if false do %>
<%= __LINE__ %>
<%= __ENV__.line %>
<% else %>
<%= __LINE__ %>
<%= __ENV__.line %>
<% end %>
<%= __ENV__.line %>
"""
assert_eval expected, string
end
test "properly handle functions" do
expected = """
Number 1
Number 2
Number 3
"""
string = """
<%= Enum.map [1,2,3], fn x -> %>
Number <%= x %>
<% end %>
"""
assert_eval expected, string
end
test "do not consider already finished functions" do
expected = """
foo
true
"""
string = """
foo
<%= cond do %>
<% false -> %> false
<% fn -> 1 end -> %>
<%= true %>
<% end %>
<%= __LINE__ %>
"""
assert_eval expected, string
@@ -270,12 +316,21 @@ foo
}
}
assert EExText.Compiled.other ==
assert EExText.Compiled.unknown ==
{ 25,
{ EExText.Compiled,
:unknown,
0,
[file: 'unknown', line: 24]
}
}
assert EExText.Compiled.other ==
{ 31,
{ EExText.Compiled,
:other,
0,
[file: 'unknown', line: 24]
[file: binary_to_list(__FILE__), line: 30]
}
}
end
+1
View File
@@ -0,0 +1 @@
ExUnit.start []
-144
View File
@@ -1,144 +0,0 @@
defmodule EEx.Tokenizer do
@moduledoc false
@doc """
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
* { :text, line, contents }
* { :expr, line, marker, contents }
* { :start_expr, line, marker, contents }
* { :end_expr, line, marker, contents }
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(binary_to_list(bin), line)
end
def tokenize(list, line) do
List.reverse(tokenize(list, line, line, [], []))
end
defp tokenize([?<,h|t], current_line, line, buffer, acc) when h in [?&, ?%] do
{ marker, t } = retrieve_marker(h, t)
{ expr, new_line, rest } = tokenize_expr h, t, line, []
token = token_name(h, expr)
expr = List.reverse(expr)
# If it isn't a start or end token, it may be a middle token.
if token == :expr do
token = middle_expr_token_name(expr)
end
acc = tokenize_text(current_line, buffer, acc)
tokenize rest, new_line, new_line, [], [ { token, line, marker, expr } | acc]
end
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
tokenize t, current_line, line + 1, [?\n|buffer], acc
end
defp tokenize([h|t], current_line, line, buffer, acc) do
tokenize t, current_line, line, [h|buffer], acc
end
defp tokenize([], current_line, _line, buffer, acc) do
tokenize_text(current_line, buffer, acc)
end
# Retrieve marker for <%, <& is ignored
defp retrieve_marker(?%, '=' ++ t) do
{ '=', t }
end
defp retrieve_marker(_, t) do
{ '', t }
end
# Tokenize an expression until we find %> or &>
defp tokenize_expr(char, [char,?>|t], line, buffer) do
{ buffer, line, t }
end
defp tokenize_expr(char, '\n' ++ t, line, buffer) do
tokenize_expr char, t, line + 1, [?\n|buffer]
end
defp tokenize_expr(char, [h|t], line, buffer) do
tokenize_expr char, t, line, [h|buffer]
end
# Raise an error if the expected token is not found
defp tokenize_expr(char, [], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: " <> <<char, ?>>>
end
# Receive an expression content and check
# if it is a start, middle or an end token.
#
# Start tokens finish with `do` and '->'
# Middle tokens are marked as <& or keywords
# End tokens contain only the end word
defp token_name(?&, _), do: :middle_expr
defp token_name(?%, rest), do: token_name(rest)
defp token_name([h|t]) when h in [?\s, ?\t] do
token_name(t)
end
defp token_name('od' ++ [h|_]) when h in [?\s, ?\t, ?)] do
:start_expr
end
defp token_name('>-' ++ _) do
:start_expr
end
defp token_name('dne' ++ t) do
if only_spaces?(t), do: :end_expr, else: :expr
end
defp token_name(_) do
:expr
end
# Receive an expression contents and see if it matches
# a keyword block syntax, like else.
defp middle_expr_token_name([h|t]) when h in [?\s, ?\t] do
middle_expr_token_name(t)
end
defp middle_expr_token_name([h|_] = list) when h >= ?a and h <= ?z do
if valid_middle_identifier?(list), do: :middle_expr, else: :expr
end
defp middle_expr_token_name(_) do
:expr
end
defp valid_middle_identifier?('else' ++ rest), do: only_spaces?(rest)
defp valid_middle_identifier?('after' ++ rest), do: only_spaces?(rest)
defp valid_middle_identifier?('catch' ++ rest), do: only_spaces?(rest)
defp valid_middle_identifier?('rescue' ++ rest), do: only_spaces?(rest)
defp valid_middle_identifier?(_), do: false
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
defp only_spaces?(other), do: other == []
# Tokenize the buffered text by appending
# it to the given accumulator.
defp tokenize_text(_line, [], acc) do
acc
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, list_to_binary(List.reverse(buffer)) } | acc]
end
end
-106
View File
@@ -1,106 +0,0 @@
defmodule Elixir.IEx.UnicodeIO do
@moduledoc false
@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(cache, _count) do
prompt = case cache do
[] -> "iex> "
_ -> "...> "
end
:unicode.characters_to_list(Erlang.io.get_line(prompt))
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 :standard_error, result
end
end
defrecord Elixir.IEx.Config, io: nil, binding: nil, cache: '', counter: 0, scope: nil
defmodule Elixir.IEx do
@moduledoc false
import Exception, only: [format_stacktrace: 1]
def start(binding // [], io // Elixir.IEx.UnicodeIO) do
config = boot_config(binding, io)
function = fn -> do_loop(config) end
Erlang.user_drv.start([:"tty_sl -c -e", {:erlang, :spawn, [function]}])
end
def simple_start(binding // [], io // Elixir.IEx.UnicodeIO) do
config = boot_config(binding, io)
do_loop(config)
end
def c(files, path // ".") do
tuples = Elixir.ParallelCompiler.files_to_path List.wrap(files), path
Enum.map tuples, elem(&1, 1)
end
## Helpers
defp boot_config(binding, io) do
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit"
scope = Erlang.elixir.scope_for_eval(
file: 'iex',
delegate_locals_to: __MODULE__
)
Elixir.IEx.Config.new(io: io, binding: binding, scope: scope)
end
defp do_loop(config) do
io = config.io
config = config.increment_counter
counter = config.counter
cache = config.cache
code = cache ++ io.get(cache, counter)
new_config =
try do
{ result, new_binding, scope } =
Erlang.elixir.eval(code, config.binding, counter, config.scope)
io.put result
config.binding(new_binding).cache('').scope(scope)
rescue
TokenMissingError ->
config.cache(code)
exception ->
stacktrace = System.stacktrace
io.error "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace io, stacktrace
config.cache('')
catch
kind, error ->
stacktrace = System.stacktrace
io.error "** (#{kind}) #{inspect(error)}"
print_stacktrace io, stacktrace
config.cache('')
end
do_loop(new_config)
end
defp print_stacktrace(io, stacktrace) do
Enum.each stacktrace, fn s -> io.error " #{format_stacktrace(s)}" end
end
end
@@ -1,8 +1,6 @@
-define(ELIXIR_WRAP_CALL(Line, Module, Function, Args),
{ call, Line,
{ remote, Line,
{ record_field, 1, { atom, 1, '' }, { atom, Line, Module } },
{ atom, Line, Function } },
{ remote, Line, { atom, Line, Module }, { atom, Line, Function } },
Args
}).
@@ -10,25 +8,25 @@
-define(ELIXIR_MACRO(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
-record(elixir_scope, {
assign=false, %% when true, new variables can be defined in that subtree
guard=false, %% when true, we are inside a guard
noref=false, %% when true, don't resolve references
context=nil, %% can be assign, guards or nil
noname=false, %% when true, don't add new names (used by try)
check_clauses=true, %% when true, check def clauses ordering
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
name_args=false, %% when true, it means arguments should be named
macro=[], %% a stack with macros nesting
function=[], %% the current function
recur=[], %% the current loop function to be recurred
module=[], %% the current module
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
module=nil, %% the current module
function=nil, %% the current function
recur=nil, %% the current loop function to be recurred
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
filename="nofile", %% the current scope filename
local=[], %% the scope to evaluate local functions against
refer=[], %% an orddict with references by new -> old names
file=(<<"nofile">>), %% the current scope filename
local=nil, %% the scope to evaluate local functions against
aliases=[], %% an orddict with aliases by new -> old names
requires=elixir_dispatch:default_requires(), %% a set with modules required
macros=elixir_dispatch:default_macros(), %% a list with macros imported by module
functions=elixir_dispatch:default_functions(), %% a list with functions imported by module
+65
View File
@@ -0,0 +1,65 @@
import Kernel, except: [access: 2]
defprotocol Access do
@moduledoc """
The Access protocol is the underlying protocol invoked
when the brackets syntax is used. For instance, `foo[bar]`
is translated to `access foo, bar` which, by default,
invokes `Access.access` protocol.
This protocol is limited and is implemented only for the
following built-in types: keywords, records, atoms and
functions.
"""
@only [List, Function, Record, Atom]
@doc """
Receives the element being accessed and the access item.
"""
def access(element, qualifier)
end
defimpl Access, for: List do
@doc """
Access the given key in a keyword list.
## Examples
keywords = [a: 1, b: 2]
keywords[:a] #=> 1
"""
def access(list, atom) when is_atom(atom) do
Keyword.get(list, atom)
end
end
defimpl Access, for: Atom do
@doc """
The access protocol can only be accessed by atoms
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}"
end
end
defimpl Access, for: Function do
@doc """
The Access protocol for functions simply invokes
the function passing the item as argument. This
is useful because it allows a function to be
passed as argument in places a dict would also fit.
"""
def access(function, item) do
function.(item)
end
end
+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
@@ -1,4 +1,4 @@
import Elixir.Builtin, except: [to_binary: 1]
import Kernel, except: [to_binary: 1]
defprotocol Binary.Chars do
@moduledoc %B"""
@@ -8,7 +8,7 @@ defprotocol Binary.Chars do
`to_binary` which does the conversion.
The `to_binary` function automatically imported
by Elixir.Builtin invokes this protocol. String
by Kernel invokes this protocol. String
interpolation also invokes to_binary in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_binary(bar)`.
+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
+444
View File
@@ -0,0 +1,444 @@
import Kernel, except: [inspect: 1]
defprotocol Binary.Inspect do
@moduledoc """
The `Binary.Inspect` protocol is responsible for
converting any structure to a binary for textual
representation. All basic data structures
(tuple, list, function, pid, etc) implement the
inspect protocol. Other structures are advised to
implement the protocol in order to provide pretty
printing.
"""
@only [BitString, List, Tuple, Atom, Number, Any]
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
@moduledoc """
Represents the atom as an Elixir term. The atoms false, true
and nil are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
inspect(:foo) #=> ":foo"
inspect(nil) #=> "nil"
inspect(Foo.Bar) #=> "Foo.Bar"
"""
def inspect(false, _), do: "false"
def inspect(true, _), do: "true"
def inspect(nil, _), do: "nil"
def inspect(:"", _), do: ":\"\""
def inspect(Elixir, _), do: "Elixir"
def inspect(atom, _) do
binary = atom_to_binary(atom)
cond do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
"Elixir-" <> rest = binary
bc <<r>> inbits rest, do: <<to_dot(r)>>
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
":" <> escape(binary, ?")
end
end
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
defp to_dot(?-), do: ?.
defp to_dot(l), do: l
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
defp valid_ref_piece?(<<>>), do: true
defp valid_ref_piece?(_), do: false
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
end
defp valid_atom_identifier?(_), do: false
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
when h == ?_ do
valid_identifier? t
end
defp valid_identifier?(other), do: other
end
defimpl Binary.Inspect, for: BitString do
import Binary.Inspect.Utils
@moduledoc %B"""
Represents the string as itself escaping
all necessary characters.
## Examples
inspect("bar") #=> "bar"
inspect("f\"oo") #=> "f\"oo"
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
escape(thing, ?")
else
as_bitstring(thing, opts)
end
end
def inspect(thing, opts) do
as_bitstring(thing, opts)
end
## Bitstrings
defp as_bitstring(bitstring, opts) do
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
end
defp each_bit(_, 0) do
"..."
end
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
end
defp each_bit(<<h :: size(8)>>, _counter) do
integer_to_binary(h)
end
defp each_bit(<<>>, _counter) do
<<>>
end
defp each_bit(bitstring, _counter) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Binary.Inspect, for: List do
import Binary.Inspect.Utils
@moduledoc %B"""
Represents a list checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used.
Inspecting a list is conservative as it does not try
to guess how the list is encoded. That said, `'josé'`
will likely be inspected as `[106,111,115,195,169]`
because we can't know if it is encoded in utf-8
or iso-5569-1, which is common in Erlang libraries.
## Examples
inspect('bar') #=> 'bar'
inspect([0|'bar']) #=> "[0,98,97,114]"
inspect([:foo,:bar]) #=> "[:foo, :bar]"
"""
def inspect([], _), do: "[]"
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
## keywords?
defp keywords?([]), do: true
defp keywords?([{ key, _value } | rest]) when is_atom(key) do
keywords?(rest)
end
defp keywords?(_other), do: false
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
defp key_to_binary(key, opts) do
case Binary.Inspect.Atom.inspect(key, opts) do
":" <> right -> right
other -> other
end
end
## printable?
defp printable?([c|cs]) when is_integer(c) and c in 32..126 do
printable?(cs)
end
defp printable?([c|cs]) when c in [?\n, ?\r, ?\t, ?\v, ?\b, ?\f, ?\e] do
printable?(cs)
end
defp printable?([]), do: true
defp printable?(_), do: false
end
defimpl Binary.Inspect, for: Tuple do
import Binary.Inspect.Utils
@moduledoc """
Inspect tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
inspect({1,2,3}) #=> "{1,2,3}"
inspect(ArgumentError.new) #=> ArgumentError[message: "argument error"]
"""
def inspect({}, _), do: "{}"
def inspect(tuple, opts) do
unless opts[:raw] do
record_protocol(tuple, opts)
end || container_join(tuple, "{", "}", opts)
end
## Helpers
defp record_protocol(tuple, opts) do
name = elem(tuple, 0)
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
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 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 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 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
@moduledoc """
Represents the number as a binary.
## Examples
inspect(1) #=> "1"
"""
def inspect(thing, _) when is_integer(thing) do
list_to_binary integer_to_list(thing)
end
def inspect(thing, _) do
list_to_binary :io_lib.format("~p", [thing])
end
end
defimpl Binary.Inspect, for: Regex do
@moduledoc %B"""
Represents the Regex using the `%r""` syntax.
## Examples
inspect(%r/foo/m) #=> "%r\"foo\"m"
"""
def inspect(thing, _) do
"%r" <> Binary.Inspect.inspect(Regex.source(thing), []) <> Regex.opts(thing)
end
end
defimpl Binary.Inspect, for: Any do
@moduledoc """
For all other terms not implemented, we use the default
Erlang representation.
## Examples
inspect Process.self #=> "<0.35.0>"
"""
def inspect(thing, _) do
iolist_to_binary :io_lib.format('~p', [thing])
end
end
+126
View File
@@ -0,0 +1,126 @@
defmodule Bitwise do
@moduledoc """
This module provide macros and operators for bitwise operators.
These macros can be used in guards.
The easiest way to use is to simply import them into
your module:
use Bitwise
bnot 1 #=> -2
1 &&& 1 #=> 1
You can select to include only or skip operators by passing options:
use Bitwise, only_operators: true
1 &&& 1 #=> 1
"""
@doc """
Allow a developer to use this module in their programs with
the following options:
* `:only_operators` - Include only operators;
* `:skip_operators` - Skip operators;
"""
defmacro __using__(options) do
except = cond do
Keyword.get(options, :only_operators) ->
[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
quote do
import Bitwise, except: unquote(except)
end
end
@doc """
Bitwise not.
"""
defmacro bnot(expr) do
quote do: __op__ :bnot, unquote(expr)
end
@doc """
Bitwise not as operator.
"""
defmacro ~~~expr do
quote do: __op__ :bnot, unquote(expr)
end
@doc """
Bitwise and.
"""
defmacro band(left, right) do
quote do: __op__ :band, unquote(left), unquote(right)
end
@doc """
Bitwise and as operator.
"""
defmacro left &&& right do
quote do: __op__ :band, unquote(left), unquote(right)
end
@doc """
Bitwise or.
"""
defmacro bor(left, right) do
quote do: __op__ :bor, unquote(left), unquote(right)
end
@doc """
Bitwise or as operator.
"""
defmacro left ||| right do
quote do: __op__ :bor, unquote(left), unquote(right)
end
@doc """
Bitwise xor.
"""
defmacro bxor(left, right) do
quote do: __op__ :bxor, unquote(left), unquote(right)
end
@doc """
Bitwise xor as operator.
"""
defmacro left ^^^ right do
quote do: __op__ :bxor, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift left.
"""
defmacro bsl(left, right) do
quote do: __op__ :bsl, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift left as operator.
"""
defmacro left <<< right do
quote do: __op__ :bsl, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift right.
"""
defmacro bsr(left, right) do
quote do: __op__ :bsr, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift right as operator.
"""
defmacro left >>> right do
quote do: __op__ :bsr, unquote(left), unquote(right)
end
end
+373
View File
@@ -0,0 +1,373 @@
defmodule Code do
@moduledoc """
The Code module is responsible to manage code compilation,
code evaluation and code loading.
It complements (Erlang's code module)[1] to add behavior
which is specific to Elixir.
[1]: (www.erlang.org/doc/man/code.html)
"""
@doc """
Returns all the loaded files.
"""
def loaded_files 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
:code.add_pathz(File.expand_path to_char_list(path))
end
@doc """
Prepends a path to Erlang VM code path.
The path is expanded with `File.expand_path` before added.
"""
def prepend_path(path) do
: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 """
Evalutes the contents given by string. The second argument is the binding
(which should be a Keyword) followed by a keyword list of options. The
options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
otherwise functions are evaluated inside Erlang's default scope.
## Examples
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
When passing the __ENV__'s file and line, we could simply get
the location which already returns both fields as a keyword list:
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval(string, binding // [], opts // []) do
{ value, binding, _scope } =
:elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding }
end
@doc """
Evalutes the quoted contents.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
## Examples
contents = quote hygiene: false, do: a + b
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
For convenience, you can also pass the current __ENV__ and
the proper information will be extracted:
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval_quoted(quoted, binding // [], opts // [])
def eval_quoted(quoted, binding, Macro.Env[] = env) do
eval_quoted(quoted, binding, env.location)
end
def eval_quoted(quoted, binding, opts) do
{ value, binding, _scope } =
:elixir.eval_quoted [quoted], binding, opts
{ value, binding }
end
@doc """
Converts the given string to AST. It returns { :ok, ast }
if it succeeds, { :error, { line, error, token } } 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.
* `: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.forms(:unicode.characters_to_list(string), line, file, opts)
case res do
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
_ -> res
end
end
@doc """
Converts the given string to AST. It returns the ast if it succeeds,
raises an exception otherwise. The exception is a TokenMissingError
in case a token is missing (usually because the expression is incomplete),
SyntaxError otherwise.
Check `Code.string_to_ast/2` for options information.
"""
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, opts)
unpack_ast(line, res)
end
defp unpack_ast(_line, []), do: nil
defp unpack_ast(_line, [forms]) when not is_list(forms), do: forms
defp unpack_ast(line, forms), do: { :__block__, line, forms }
@doc """
Loads the given `file`. Accepts `relative_to` as an argument
to tell where the file is located. If the file was already
required/loaded, loads it again. It returns all the modules
defined in the file.
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 { :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, loads it again. It returns all the modules
defined in the file.
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({ :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
@doc """
Loads the compilation options from the code server.
Check compiler_options/1 for more information.
"""
def compiler_options do
server_call :compiler_options
end
@doc """
Sets compilation options. Those options are global
since they are stored by Elixir's Code Server.
Available options are:
* docs - when true, retain documentation in the compiled module.
True by default;
* debug_info - when true, retain debug information in the compiled module.
This allows a developer to reconstruct the original source
code, for such reasons, false by default;
* ignore_module_conflict - when true, override modules that were already defined
without raising errors, false by default;
"""
def compiler_options(opts) do
server_call { :compiler_options, opts }
end
@doc """
Compiles the given string and returns a list of tuples where
the first element is the module name and the second one is its
binary.
For compiling many files at once, check `Kernel.ParallelCompiler`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
:elixir_compiler.string :unicode.characters_to_list(string), to_binary(file)
end
@doc """
Ensures the given module is loaded. If the module is already
loaded, it works as no-op. If the module was not loaded yet,
it tries to load it.
If it succeeds loading the module anyhow, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
## Code loading on the Erlang VM
Erlang has two modes to load code: interactive and embedded.
By default, the Erlang VM runs on interactive mode, where modules
are loaded as needed. In embedded mode the opposite happens, as all
modules need to be loaded upfront or explicitly.
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 for a given
URI scheme.
## Code.ensure_compiled
Elixir also contains an `ensure_compiled/1` function that is a
superset of `ensure_loaded/1`.
Since Elixir's compilation happens in parallel, in some situations
you may need to use a module but it was not compiled yet, therefore
it can't even be loaded.
`ensure_compiled/1` puts a halt in the current process until the
module we are depending on is available.
In most of the cases, `ensure_loaded` is enough. `ensure_compiled`
must be used just in same rare conditions, usually involving macros
that needs to invoke a module for callback information.
"""
def ensure_loaded(module) when is_atom(module) do
: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 """
Ensures the given module is compiled and loaded. If the module
is already loaded, it works as no-op. If the module was not
loaded yet, it checks if it needs to be compiled first and just
then tries to load it.
If it succeeds loading the module anyhow, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
Check `ensure_loaded/1` for more information on module loading
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
"""
def ensure_compiled(module) when is_atom(module) do
case :code.ensure_loaded(module) do
{ :error, :nofile } = error ->
case :erlang.get(:elixir_ensure_compiled) do
:undefined -> error
_ ->
try do
module.__info__(:self)
{ :module, module }
rescue
UndefinedFunctionError -> error
end
end
other -> other
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.
# If the file is found, returns its path in binary, fails otherwise.
defp find_file(file, relative_to) do
file = to_binary(file)
file = if relative_to do
File.expand_path(file, relative_to)
else
File.expand_path(file)
end
if File.regular?(file) do
file
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}"
end
end
end
defp server_call(args) do
:gen_server.call(:elixir_code_server, args)
end
defp server_cast(args) do
:gen_server.cast(:elixir_code_server, args)
end
end
+220
View File
@@ -0,0 +1,220 @@
defmodule Dict do
@moduledoc """
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict based on
the tuple signature.
The keyword list used throughout Elixir cannot be
manipulated via the Dict module, you must use the
Keyword module instead. This distinction is intentional:
the Dict module is meant to work on structures that work
as storage.
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
the underlying dict implementation defines so.
## Examples
d = new [a: 1, b: 2]
Dict.keys d #=> [:a,:b]
"""
def keys(dict) do
elem(dict, 0).keys(dict)
end
@doc """
Returns a list containing all dict's values.
## Examples
d = new [a: 1, b: 2]
Dict.values d #=> [1,2]
"""
def values(dict) do
elem(dict, 0).values(dict)
end
@doc """
Returns the number of elements in `dict`.
## Examples
d = new [a: 1, b: 2]
Dict.size d #=> 2
"""
def size(dict) do
elem(dict, 0).size(dict)
end
@doc """
Returns whether the given key exists in the given dict.
## Examples
d = new [a: 1]
Dict.has_key?(d, :a) #=> true
Dict.has_key?(d, :b) #=> false
"""
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
contain `key`, returns `default` (or nil if not provided).
## Examples
d = new [a: 1]
Dict.get d, :a #=> 1
Dict.get d, :b #=> nil
Dict.get d, :b, 3 #=> 3
"""
def get(dict, key, default // nil) do
elem(dict, 0).get(dict, key, default)
end
@doc """
Stores the given `value` under `key` in `dict`.
If `dict` already has `key`, the stored value is replaced by the new one.
## Examples
d = new [a: 1, b: 2]
Dict.put d, :a, 3
#=> [a: 3, b: 2]
"""
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`.
If `dict` does not contain `key`, returns the dictionary unchanged.
## Examples
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) 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. 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
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) 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
function is invoked to solve conflicts.
## Examples
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) 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
value. An exception is generated if `key` is not present in the dict.
## Examples
d = new [a: 1, b: 2]
Dict.update d, :a, fn val -> -val end
#=> [a: -1, b: 2]
"""
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
`key` is not present in `dict` then `initial` will be stored as the first
value.
## Examples
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) do
elem(dict, 0).update(dict, key, initial, fun)
end
@doc """
Returns an empty dict of the same type as `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) do
elem(dict, 0).to_list(dict)
end
end
@@ -1,13 +1,15 @@
defmodule Dict.Common do
@moduledoc false
defmacro __using__(_module, 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 """
@@ -15,13 +17,13 @@ defmodule Dict.Common do
## Examples
#{unquote(inspect(__MODULE__))}.new [{:b,1},{:a,2}]
#{inspect(__MODULE__)}.new [{:b,1},{:a,2}]
#=> [a: 1, b: 2]
"""
def new(pairs) do
Enum.reduce pairs, new, fn { k, v }, dict ->
unquote(ref).put(dict, k, v)
put(dict, k, v)
end
end
@@ -31,13 +33,13 @@ defmodule Dict.Common do
## Examples
#{unquote(inspect(__MODULE__))}.new ["a", "b"], fn x -> {x, x} end
#{inspect(__MODULE__)}.new ["a", "b"], fn x -> {x, x} end
#=> ["a": "a", "b": "b"]
"""
def new(list, transform) when is_function(transform) do
Enum.reduce list, new(), fn i, dict ->
{ k, v } = transform.(i)
unquote(ref).put(dict, k, v)
put(dict, k, v)
end
end
end
File diff suppressed because it is too large Load Diff
@@ -56,22 +56,43 @@ defmodule Exception do
ErlangError.new original: other
end
# Check the given module is a valid record.
@doc false
def check!(module) do
unless :erlang.function_exported(module, :message, 1) do
raise "Expected #{inspect module} to implement message/1"
end
end
@doc """
Receives a module, fun and arity and returns a string
representing such invocation. Arity may also be a list
of arguments. It follows the same syntax as in stacktraces.
"""
def format_module_fun_arity(module, fun, arity) do
<< ?:, fun | :binary >> = inspect(fun)
case inspect(fun) do
<< ?:, fun :: binary >> -> :ok
fun -> :ok
end
if is_list(arity) do
inspected = lc x in arity, do: inspect(x)
inspected = lc x inlist arity, do: inspect(x)
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect module}.#{fun}/#{arity}"
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.
"""
@@ -81,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))
@@ -194,4 +215,12 @@ defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
end
defexception Keyword.KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
File diff suppressed because it is too large Load Diff
+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
+97
View File
@@ -0,0 +1,97 @@
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
@doc false
def get(dict(data), key, default) do
case :dict.find(key, data) do
{: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
@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(dict), do: HashDict.to_list(dict)
def count(dict), do: HashDict.size(dict)
end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
end
+138
View File
@@ -0,0 +1,138 @@
defmodule IO do
@moduledoc """
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;
"""
@doc """
Reads `count` bytes from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
"""
def read(device // :stdio, count) do
:io.get_chars(map_dev(device), "", count)
end
@doc """
Read a line from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
This function does the same as `gets/2`,
except the prompt is not required as argument.
"""
def readline(device // :stdio) do
:io.get_line(map_dev(device), "")
end
@doc """
Writes the given argument to the given device.
By default the device is the standard output.
The argument is expected to be a chardata (i.e.
a char list or an unicode binary).
It returns `:ok` if it succeeds.
## Examples
IO.write "sample"
#=> "sample"
IO.write :stderr, "error"
#=> "error"
"""
def write(device // :stdio, item) do
:io.put_chars map_dev(device), to_iodata(item)
end
@doc """
Writes the argument to the device, similarly to write
but adds a new line at the end. The argument is expected
to be a chardata.
"""
def puts(device // :stdio, item) do
erl_dev = map_dev(device)
: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, opts // []) do
puts device, Binary.Inspect.inspect(item, opts)
item
end
@doc """
Gets `count` bytes from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
"""
def getb(device // :stdio, prompt, count // 1) do
:io.get_chars(map_dev(device), to_iodata(prompt), count)
end
@doc """
Reads a line from the IO device. It returns:
* `data` - The characters in the line terminated
by a LF (or end of file).
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
"""
def gets(device // :stdio, prompt) do
:io.get_line(map_dev(device), to_iodata(prompt))
end
# Map the Elixir names for standard io and error to Erlang names
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other), do: other
defp to_iodata(io) when is_list(io) or is_binary(io), do: io
defp to_iodata(other), do: to_binary(other)
end
File diff suppressed because it is too large Load Diff
@@ -1,20 +1,18 @@
defrecord Elixir.CLI.Config, commands: [], close: [],
output: '.', compile: [], halt: true, compiler_options: []
defrecord Kernel.CLI.Config, commands: [], output: ".",
compile: [], halt: true, compiler_options: []
defmodule Elixir.CLI do
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, Elixir.CLI.Config.new)
{ config, argv } = process_options(options, Kernel.CLI.Config.new)
argv = lc arg in argv, do: list_to_binary(arg)
Erlang.gen_server.call(:elixir_code_server, { :argv, argv })
argv = lc arg inlist argv, do: list_to_binary(arg)
:gen_server.call(:elixir_code_server, { :argv, argv })
all_commands = List.reverse(config.commands) ++ List.reverse(config.close)
all_commands = Enum.reverse(config.commands)
try do
Enum.map all_commands, process_command(&1, config)
@@ -25,9 +23,9 @@ defmodule Elixir.CLI do
rescue
exception ->
at_exit(1)
stacktrace = System.stacktrace
IO.puts :standard_error, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace(stacktrace)
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.formatted_stacktrace(trace)
halt(1)
catch
:exit, reason when is_integer(reason) ->
@@ -38,9 +36,9 @@ defmodule Elixir.CLI do
halt(0)
kind, reason ->
at_exit(1)
stacktrace = System.stacktrace
IO.puts :standard_error, "** (#{kind}) #{inspect(reason)}"
print_stacktrace(stacktrace)
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
IO.puts Exception.formatted_stacktrace(trace)
halt(1)
end
end
@@ -48,24 +46,26 @@ defmodule Elixir.CLI do
## Private
defp at_exit(status) do
hooks = Erlang.gen_server.call(:elixir_code_server, :at_exit)
lc hook in hooks do
hooks = :gen_server.call(:elixir_code_server, :at_exit)
lc hook inlist hooks do
try do
hook.(status)
rescue
exception ->
IO.puts :standard_error, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace(System.stacktrace)
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.formatted_stacktrace(trace)
catch
kind, reason ->
IO.puts :standard_error, "** #{kind} #{inspect(reason)}"
print_stacktrace(System.stacktrace)
trace = System.stacktrace
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts Exception.formatted_stacktrace(trace)
end
end
end
defp invalid_option(option) do
IO.puts(:standard_error, "Unknown option #{list_to_binary(option)}")
IO.puts(:stderr, "Unknown option #{list_to_binary(option)}")
halt(1)
end
@@ -78,10 +78,6 @@ defmodule Elixir.CLI do
end
end
defp print_stacktrace(stacktrace) do
Enum.each stacktrace, fn s -> IO.puts :standard_error, " #{format_stacktrace(s)}" end
end
# Process shared options
defp process_shared(['-v'|t], config) do
@@ -89,8 +85,12 @@ defmodule Elixir.CLI do
process_shared t, config
end
defp process_shared(['--no-halt'|t], config) do
process_shared t, config.halt(false)
end
defp process_shared(['-e',h|t], config) do
process_shared t, config.prepend_commands [{:eval,h}]
process_shared t, config.prepend_commands [eval: h]
end
defp process_shared(['-pa',h|t], config) do
@@ -104,14 +104,20 @@ defmodule Elixir.CLI do
end
defp process_shared(['-r',h|t], config) do
h = list_to_binary(h)
config = Enum.reduce File.wildcard(h), config, fn path, config ->
config.prepend_commands [{:require, path}]
config.prepend_commands [require: path]
end
process_shared t, config
end
defp process_shared(['-pr',h|t], config) do
process_shared t, config.prepend_commands [{:parallel_require, h}]
h = list_to_binary(h)
process_shared t, config.prepend_commands [parallel_require: h]
end
defp process_shared([erl,_|t], config) when erl in ['--erl', '--sname', '--remsh', '--name'] do
process_shared t, config
end
defp process_shared(list, config) do
@@ -124,12 +130,18 @@ defmodule Elixir.CLI do
{ config, t }
end
def process_options(['--no-halt'|t], config) do
process_options t, config.halt(false)
def process_options(['--compile'|t], config) do
process_compiler t, config
end
def process_options(['+compile'|t], config) do
process_compiler t, config
def process_options(['-S',h|t], config) do
exec = System.find_executable(h)
if exec do
{ config.prepend_commands([require: list_to_binary(exec)]), t }
else
IO.puts(:stderr, "Could not find executable #{h}")
halt(1)
end
end
def process_options([h|t] = list, config) do
@@ -137,7 +149,8 @@ defmodule Elixir.CLI do
'-' ++ _ ->
shared_option? list, config, process_options(&1, &2)
_ ->
{ config.prepend_commands([{:require, h}]), t }
h = list_to_binary(h)
{ config.prepend_commands([require: h]), t }
end
end
@@ -152,19 +165,17 @@ defmodule Elixir.CLI do
end
defp process_compiler(['-o',h|t], config) do
process_compiler t, config.output(h)
process_compiler t, config.output(list_to_binary(h))
end
defp process_compiler(['--docs'|t], config) do
process_compiler t, config.merge_compiler_options(docs: true)
defp process_compiler(['--no-docs'|t], config) do
process_compiler t, config.merge_compiler_options(docs: false)
end
defp process_compiler(['--debug-info'|t], config) do
process_compiler t, config.merge_compiler_options(debug_info: true)
end
# This option is used internally so we can compile
# Elixir with Elixir without raising module conflicts
defp process_compiler(['--ignore-module-conflict'|t], config) do
process_compiler t, config.merge_compiler_options(ignore_module_conflict: true)
end
@@ -174,76 +185,42 @@ defmodule Elixir.CLI do
'-' ++ _ ->
shared_option? list, config, process_compiler(&1, &2)
_ ->
pattern = if File.dir?(h), do: '#{h}/**/*.ex', else: h
h = list_to_binary(h)
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.prepend_compile [pattern]
end
end
defp process_compiler([], config) do
{ config.prepend_commands([{:compile, config.compile}]), [] }
{ config.prepend_commands([compile: config.compile]), [] }
end
# Process commands
defp process_command({:eval, expr}, _config) do
Erlang.elixir.eval(expr, [])
defp process_command({:eval, expr}, _config) when is_list(expr) do
:elixir.eval(expr, [])
end
defp process_command({:require, file}, _config) do
defp process_command({:require, file}, _config) when is_binary(file) do
Code.require_file(file)
end
defp process_command({:parallel_require, pattern}, _config) do
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = File.wildcard(pattern)
files = List.uniq(files)
files = Enum.filter files, File.regular?(&1)
spawn_requires(files, [])
Kernel.ParallelRequire.files(files)
end
defp process_command({:compile, patterns}, config) do
Erlang.file.make_dir(config.output)
File.mkdir_p(config.output)
files = Enum.map patterns, File.wildcard(&1)
files = List.uniq(List.concat(files))
files = Enum.filter files, File.regular?(&1)
Code.compiler_options(config.compiler_options)
Elixir.ParallelCompiler.files_to_path(files, config.output,
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
end
# Responsible for spawning requires in parallel
# For now, we spawn at maximum four process at the same time
defp spawn_requires([], []), do: :done
defp spawn_requires([], waiting), do: wait_for_messages([], waiting)
defp spawn_requires(files, waiting) when length(waiting) >= 4 do
wait_for_messages(files, waiting)
end
defp spawn_requires([h|t], waiting) do
parent = Process.self
child = spawn_link fn ->
try do
Code.require_file(h)
parent <- { :required, Process.self }
catch
kind, reason ->
parent <- { :failure, Process.self, kind, reason, System.stacktrace }
end
end
spawn_requires(t, [child|waiting])
end
defp wait_for_messages(files, waiting) do
receive do
{ :required, child } ->
spawn_requires(files, List.delete(waiting, child))
{ :failure, _child, kind, reason, stacktrace } ->
Erlang.erlang.raise(kind, reason, stacktrace)
end
end
end
@@ -1,24 +1,24 @@
# Implement error_handler pattern for Erlang
# which is integrated with Elixir.ParallelCompiler
defmodule Elixir.ErrorHandler do
# which is integrated with Kernel.ParallelCompiler
defmodule Kernel.ErrorHandler do
@moduledoc false
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
case Code.ensure_loaded(module) do
{ :module, _ } -> []
{ :error, _ } ->
parent = Process.get(:elixir_parent_compiler)
parent <- { :waiting, Process.self, module }
parent = Process.get(:elixir_compiler_pid)
parent <- { :waiting, self(), module }
receive do
{ :release, ^parent } -> :ok
end
@@ -1,5 +1,5 @@
defmodule Elixir.ParallelCompiler do
refer Erlang.orddict, as: Orddict
defmodule Kernel.ParallelCompiler do
alias :orddict, as: OrdDict
@moduledoc """
A module responsible for compiling files in parallel.
@@ -15,9 +15,8 @@ defmodule Elixir.ParallelCompiler do
the current file stops being compiled until the dependency is
resolved.
A callback that receives every time a file is compiled
with the module names and binaries defined inside it can
be optionally given as argument.
A callback that is invoked every time a file is compiled
with its name can be optionally given as argument.
"""
def files(files, callback // default_callback) do
files_to_path(files, nil, callback)
@@ -27,10 +26,8 @@ defmodule Elixir.ParallelCompiler do
Compiles the given files to the given path.
Read files/2 for more information.
"""
def files_to_path(files, path, callback // default_callback) do
Code.ensure_loaded(Elixir.ErrorHandler)
files = Enum.map(files, to_char_list(&1))
path = if path, do: to_char_list(path)
def files_to_path(files, path, callback // default_callback) when is_binary(path) do
Code.ensure_loaded(Kernel.ErrorHandler)
spawn_compilers(files, path, callback, [], [], [])
end
@@ -42,22 +39,23 @@ defmodule Elixir.ParallelCompiler do
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], output, callback, waiting, queued, result) do
parent = Process.self()
parent = self()
child = spawn_link fn ->
Process.put(:elixir_parent_compiler, parent)
Process.flag(:error_handler, Elixir.ErrorHandler)
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, Process.self(), h }
parent <- { :compiled, self(), h }
catch
kind, reason ->
parent <- { :failure, Process.self(), kind, reason, System.stacktrace }
parent <- { :failure, self(), kind, reason, System.stacktrace }
end
end
@@ -69,7 +67,7 @@ defmodule Elixir.ParallelCompiler do
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], output, callback, waiting, queued, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _ } -> child <- { :release, Process.self() } end
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
wait_for_messages([], output, callback, waiting, queued, result)
end
@@ -82,27 +80,27 @@ defmodule Elixir.ParallelCompiler do
defp wait_for_messages(files, output, callback, waiting, queued, result) do
receive do
{ :compiled, child, file } ->
callback.(list_to_binary(file))
new_queued = List.keydelete(queued, child, 1)
callback.(file)
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)
IO.puts "== Compilation error on file #{list_to_binary(file)}#{extra} =="
Erlang.erlang.raise(kind, reason, stacktrace)
{^child, file} = List.keyfind(queued, child, 0)
IO.puts "== Compilation error on file #{file}#{extra} =="
:erlang.raise(kind, reason, stacktrace)
end
end
@@ -110,7 +108,7 @@ defmodule Elixir.ParallelCompiler do
defp release_waiting_processes(module, waiting) do
Enum.filter waiting, fn { child, waiting_module } ->
if waiting_module == module do
child <- { :release, Process.self() }
child <- { :release, self() }
false
else
true
+51
View File
@@ -0,0 +1,51 @@
defmodule Kernel.ParallelRequire do
@moduledoc """
A module responsible for requiring files in parallel.
"""
defmacrop default_callback, do: quote(do: fn x -> x end)
@doc """
Requires the given files.
A callback that is invoked every time a file is required
can be optionally given as argument.
"""
def files(files, callback // default_callback) do
spawn_requires(files, [], callback, [])
end
defp spawn_requires([], [], _callback, result), do: result
defp spawn_requires([], waiting, callback, result), do: wait_for_messages([], waiting, callback, result)
defp spawn_requires(files, waiting, callback, result) when length(waiting) >= 4 do
wait_for_messages(files, waiting, callback, result)
end
defp spawn_requires([h|t], waiting, callback, result) do
parent = self
child = spawn_link fn ->
try do
new = Code.require_file(h)
result = (new || []) ++ result
callback.(h)
parent <- { :required, self }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
end
end
spawn_requires(t, [child|waiting], callback, result)
end
defp wait_for_messages(files, waiting, callback, result) do
receive do
{ :required, child } ->
spawn_requires(files, List.delete(waiting, child), callback, result)
{ :failure, _child, kind, reason, stacktrace } ->
:erlang.raise(kind, reason, stacktrace)
end
end
end
+458
View File
@@ -0,0 +1,458 @@
defmodule Kernel.SpecialForms do
@moduledoc """
In this module we define Elixir special forms. Those are called
special forms because they cannot be overridden by the developer
and sometimes have lexical scope (like `alias`, `import`, etc).
This module also documents Elixir's pseudo variables (`__MODULE__`,
`__FILE__`, `__ENV__` and `__CALLER__`). Pseudo variables return
information about Elixir's compilation environment and can only
be read, never assigned to.
Finally, it also documents 3 special forms (`__block__`,
`__scope__` and `__aliases__`), which are not intended to be
called directly by the developer but they appear in quoted
contents since they are essential in Elixir's constructions.
"""
@doc """
Defines a new tuple.
## Examples
:{}.(1,2,3)
{ 1, 2, 3 }
"""
defmacro :{}.(args)
@doc """
Defines a new list.
## Examples
:[].(1,2,3)
[ 1, 2, 3 ]
"""
defmacro :[].(args)
@doc """
Defines a new bitstring.
## Examples
:<<>>.(1,2,3)
<< 1, 2, 3 >>
"""
defmacro :<<>>.(args)
@doc """
`alias` is used to setup atom aliases, often useful with modules names.
## Examples
`alias` can be used to setup an alias for any module:
defmodule Math do
alias MyKeyword, as: Keyword
end
In the example above, we have set up `MyOrdict` to be alias
as `Keyword`. So now, any reference to `Keyword` will be
automatically replaced by `MyKeyword`.
In case one wants to access the original `Keyword`, it can be done
by accessing Elixir:
Keyword.values #=> uses MyKeyword.values
Elixir.Keyword.values #=> uses Keyword.values
Notice that calling `alias` without the `as:` option automatically
sets an alias based on the last part of the module. For example:
alias Foo.Bar.Baz
Is the same as:
alias Foo.Bar.Baz, as: Baz
## Lexical scope
`import`, `require` and `alias` are called directives and all
have lexical scope. This means you can set up aliases inside
specific functions and it won't affect the overall scope.
"""
defmacro alias(module, opts)
@doc """
`require` is used to require the presence of external
modules so macros can be invoked.
## Examples
Notice that usually modules should not be required before usage,
the only exception is if you want to use the macros from a module.
In such cases, you need to explicitly require them.
Let's suppose you created your own `if` implementation in the module
`MyMacros`. If you want to invoke it, you need to first explicitly
require the `MyMacros`:
defmodule Math do
require MyMacros
MyMacros.if do_something, it_works
end
An attempt to call a macro that was not loaded will raise an error.
## Alias shortcut
`require` also accepts `as:` as an option so it automatically sets
up an alias. Please check `alias` for more information.
"""
defmacro require(module, opts)
@doc """
`import` allows one to easily access functions or macros from
others modules without using the qualified name.
## Examples
If you are using several functions from a given module, you can
import those functions and reference them as local functions,
for example:
import List
flatten([1,[2],3]) #=> [1,2,3]
## Selector
By default, Elixir imports functions and macros from the given
module, except the ones starting with underscore (which are
usually callbacks):
import List
A developer can change this behavior to include all macros and
functions, regardless if it starts with underscore, by passing
`:all` as first argument:
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
It is important to notice that `import` is lexical. This means you
can import specific macros inside specific functions:
defmodule Math do
def some_function do
# 1) Disable `if/2` from Kernel
import Kernel, except: [if: 2]
# 2) Require the new `if` macro from MyMacros
import MyMacros
# 3) Use the new macro
if do_something, it_works
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.
## Alias/Require shortcut
All imported modules are also required by default. `import`
also accepts `as:` as an option so it automatically sets up
an alias. Please check `alias` for more information.
"""
defmacro import(module, opts)
@doc """
Returns the current environment information as a `Macro.Env`
record. In the environment you can access the current filename,
line numbers, set up aliases, the current function and others.
"""
defmacro __ENV__
@doc """
Returns the current module name as an atom or nil otherwise.
Although the module can be accessed in the __ENV__, this macro
is a convenient shortcut.
"""
defmacro __MODULE__
@doc """
Returns the current file name as a binary.
Although the file can be accessed in the __ENV__, this macro
is a convenient shortcut.
"""
defmacro __FILE__
@doc """
Allows you to get the representation of any expression.
## Examples
quote do: sum(1, 2, 3)
#=> { :sum, 0, [1, 2, 3] }
## Homoiconicity
Elixir is an homoiconic language. Any Elixir program can be
represented using its own data structures. The building block
of Elixir homoiconicity is a tuple with three elements, for example:
{ :sum, 1, [1, 2, 3] }
The tuple above represents a function call to sum passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
another tuple in the same representation;
* The second element of the tuple is always an integer
representing the line number;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, meaning
that it may be a variable.
## Macro literals
Besides the tuple described above, Elixir has a few literals that
when quoted return themselves. They are:
:sum #=> Atoms
1 #=> Integers
2.0 #=> Floats
[1,2] #=> Lists
"binaries" #=> Binaries
{key, value} #=> Tuple with two elements
## Hygiene
Elixir macros are hygienic regarding to variables. This means
a variable defined in a macro cannot affect the scope where
the macro is included. Consider the following example:
defmodule Hygiene do
defmacro no_interference do
quote do: a = 1
end
end
require Hygiene
a = 10
Hygiene.no_interference
a #=> 10
In the example above, `a` returns 10 even if the macro
is apparently setting it to 1 because the variables defined
in the macro does not affect the context the macro is
executed. If you want to set or get a variable, you can do
it with the help of the `var!` macro:
defmodule NoHygiene do
defmacro interference do
quote do: var!(a) = 1
end
end
require NoHygiene
a = 10
NoHygiene.interference
a #=> 11
Notice that aliases are not hygienic in Elixir, ambiguity
must be solved by prepending Elixir:
quote do
Elixir.Foo #=> Access the root Foo
Foo #=> Access the Foo alias in the current module
(if any is set), then fallback to Elixir.Foo
end
## Options
* `:hygiene` - When false, disables hygiene;
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control which quote is able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
Read the Stacktrace information section below for more information;
## Stacktrace information
One of Elixir goals is to provide proper stacktrace whenever there is an
exception. In order to work properly with macros, the default behavior
in quote is to set the line to 0. When a macro is invoked and the quoted
expressions is expanded, 0 is replaced by the line of the call site.
This is a good behavior for the majority of the cases, except if the macro
is defining new functions. Consider this example:
defmodule MyServer do
use GenServer.Behaviour
end
`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.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:
quote location: :keep do
def handle_call(request, _from, state) do
{ :reply, :undef, state }
end
end
It is important to warn though that `location: :keep` evaluates the
code as if it was defined inside `GenServer.Behaviour` file, in
particular, the macro `__FILE__` will always point to
`GenServer.Behaviour` file.
"""
defmacro quote(opts, do: contents)
@doc """
Unquotes the given expression from inside a macro.
## Examples
Imagine the situation you have a variable `name` and
you want to inject it inside some quote. The first attempt
would be:
value = 13
quote do: sum(1, value, 3)
Which would then return:
{ :sum, 0, [1, { :value, 0, quoted }, 3] }
Which is not the expected result. For this, we use unquote:
value = 13
quote do: sum(1, unquote(value), 3)
#=> { :sum, 0, [1, 13, 3] }
"""
defmacro unquote(expr)
@doc """
Unquotes the given list expanding its arguments. Similar
to unquote.
## Examples
values = [2,3,4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, 0, [1, 2, 3, 4, 5] }
"""
defmacro unquote_splicing(expr)
@doc """
List comprehensions allow you to quickly build a list from another list:
lc n inlist [1,2,3,4], do: n * 2
#=> [2,4,6,8]
A comprehension accepts many generators and also filters. Generators
are defined using both `inlist` and `inbits` operators, allowing you
to loop lists and bitstrings:
# A list generator:
lc n inlist [1,2,3,4], do: n * 2
#=> [2,4,6,8]
# A bit string generator:
lc <<n>> inbits <<1,2,3,4>>, do: n * 2
#=> [2,4,6,8]
# A generator from a variable:
list = [1,2,3,4]
lc n inlist list, do: n * 2
#=> [2,4,6,8]
# A comprehension with two generators
lc x inlist [1,2], y inlist [2,3], do: x*y
#=> [2,3,4,6]
Filters can also be given:
# A comprehension with a generator and a filter
lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
#=> [2,4,6]
Bit string generators are quite useful when you need to
organize bit string streams:
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
iex> lc <<r:8,g:8,b:8>> inbits pixels, do: {r,g,b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
"""
defmacro lc(args)
@doc """
Defines a bit comprehension. It follows the same syntax as
a list comprehension but expects each element returned to
be a bitstring. For example, here is how to remove all
spaces from a string:
bc <<c>> inbits " hello world ", c != ? , do: <<c>>
"helloworld"
"""
defmacro bc(args)
@doc """
This is the special form used whenever we have a block
of expressions in Elixir. This special form is private
and should not be invoked directly:
quote do: (1; 2; 3)
#=> { :__block__, 0, [1,2,3] }
"""
defmacro __block__(args)
@doc """
This is the special form used whenever we have to temporarily
change the scope information of a block. Used when `quote` is
invoked with `location: :keep` to execute a given block as if
it belonged to another file.
quote location: :keep, do: 1
#=> { :__scope__, 1,[[file: "iex"], 1] }
Check `quote/1` for more information.
"""
defmacro __scope__(opts, args)
@doc """
This is the special form used to hold aliases information.
At compilation time, it is usually compiled to an atom:
quote do: Foo.Bar
{ :__aliases__, 0, [:Foo,:Bar] }
"""
defmacro __aliases__(args)
end
+245
View File
@@ -0,0 +1,245 @@
defmodule Kernel.Typespec do
@moduledoc """
This is the module that converts Elixir typespecs
to Erlang typespecs syntax. Everytime @spec, @type
and @typep are used they proxy to the functions
in this module.
"""
defmacro deftype(name, options // []) do
_deftype(name, true, __CALLER__, options)
end
defmacro deftypep(name) do
_deftype(name, false, __CALLER__, [])
end
defmacro defspec(spec, block) do
_defspec(:spec, __CALLER__, spec, block)
end
defmacro defcallback(spec, block) do
_defspec(:callback, __CALLER__, spec, block)
end
@doc """
Get the types defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
"""
def get_types(module) do
Module.get_attribute(module, :type) ++ Module.get_attribute(module, :opaque)
end
@doc """
Get the specs defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
"""
def get_specs(module) do
specs = :ets.tab2list(spec_table_for(module))
keys = :lists.ukeysort(1, specs)
lc { k, _ } inlist keys, do: { k, :proplists.append_values(k, specs) }
end
## Typespec conversion
# Handle unions
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
exprs = Enum.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line, :union, union }
end
# Handle binaries
defp typespec({:<<>>, line, []}, _,_) do
{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}
end
defp typespec({:<<>>, line, [{:|, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, 0}, {:integer, line2, unit}]}
end
defp typespec({:<<>>, line, [{:|, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, base}, {:integer, line2, 0}]}
end
# Handle ranges
defp typespec({:"..", line, args}, vars, caller) do
typespec({:range, line, args}, vars, caller)
end
# Handle aliases
defp typespec({:__aliases__, _, _} = alias, vars, caller) do
atom = Macro.expand alias, caller
typespec(atom, vars, caller)
end
# Handle type operator
defp typespec({:"::", line, [var, expr] }, vars, caller) do
left = typespec(var, [elem(var, 0)|vars], caller)
right = typespec(expr, vars, caller)
{ :ann_type, line, [left, right] }
end
# Handle unary ops
defp typespec({op, line, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
{ :op, line, op, {:integer, line, integer} }
end
# Handle remote calls
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
remote = Macro.expand remote, caller
unless is_atom(remote), do: raise(ArgumentError, message: "Invalid remote in typespec")
remote_type({typespec(remote, vars, caller), line, typespec(name, vars, caller), args}, vars, caller)
end
# Handle tuples
defp typespec({:tuple, line, atom}, vars, caller) when is_atom(atom) do
typespec({:{}, line, []}, vars, caller)
end
defp typespec({:{}, line, []}, _, _) do
{ :type, line, :tuple, :any }
end
defp typespec({:{}, line, t}, vars, caller) when is_list(t) do
args = lc e inlist t, do: typespec(e, vars, caller)
{ :type, line, :tuple, args }
end
# Handle funs
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
args =
case Enum.reverse(arguments) do
[[{:do,h}]|t] -> fn_args(line, Enum.reverse(t), h, vars, caller)
[] -> []
_ -> [fn_args(line, arguments, vars, caller)]
end
{ :type, line, :fun, args }
end
# Handle variables or local calls
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
{ :var, line, name }
else
typespec({name, line, []}, vars, caller)
end
end
# Handle local calls
defp typespec({name, line, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, name, arguments }
end
# Handle literals
defp typespec(atom, _, _) when is_atom(atom) do
{ :atom, 0, atom }
end
defp typespec(integer, _, _) when is_integer(integer) do
{ :integer, 0, integer }
end
defp typespec([], vars, caller) do
typespec({ nil, 0, [] }, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({ :list, 0, [spec] }, vars, caller)
end
defp typespec(l, _, _) when is_list(l) do
raise(ArgumentError, message: "Unexpected list #{inspect l}")
end
defp typespec(t, vars, caller) when is_tuple(t) do
args = lc e inlist tuple_to_list(t), do: typespec(e, vars, caller)
{ :type, 0, :tuple, args }
end
## Helpers
defp remote_type({remote, line, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :remote_type, line, [ remote, name, arguments ] }
end
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
defp collect_union(v), do: [v]
defp fn_args(line, args, return, vars, caller) do
[fn_args(line, args, vars, caller), typespec(return, vars, caller)]
end
defp fn_args(line, [{:"...", _, _}], _vars, _caller) do
{ :type, line, :any }
end
defp fn_args(line, args, vars, caller) do
args = lc arg inlist args, do: typespec(arg, vars, caller)
{ :type, line, :product, args }
end
def _deftype({:"::", _, [name, definition]}, export, caller, opts) do
_deftype(name, definition, export, caller, opts)
end
def _deftype(name, export, caller, opts) do
_deftype(name, { :term, caller.line, nil }, export, caller, opts)
end
defp _deftype({name, _, args}, definition, export, caller, options) do
args = if is_atom(args), do: [], else: lc(arg inlist args, do: variable(arg))
vars = lc {:var, _, var} inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc ({:var, _, _} = var) inlist args, do: var
attr = if options[:opaque], do: :opaque, else: :type
export = if export do
quote do: Module.compile_type(__MODULE__, :export_type, [{name, length(vars)}])
else
nil
end
quote do
name = unquote(name)
spec = unquote(Macro.escape(spec))
vars = unquote(Macro.escape(vars))
type = { name, spec, vars }
Module.compile_type __MODULE__, unquote(attr), type
unquote(export)
{ unquote(attr), type }
end
end
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
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)
quote do
code = unquote(code)
:ets.insert(unquote(table), code)
code
end
end
defp spec_table_for(module) do
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
end
defp variable({name, line, _}) do
{:var, line, name}
end
end
+300
View File
@@ -0,0 +1,300 @@
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.
A keyword may have duplicated keys, so it is not strictly
a dictionary. However most of the functions in this module
allows it to behave exactly as a dictionary. For example,
`Keyword.get` will get the first entry matching the given
key, regardless if duplicated entries exist. Similarly,
`Keyword.put` and `Keyword.delete` ensure all duplicated
entries for a given key are removed when invoked.
"""
@doc """
Creates a Keyword from enum. Differently from `Keyword.new`
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.map(enum, fn(x) -> x end)
end
@doc """
Returns an empty keyword list, i.e. an empty list.
"""
def new do
[]
end
@doc """
Creates a Keyword from an enumerable. Similarly to dicts,
duplicated entries are removed, the latest one prevails.
## Examples
Keyword.new [{:b,1},{:a,2}]
#=> [a: 2, b: 1]
"""
def new(pairs) do
Enum.reduce pairs, [], fn { k, v }, keywords ->
put(keywords, k, v)
end
end
@doc """
Creates a Keyword from an enumerable with the
help of the transformation function. Duplicated
entries are removed, the latest one prevails.
## Examples
Keyword.new [:a, :b], fn x -> {x,x} end
#=> [a: :a, b: :b]
"""
def new(pairs, transform) do
Enum.reduce pairs, [], fn i, keywords ->
{ k, v } = transform.(i)
put(keywords, k, v)
end
end
@doc """
Gets the value for specific key.
If key not exist return default value (nil if no default value)
exists.
If duplicated entries exist, the first one is returned.
Use get_values/2 to retrieve all entries.
## Examples
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) 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,
an error is raised.
## Examples
Keyword.get! [a: 1], :a #=> 1
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
"""
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.
## Examples
Keyword.get_values [a: 1, a: 2], :a
#=> [1,2]
"""
def get_values(keywords, key) do
lc { k, v } inlist keywords, key == k, do: v
end
@doc """
Returns all keys from the keyword list. Duplicated
keys appear duplicated in the final list of keys.
## Examples
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
"""
def keys(keywords) do
lc { key, _ } inlist keywords, do: key
end
@doc """
Returns all values.
## 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 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.
## Examples
Keyword.delete [a: 1, b: 2], :a #=> [b: 2]
Keyword.delete [b: 2], :a #=> [b: 2]
"""
def delete(keywords, key) do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
end
@doc """
Sets the given `value` under `key`.
If a previous value is already stored, all entries are
removed and the value is overriden.
## Examples
Keyword.put [a: 1, b: 2], :a, 3
#=> [a: 3, b: 2]
"""
def put(list, key, value) do
[{key, value}|delete(list, key)]
end
@doc """
Checks if two keywords are equal. I.e. they contain
the same keys and those keys contain the same values.
## Examples
Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
#=> true
"""
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
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
end
@doc """
Merges two keyword lists into one. If they have duplicated
entries, the given function is invoked to solve conflicts.
## Examples
Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
v1 + v2
end
#=> [a:4, b:2, d: 4]
"""
def merge(d1, d2, fun) do
do_merge(d2, d1, fun)
end
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
defp do_merge([], acc, _fun) do
acc
end
@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.has_key?([a: 1], :a)
#=> true
Keyword.has_key?([a: 1], :b)
#=> 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
+83 -102
View File
@@ -10,14 +10,6 @@ defmodule List do
first argument.
"""
@doc """
Simply invokes the Access protocol for the given list.
Check `Access.List` for more information.
"""
def access(list, access) when is_list(list) do
Access.List.access(list, access)
end
@doc """
Given a list of lists, concatenates the sublists into a single list.
@@ -28,7 +20,7 @@ defmodule List do
"""
def concat(list) when is_list(list) do
Erlang.lists.append(list)
:lists.append(list)
end
@doc """
@@ -44,10 +36,6 @@ defmodule List do
#=> [1,2,3,4,5,6]
"""
def concat([h], elements) when is_list(elements) do
[h|elements]
end
def concat(list, elements) when is_list(list) and is_list(elements) do
list ++ elements
end
@@ -64,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 """
@@ -82,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 """
@@ -103,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 """
@@ -117,20 +120,14 @@ 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]
"""
defdelegate [reverse: 1], to: Erlang.lists
@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
@doc """
Returns the last element in `list` or nil if the `list` is empty.
@@ -147,7 +144,9 @@ defmodule List do
"""
def last([]), do: nil
defdelegate [last: 1], to: Erlang.lists
def last(list) do
:lists.last(list)
end
@doc """
Checks if the given `term` is included in the list.
@@ -164,7 +163,7 @@ defmodule List do
"""
def member?(list, term) do
Erlang.lists.member(term, list)
:lists.member(term, list)
end
@doc """
@@ -174,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 """
@@ -195,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 """
@@ -216,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 """
@@ -251,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
@@ -284,7 +312,6 @@ defmodule List do
:lists.sort list
end
@doc """
Sorts the list according to an ordering function. fun(a, b) should
return true if `a` compares less than or equal to `b`, `false` otherwise.
@@ -312,41 +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 """
Looks for a term in a list and returns its position.
If term is found in the first position, return 1.
If no terms not found in list, the return value is nil.
### Examples
List.find_index ['a'], 'b'
#=> nil
List.find_index ['a'], 'a'
#=> 1
"""
def find_index(list, term) do
index = Erlang.string.str(list, [term])
case index == 0 do
true -> nil
false -> index
end
end
@doc """
Wraps the argument in a list.
If the argument is already a list, returns the list.
@@ -369,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
@@ -399,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
@@ -425,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 ->
@@ -444,7 +425,7 @@ defmodule List do
end
defp do_zip(_, _, acc) do
reverse acc
:lists.reverse acc
end
defp do_zip(list, acc) do
@@ -6,7 +6,7 @@ defprotocol List.Chars do
`to_char_list` which does the conversion.
The `to_char_list` function automatically imported
by Elixir.Builtin invokes this protocol.
by Kernel invokes this protocol.
"""
@only [BitString, List, Atom, Number, Record]
+489
View File
@@ -0,0 +1,489 @@
import Kernel, except: [to_binary: 1]
defmodule Macro do
@moduledoc """
This module provides conveniences for working with macros.
"""
@doc """
Returns a list of binary operators. This is available
as a macro so it can be used in guard clauses.
"""
defmacro binary_ops do
[
:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :/>, :=~,
:<, :>,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~
]
end
@doc """
Returns a list of unary operators. This is available
as a macro so it can be used in guard clauses.
"""
defmacro unary_ops do
[:!, :@, :^, :not, :+, :-]
end
@doc """
Recursively escapes the given value so it can be inserted
into a syntax tree. Structures that are valid syntax nodes
(like atoms, integers, binaries) are represented by themselves.
## Examples
Macro.escape(:foo)
#=> :foo
Macro.escape({ :a, :b, :c })
#=> { :{}, 0, [:a, :b, :c] }
"""
def escape({ left, right }) do
{ escape(left), escape(right) }
end
def escape(tuple) when is_tuple(tuple) do
{ :{}, 0, escape(tuple_to_list(tuple)) }
end
def escape(list) when is_list(list) do
lc item inlist list, do: escape(item)
end
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.
## Examples
Macro.to_binary(quote do: foo.bar(1, 2, 3))
#=> "foo.bar(1, 2, 3)"
"""
def to_binary(tree)
# Variables
def to_binary({ var, _, atom }) when is_atom(atom) do
atom_to_binary(var, :utf8)
end
# Aliases
def to_binary({ :__aliases__, _, refs }) do
Enum.map_join(refs, ".", call_to_binary(&1))
end
# Blocks
def to_binary({ :__block__, _, [expr] }) do
to_binary(expr)
end
def to_binary({ :__block__, _, _ } = expr) do
block = adjust_new_lines block_to_binary(expr), "\n "
"(\n " <> block <> "\n)"
end
# Bits containers
def to_binary({ :<<>>, _, args }) do
"<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
end
# Tuple containers
def to_binary({ :{}, _, args }) do
"{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
end
# List containers
def to_binary({ :[], _, args }) do
"[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
end
# Fn keyword
def to_binary({ :fn, _, [[do: block]] }) do
"fn " <> block_to_binary(block) <> "\nend"
end
# Partial call
def to_binary({ :&, _, [num] }) do
"&#{num}"
end
# Binary ops
def to_binary({ op, _, [left, right] }) when op in binary_ops do
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
end
# Unary ops
def to_binary({ op, _, [arg] }) when op in unary_ops do
atom_to_binary(op, :utf8) <> to_binary(arg)
end
# All other calls
def to_binary({ target, _, args }) when is_list(args) do
{ list, last } = :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)
end
end
# Two-item tuples
def to_binary({ left, right }) do
to_binary({ :{}, 0, [left, right] })
end
# Lists
def to_binary(list) when is_list(list) do
to_binary({ :[], 0, list })
end
# All other structures
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, 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)
defp call_to_binary({ :., _, [arg] }), do: call_to_binary(arg) <> "."
defp call_to_binary({ :., _, [left, right] }), do: call_to_binary(left) <> "." <> call_to_binary(right)
defp call_to_binary(other), do: to_binary(other)
defp call_to_binary_with_args(target, args) do
args = Enum.map_join(args, ", ", to_binary(&1))
call_to_binary(target) <> "(" <> args <> ")"
end
defp kw_blocks_to_binary(kw) do
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
case Keyword.has_key?(kw, x) do
true -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
false -> acc
end
end) <> "end"
end
defp kw_block_to_binary(key, value) do
block = adjust_new_lines block_to_binary(value), "\n "
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
end
defp block_to_binary({ :->, _, exprs }) do
Enum.map_join(exprs, "\n", fn({ left, right }) ->
left = Enum.map_join(left, ", ", to_binary(&1))
left <> " ->\n " <> adjust_new_lines block_to_binary(right), "\n "
end)
end
defp block_to_binary({ :__block__, _, exprs }) do
Enum.map_join(exprs, "\n", to_binary(&1))
end
defp block_to_binary(other), do: to_binary(other)
defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
"(" <> to_binary(expr) <> ")"
end
defp op_to_binary(expr), do: to_binary(expr)
defp adjust_new_lines(block, replacement) do
bc <<x>> inbits block do
<< case x == ?\n do
true -> replacement
false -> <<x>>
end :: binary >>
end
end
@doc """
Receives an expression representation and expands it. The following
contents are expanded:
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (__FILE__, __MODULE__, etc);
In case the expression cannot be expanded, it returns the expression itself.
Notice that `Macro.expand` is not recursive and it does not
expand child expressions. For example, `!some_macro` will expand as:
iex> IO.puts Macro.to_binary Macro.expand(quote(do: !some_macro), __ENV__)
case some_macro do
false -> true
nil -> true
_ -> false
end
Notice that the `!` operator is a macro that expands to a case.
Even though `some_macro` is also a macro, it is not expanded
because it is a child expression given to `!` as argument.
## Examples
In the example below, we have a macro that generates a module
with a function named `name_length` that returns the length
of the module name. The value of this function will be calculated
at compilation time and not at runtime.
Consider the implementation below:
defmacro defmodule_with_length(name, do: block) do
length = length(atom_to_list(name))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end
When invoked like this:
defmodule_with_length My.Module do
def other_function, do: ...
end
The compilation will fail because `My.Module` when quoted
is not an atom, but a syntax tree as follow:
{:__aliases__, 0, [:My, :Module] }
That said, we need to expand the aliases node above to an
atom, so we can retrieve its length. Expanding the node is
not straight-forward because we also need to expand the
caller aliases. For example:
alias MyHelpers, as: My
defmodule_with_length My.Module do
def other_function, do: ...
end
The final module name will be `MyHelpers.Module` and not
`My.Module`. With `Macro.expand`, such aliases are taken
into consideration. Local and remote macros are also
expanded. We could rewrite our macro above to use this
function as:
defmacro defmodule_with_length(name, do: block) do
expanded = Macro.expand(name, __CALLER__)
length = length(atom_to_list(expanded))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end
"""
def expand(aliases, env)
# The first case we handle is __aliases__. In case
# aliases just contain one item, we are sure it is
# an atom, so we just expand it based on the aliases
# dict.
def expand({ :__aliases__, _, [h] }, env) when h != Elixir do
expand_alias(h, env)
end
# In case aliases contains more than one item, we need
# to loop them checking if they are all atoms or not.
# Macros and pseudo-variables are then expanded.
def expand({ :__aliases__, _, [h|t] } = original, env) do
aliases = case h do
x when is_atom(x) and x != Elixir -> [expand_alias(x, env)|t]
_ -> [h|t]
end
aliases = lc alias inlist aliases, do: expand(alias, env)
case :lists.all(is_atom(&1), aliases) do
true -> :elixir_aliases.concat(aliases)
false -> original
end
end
# 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
def expand({ :__FILE__, _, atom }, env) when is_atom(atom), do: env.file
def expand({ :__ENV__, _, atom }, env) when is_atom(atom), do: env
# Expand possible macro import invocation
def expand({ atom, line, args } = original, env) when is_atom(atom) do
args = case is_atom(args) do
true -> []
false -> args
end
case not is_partial?(args) do
false -> original
true ->
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
{ :error, _ } -> original
end
end
end
# Expand possible macro require invocation
def expand({ { :., _, [left, right] }, line, args } = original, env) when is_atom(right) do
receiver = expand(left, env)
case is_atom(receiver) and not is_partial?(args) do
false -> original
true ->
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
args, env.module, env.function, env.requires, env)
case expand do
{ :ok, expanded } -> expanded
{ :error, _ } -> original
end
end
end
# Anything else is just returned
def expand(other, _env), do: other
## Helpers
defp is_partial?(args) do
:lists.any(match?({ :&, _, [_] }, &1), args)
end
defp expand_alias(h, env) do
atom = list_to_atom('Elixir-' ++ atom_to_list(h))
:elixir_aliases.lookup(atom, env.aliases)
end
@doc """
Recurs the quoted expression checking if all sub terms are
safe (i.e. they represented data structured and don't actually
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
+73
View File
@@ -0,0 +1,73 @@
defmodule Macro.Env do
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__.
"""
@doc """
Returns the current module name.
"""
def module(record)
@doc """
Returns the current file name as a binary.
"""
def file(record)
@doc """
Returns the current line as an integer.
"""
def line(record)
@doc """
Returns a tuple as { Atom, Integer }, where the first element
is the function name and the seconds its arity. Returns `nil`
if not inside a function.
"""
def function(record)
@doc """
Returns a list of two item tuples, where the first
item is the aliased name and the second the actual name.
"""
def aliases(record)
@doc """
Returns the context of the environment. It can be nil
(default context), inside a guard or inside an assign.
"""
def context(record)
@doc """
Returns the list of required modules.
"""
def requires(record)
@doc """
Returns a list of macros imported from each module.
"""
def macros(record)
@doc """
Returns a keyword list containing the file and line
information as keys.
"""
def location(record) do
[file: file(record), line: line(record)]
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
+627
View File
@@ -0,0 +1,627 @@
defmodule Module do
require :ets, as: ETS
@moduledoc """
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
After the module is compiled, using many of the functions in
this module will raise errors, since it is out of their purpose
to inspect runtime data. Most of the runtime data can be inspected
via the `__info__(attr)` function attached to each compiled module.
"""
@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.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
## Examples
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __MODULE__, contents, []
end
Foo.sum(1, 2) #=> 3
This function also accepts a `Macro.Env` as first argument. This
is useful to evalute the quoted contents inside an existing
environment (considering the environemnt module, line and file):
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __ENV__, contents, []
end
Foo.sum(1, 2) #=> 3
"""
def eval_quoted(env, quoted, binding // [], opts // [])
def eval_quoted(Macro.Env[module: module] = env, quoted, binding, opts) do
eval_quoted(module, quoted, binding, Keyword.merge(env.location, opts))
end
def eval_quoted(module, quoted, binding, opts) do
assert_not_compiled!(:eval_quoted, module)
:elixir_module.eval_quoted(module, quoted, binding, opts)
end
@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.
## Examples
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 """
Concatenates the list of aliases and returns a new alias.
It handles char lists, binaries and atoms.
## Examples
Module.concat [Foo, Bar] #=> Foo.Bar
Module.concat [Foo, "Bar"] #=> Foo.Bar
Module.concat [Foo, 'Bar'] #=> Foo.Bar
"""
def concat(list) when is_list(list) do
:elixir_aliases.concat(list)
end
@doc """
Concatenates the two given aliases and returns a new alias.
It handles char lists, binaries and atoms.
## Examples
Module.concat Foo, Bar #=> Foo.Bar
Module.concat Foo, "Bar" #=> Foo.Bar
Module.concat Foo, 'Bar' #=> Foo.Bar
"""
def concat(left, right) do
:elixir_aliases.concat([left, right])
end
@doc """
Concatenates the list aliases and returns a new alias only
if the alias was already referenced. If the alias was not
referenced yet, fails with ArgumentError.
It handles char lists, binaries and atoms.
## Examples
Module.safe_concat [Unknown, Module]
#=> ArgumentError
Module.safe_concat [List, Chars]
#=> List.Chars
"""
def safe_concat(list) when is_list(list) do
:elixir_aliases.safe_concat(list)
end
@doc """
Concatenates the two aliases and returns a new alias only
if the alias was already referenced. If the alias was not
referenced yet, fails with ArgumentError.
It handles char lists, binaries and atoms.
## Examples
Module.safe_concat Unknown, Module
#=> ArgumentError
Module.safe_concat List, Chars
#=> List.Chars
"""
def safe_concat(left, right) do
:elixir_aliases.safe_concat([left, right])
end
@doc """
Attaches documentation to a given function. It expects
the module the function belongs to, the line (a non negative
integer), the kind (def or defmacro), a tuple representing
the function and its arity and the documentation, which should
be either a binary or a boolean.
## Examples
defmodule MyModule do
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, { :version, 0 }, [], "Manually added docs")
def version, do: 1
end
"""
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop] do
if doc, do: { :error, :private_doc }, else: :ok
end
def add_doc(module, line, kind, tuple, signature, doc) when
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)
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
{ simplify_signature(x, line, acc), acc + 1 }
end
case ETS.lookup(table, tuple) do
[] ->
ETS.insert(table, { tuple, line, kind, signature, doc })
:ok
[{ tuple, line, old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
ETS.insert(table, {
tuple,
line,
merge_kind(old_kind, kind),
merge_signatures(old_sign, signature, 1),
doc || old_doc
})
:ok
_ ->
{ :error, :existing_doc }
end
end
# Simplify signatures to be stored in docs
defp simplify_signature({ ://, defline, [left, right ] }, line, i) do
{ ://, defline, [simplify_signature(left, line, i), right] }
end
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
case atom_to_list(var) do
[?_|_] -> { var, line, :guess }
_ -> { var, line, nil }
end
end
defp simplify_signature({ :=, _, [_, right] }, line, i) do
simplify_signature(right, line, i)
end
defp simplify_signature(other, line, i) when is_integer(other), do: { :"int#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_boolean(other), do: { :"bool#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_atom(other), do: { :"atom#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_list(other), do: { :"list#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_float(other), do: { :"float#{i}", line, :guess }
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
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)]
end
defp merge_signatures([], [], _) do
[]
end
defp merge_signature({ ://, line, [left, right] }, newer, i) do
{ ://, line, [merge_signature(left, newer, i), right] }
end
defp merge_signature(older, { ://, _, [left, _] }, i) do
merge_signature(older, left, i)
end
# The older signature, when given, always have higher precedence
defp merge_signature({ _, _, nil } = older, _newer, _), do: older
defp merge_signature(_older, { _, _, nil } = newer, _), do: newer
# Both are a guess, so check if they are the same guess
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
# Otherwise, returns a generic guess
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, :guess }
@doc """
Checks if the module defines the given function or macro.
Use `defines?/3` to assert for an specific type.
## Examples
defmodule Example do
Module.defines? __MODULE__, { :version, 0 } #=> false
def version, do: 1
Module.defines? __MODULE__, { :version, 0 } #=> true
end
"""
def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
ETS.lookup(table, tuple) != []
end
@doc """
Checks if the module defines a function or macro with the
given `kind`. `kind` can be either `:def`, `:defp`,
`:defmacro` or `:defmacrop`.
## Examples
defmodule Example do
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
def version, do: 1
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
end
"""
def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
case ETS.lookup(table, tuple) do
[{ _, ^kind, _, _, _, _, _, _ }] -> true
_ -> false
end
end
@doc """
Return all functions defined in the given module.
## Examples
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__ #=> [{:version,1}]
end
"""
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, _, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
end
@doc """
Returns all functions defined in te given module according
to its kind.
## Examples
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__, :def #=> [{:version,1}]
Module.definitions_in __MODULE__, :defp #=> []
end
"""
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, stored_kind, _, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
end
@doc """
Makes the given functions in the given module overridable.
An overridable function is lazily defined, allowing a
developer to customize it.
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
table = function_table_for(module)
lc tuple inlist tuples do
case ETS.lookup(table, tuple) do
[clause] ->
ETS.delete(table, tuple)
old = get_attribute(module, :__overridable)
new = [ { tuple, { 1, [clause] } } ]
merged = :orddict.merge(fn(_k, { count, v1 }, _v2) -> { count + 1, [clause|v1] } end, old, new)
put_attribute(module, :__overridable, merged)
_ ->
{ name, arity } = tuple
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
end
end
end
@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 """
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.put_attribute __MODULE__, :custom_threshold_for_lib, 10
end
"""
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)
new =
if List.member?(acc, key) do
case ETS.lookup(table, key) do
[{^key,old}] -> [value|old]
[] -> [value]
end
else
value
end
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 """
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.put_attribute __MODULE__, :value, 1
Module.get_attribute __MODULE__, :value #=> 1
Module.register_attribute __MODULE__, :value, accumulate: true
Module.put_attribute __MODULE__, :value, 1
Module.get_attribute __MODULE__, :value #=> [1]
end
"""
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
[{^key,old}] -> old
[] ->
acc = ETS.lookup_element(table, :__acc_attributes, 2)
if List.member?(acc, key), do: [], else: nil
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.put_attribute __MODULE__, :custom_threshold_for_lib, 10
Module.delete_attribute __MODULE__, :custom_threshold_for_lib
end
"""
def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module)
ETS.delete(table, key)
end
@doc """
Registers an attribute. By registering an attribute, a developer
is able to customize how Elixir will store and accumulate the
attribute values.
## Options
When registering an attribute, two options can be given:
* `:accumulate` - Several calls to the same attribute will
accumulate instead of override the previous one;
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are true. Which means that registering
an attribute without passing any options will revert the attribute
behavior to exactly the same expected in :
## Examples
defmodule MyModule do
Module.register_attribute __MODULE__,
:custom_threshold_for_lib,
accumulate: true, persist: false
@custom_threshold_for_lib 10
@custom_threshold_for_lib 20
@custom_threshold_for_lib #=> [20, 10]
end
"""
def register_attribute(module, new, opts // []) do
assert_not_compiled!(:register_attribute, module)
table = data_table_for(module)
if Keyword.get(opts, :persist, true) do
old = ETS.lookup_element(table, :__persisted_attributes, 2)
ETS.insert(table, { :__persisted_attributes, [new|old] })
end
if Keyword.get(opts, :accumulate, true) do
old = ETS.lookup_element(table, :__acc_attributes, 2)
ETS.insert(table, { :__acc_attributes, [new|old] })
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(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)
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!(:put_attribute, module)
table = data_table_for(module)
new =
case ETS.lookup(table, key) do
[{^key,old}] -> [value|old]
[] -> [value]
end
ETS.insert(table, { key, new })
end
## Helpers
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
{ atom, 0 }
end
defp normalize_attribute(kind, atom) when kind in [:behavior, :behaviour] and is_atom(atom) do
Code.ensure_compiled(atom)
atom
end
defp normalize_attribute(:file, Macro.Env[file: file, line: line]), do: { binary_to_list(file), line}
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
defp normalize_attribute(key, atom) when 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
raise ArgumentError, message: "Attributes type, typep, export_type, opaque and callback " <>
"must be set via Kernel.Typespec"
end
defp normalize_attribute(_key, value) do
value
end
defp data_table_for(module) do
module
end
defp function_table_for(module) do
list_to_atom :lists.concat([:f, module])
end
defp docs_table_for(module) do
list_to_atom :lists.concat([:o, module])
end
defp assert_not_compiled!(fun, module) do
open?(module) ||
raise ArgumentError,
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
end
end
+52
View File
@@ -3,6 +3,13 @@ defmodule Node do
Functions related to Erlang nodes.
"""
@doc """
Returns the current node. It returns the same as the built-in node().
"""
def self do
:erlang.node()
end
@doc """
Returns true if the local node is alive; that is, if the node can be
part of a distributed system. Otherwise, it returns false.
@@ -61,4 +68,49 @@ defmodule Node do
def disconnect(node) do
:erlang.disconnect_node(node)
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
the list of available options.
"""
def spawn(node, fun, opts // []) do
:erlang.spawn_opt(node, fun, opts)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists, a useless
pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
the list of available options.
"""
def spawn(node, module, fun, args, opts // []) do
:erlang.spawn(node, module, fun, args, opts)
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. A link is created between the calling process and the
new process, atomically. If `node` does not exist, a useless pid is returned
(and due to the link, an exit signal with exit reason :noconnection will be
received).
"""
def spawn_link(node, fun) do
:erlang.spawn_link(node, fun)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. A link is created between the calling
process and the new process, atomically. If `node` does not exist, a useless
pid is returned (and due to the link, an exit signal with exit reason
:noconnection will be received).
"""
def spawn_link(node, module, fun, args) do
:erlang.spawn_link(node, module, fun, args)
end
end
+140
View File
@@ -0,0 +1,140 @@
defmodule OptionParser do
@doc """
Parses the argv and returns one tuple with parsed options
and the arguments.
## Examples
OptionParser.parse(["--debug"])
#=> { [debug: true], [] }
OptionParser.parse(["--source", "lib"])
#=> { [source: "lib"], [] }
OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
#=> { [source: "lib", verbose: true], ["test/enum_test.exs"] }
## Aliases
A set of aliases can be given as second argument:
OptionParser.parse(["-d"], aliases: [d: :debug])
#=> { [debug: true], [] }
## Flags
A set of flags can be given as argument too. Those are considered
boolean and never consume the next value unless it is a boolean:
OptionParser.parse(["--unlock path/to/file"], flags: [:unlock])
#=> { [unlock: true], ["path/to/file"] }
OptionParser.parse(["--unlock false path/to/file"], flags: [:unlock])
#=> { [unlock: false], ["path/to/file"] }
## Negation switches
Any switches starting with `--no-` are always considered to be
booleans and never parse the next value.
OptionParser.parse(["--no-op path/to/file"])
#=> { [no_op: true], ["path/to/file"] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
dict = Keyword.new(flags, fn(k) -> { k, false } end)
parse(argv, aliases, flags, dict, [], true)
end
@doc """
Similar to parse but only parses the head of the argv.
I.e. as soon as it finds a non switch, it stops parsing.
Check `parse/2` for more info.
## Example
OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
#=> { [source: "lib"], ["test/enum_test.exs", "--verbose"] }
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
dict = Keyword.new(flags, fn(k) -> { k, false } end)
parse(argv, aliases, flags, dict, [], false)
end
## Helpers
defp parse(["-" <> option|t], aliases, flags, dict, args, all) do
{ option, value } = normalize_option(option, aliases)
if value == nil do
{ value, t } = if is_flag?(flags, option) do
flag_from_tail(t)
else
value_from_tail(t)
end
end
dict = store_option dict, option, value
parse(t, aliases, flags, dict, args, all)
end
defp parse([], _, _, dict, args, true) do
{ dict, Enum.reverse(args) }
end
defp parse([h|t], aliases, flags, dict, args, true) do
parse(t, aliases, flags, dict, [h|args], true)
end
defp parse(value, _, _, dict, _args, false) do
{ dict, value }
end
defp flag_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
defp flag_from_tail(t) , do: { true, t }
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
defp value_from_tail([h|t]), do: { h, t }
defp value_from_tail([]), do: { true, [] }
defp store_option(dict, option, value) when value in ["false", "true"] do
store_option(dict, option, binary_to_atom(value))
end
defp store_option(dict, option, value) do
Keyword.put dict, option, value
end
defp normalize_option(<<?-, option :: binary>>, aliases) do
normalize_option(option, aliases)
end
defp normalize_option(option, aliases) do
{ option, value } = split_option(option)
if is_no?(option), do: value = true
atom = option /> to_underscore /> binary_to_atom
{ aliases[atom] || atom, value }
end
defp split_option(option) do
case :binary.split(option, "=") do
[h] -> { h, nil }
[h|t] -> { h, Enum.join(t, "=") }
end
end
defp to_underscore(option) do
bc <<c>> inbits option, do: << if c == ?-, do: ?_, else: c >>
end
defp is_no?("no-" <> _), do: true
defp is_no?(_), do: false
defp is_flag?(flags, option), do: List.member?(flags, option)
end
+95
View File
@@ -0,0 +1,95 @@
defmodule OrdDict do
@moduledoc """
This module implements a dictionary type that stores items
as a list of tuples. It is a simple wrapper around
[Erlang's orddict module](http://www.erlang.org/doc/man/orddict.html)
and exposed via the `Dict` module.
Check the `Dict` module for examples and documentation.
"""
use Dict.Common
defmacrop dict(data) do
quote do
{ OrdDict, unquote(data) }
end
end
@doc false
def keys(dict(data)) do
lc { k, _ } inlist data, do: k
end
@doc false
def values(dict(data)) do
lc { _, v } inlist data, do: v
end
@doc false
def size(dict(data)) do
length(data)
end
@doc false
def has_key?(dict(data), key) do
:orddict.is_key key, data
end
@doc false
def get(dict(data), key, default) do
case :orddict.find(key, data) do
{:ok, value} -> value
:error -> default
end
end
@doc false
def put(dict(data), key, value) do
dict(:orddict.store key, value, data)
end
@doc false
def delete(dict(data), key) do
dict(:orddict.erase key, data)
end
@doc false
def merge(dict(d1), dict(d2), fun) do
dict(:orddict.merge fun, d1, d2)
end
@doc false
def merge(dict(_) = d1, d2, fun) do
merge(d1, new(d2), fun)
end
@doc false
def update(dict(data), key, fun) do
dict(:orddict.update key, fun, data)
end
@doc false
def update(dict(data), key, initial, fun) do
dict(:orddict.update key, fun, initial, data)
end
@doc false
def empty(_) do
dict([])
end
@doc false
def to_list(dict(data)) do
data
end
end
defimpl Enum.Iterator, for: OrdDict do
def iterator({ OrdDict, data }), do: data
def count({ OrdDict, data }), do: length(data)
end
defimpl Access, for: OrdDict do
def access(dict, key), do: OrdDict.get(dict, key, nil)
end
+73 -77
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()
@@ -49,7 +49,9 @@ defmodule Process do
@doc """
Returns all keys that have the given `value`.
"""
defdelegate [get_keys: 1], to: :erlang
def get_keys(value) do
:erlang.get_keys(value)
end
@doc """
Stores the given key-value in the process dictionary.
@@ -92,113 +94,72 @@ defmodule Process do
## Examples
Process.exit(other, :kil)
Process.exit(pid, :kill)
"""
defdelegate [exit: 2], to: :erlang
def exit(pid, status) do
:erlang.exit(pid, status)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
Otherwise works like spawn/3.
"""
defdelegate [spawn: 1], to: :erlang
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
Otherwise works like spawn/3.
It behaves exactly the same as the `Kernel.spawn/1` function except
it also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
"""
defdelegate [spawn: 2], to: :erlang
def spawn(fun, opts // []) do
:erlang.spawn_opt(fun, opts)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)`. The new process created will be placed in the system
scheduler queue and be run some time later.
See http://www.erlang.org/doc/man/erlang.html#spawn-3 for more info.
"""
defdelegate [spawn: 3], to: :erlang
It behaves exactly the same as the `Kernel.spawn/3` function except
it also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists, a useless
pid is returned. Otherwise works like spawn/3.
"""
defdelegate [spawn: 4], to: :erlang
def spawn(mod, fun, args, opts // []) do
:erlang.spawn_opt(mod, fun, args, opts)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
A link is created between the calling process and the new
process, atomically. Otherwise works like spawn/3.
process, atomically.
"""
defdelegate [spawn_link: 1], to: :erlang
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. A link is created between the calling process and the
new process, atomically. If `node` does not exist, a useless pid is returned
(and due to the link, an exit signal with exit reason :noconnection will be
received). Otherwise works like spawn/3.
"""
defdelegate [spawn_link: 2], to: :erlang
def spawn_link(fun) do
:erlang.spawn_link(fun)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)`. A link is created between the calling process
and the new process, atomically. Otherwise works like spawn/3.
"""
defdelegate [spawn_link: 3], to: :erlang
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. A link is created between the calling
process and the new process, atomically. If `node` does not exist, a useless
pid is returned (and due to the link, an exit signal with exit reason
:noconnection will be received). Otherwise works like spawn/3.
"""
defdelegate [spawn_link: 4], to: :erlang
def spawn_link(mod, fun, args) do
:erlang.spawn_link(mod, fun, args)
end
@doc """
Returns the pid of a new process started by the application of `fun`
and reference for a monitor created to the new process.
Otherwise works like spawn/3.
"""
defdelegate [spawn_monitor: 1], to: :erlang
def spawn_monitor(fun) do
:erlang.spawn_monitor(fun)
end
@doc """
A new process is started by the application of `module.function(args)`
and the process is monitored at the same time. Returns the pid and a
reference for the monitor. Otherwise works like spawn/3.
"""
defdelegate [spawn_monitor: 3], to: :erlang
@doc """
The same as `spawn/1` but accepts extra options as arguments.
Please read http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
documentation of the options.
"""
defdelegate [spawn_opt: 2], to: :erlang
@doc """
The same as `spawn/2` but accepts extra options as arguments.
Please read http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
documentation of the options.
"""
defdelegate [spawn_opt: 3], to: :erlang
@doc """
The same as `spawn/3` but accepts extra options as arguments.
Please read http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
documentation of the options.
"""
defdelegate [spawn_opt: 4], to: :erlang
@doc """
The same as `spawn/4` but accepts extra options as arguments.
Please read http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
documentation of the options.
"""
defdelegate [spawn_opt: 5], to: :erlang
def spawn_monitor(mod, fun, args) do
:erlang.spawn_monitor(mod, fun, args)
end
@doc """
The calling process starts monitoring the item given.
@@ -241,7 +202,9 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
"""
defdelegate [link: 1], to: :erlang
def link(pid) do
:erlang.link(pid)
end
@doc """
Removes the link, if there is one, between the calling process and
@@ -250,7 +213,9 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
"""
defdelegate [unlink: 1], to: :erlang
def unlink(pid) do
:erlang.unlink(pid)
end
@doc """
Associates the name with a pid or a port identifier. name, which must
@@ -259,14 +224,18 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
"""
defdelegate [register: 2], to: :erlang
def register(pid, name) do
:erlang.register(name, pid)
end
@doc """
Removes the registered name, associated with a pid or a port identifier.
See http://www.erlang.org/doc/man/erlang.html#unregister-1 for more info.
"""
defdelegate [unregister: 1], to: :erlang
def unregister(name) do
:erlang.unregister(name)
end
@doc """
Returns the pid or port identifier with the registered name.
@@ -274,7 +243,24 @@ defmodule Process do
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
defdelegate [whereis: 1], to: :erlang
def whereis(name) do
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 """
Returns a list of names which have been registered using register/2.
@@ -304,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.
+77 -66
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,9 +21,8 @@ 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
import Elixir.Builtin, except: [
# We don't allow function definition inside protocols
import Kernel, except: [
defmacro: 1, defmacro: 2, defmacro: 4,
defp: 1, defp: 2, defp: 4,
def: 1, def: 2, def: 4
@@ -40,8 +39,8 @@ defmodule Protocol do
# Define callbacks and meta information
{ conversions, fallback } = Protocol.conversions_for(__MODULE__, @only, @except)
Protocol.impl_for(__MODULE__, conversions)
Protocol.meta(__MODULE__, @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,11 +79,9 @@ defmodule Protocol do
if not loaded or not a protocol.
"""
def assert_protocol(module) do
try do
module.__info__(:data)
rescue
UndefinedFunctionError ->
raise ArgumentError, message: "#{module} is not loaded"
case Code.ensure_compiled(module) do
{ :module, ^module } -> nil
_ -> raise ArgumentError, message: "#{module} is not loaded"
end
try do
@@ -88,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(module, 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 ->
@@ -118,8 +110,9 @@ defmodule Protocol do
try do
target.__impl__
target
rescue
UndefinedFunctionError -> __fallback__
catch
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
__fallback__
end
other ->
other
@@ -137,7 +130,7 @@ defmodule Protocol do
defp __fallback__, do: unquote(fallback)
end
Module.eval_quoted module, contents, [], file: __FILE__, line: __LINE__
Module.eval_quoted env, contents
end
@doc """
@@ -145,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(module, conversions) do
contents = lc kind in 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
@@ -158,7 +153,7 @@ defmodule Protocol do
end]
end
Module.eval_quoted module, contents, [], file: __FILE__, line: __LINE__
Module.eval_quoted env, contents
end
@doc """
@@ -206,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)
@@ -220,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
'__MAIN__' ++ _ -> __MODULE__.Record
_ -> __MODULE__.Tuple
'Elixir-' ++ _ -> __MODULE__.Record
_ -> __fallback__
end
end
end
@@ -261,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 in :lists.seq(1, arity) do
generated = lc i inlist :lists.seq(1, arity) do
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
end
@@ -283,28 +291,31 @@ defmodule Protocol.DSL do
# Append new function to the list
@functions [unquote({name, arity})|@functions]
Elixir.Builtin.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
+50
View File
@@ -0,0 +1,50 @@
defrecord Range, [:first, :last] do
@moduledoc """
Defines a Range.
"""
end
defprotocol Range.Iterator do
@doc """
How to iterate the range, receives the first
and range as arguments. It needs to return a
function that receives an item and returns
a tuple with two elements: the given item
and the next item in the iteration.
"""
def iterator(first, range)
@doc """
Count how many items are in the range.
"""
def count(first, range)
end
defimpl Enum.Iterator, for: Range do
def iterator(Range[first: first] = range) do
iterator = Range.Iterator.iterator(first, range)
{ iterator, iterator.(first) }
end
def count(Range[first: first] = range) do
Range.Iterator.count(first, range)
end
end
defimpl Range.Iterator, for: Number do
def iterator(first, Range[first: f, last: last]) when is_integer(first) and is_integer(last) and last < f do
fn(current) ->
if current < last, do: :stop, else: { current, current - 1 }
end
end
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
fn(current) ->
if current > last, do: :stop, else: { current, current + 1 }
end
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
last - first + 1
end
end
+615
View File
@@ -0,0 +1,615 @@
defmodule Record do
@moduledoc """
Functions to define Elixir records
"""
@doc """
Extract record information from an Erlang file and
return the fields as a list of tuples.
## Examples
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
"""
def extract(name, opts) do
Record.Extractor.retrieve(name, opts)
end
@doc """
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_keyword(keyword) do
raise "expected contents inside brackets to be a Keyword"
end
in_match = caller.in_match?
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.has_key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match or has_underscore_value do
true -> underscore_value
false -> Macro.escape(default)
end
end
{ new_fields, Keyword.delete(each_keyword, field) }
end
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] -> { :{}, caller.line, [atom|match] }
_ ->
keys = lc { key, _ } inlist remaining, do: key
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
end
end
# Dispatch the call to either update or to_list depending on the args given.
@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
# 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
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
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.
#
# Note that fields are *not* keywords. They are in the same
# order as given as parameter and reflects the order of the
# fields in the tuple.
#
# ## Examples
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# FileInfo.__record__(:name) #=> FileInfo
# FileInfo.__record__(:fields) #=> [atime: nil, mtime: nil]
#
defp reflection(values) do
quote do
def __record__(kind, _), do: __record__(kind)
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
end
end
# Define initializers methods. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define three methods:
#
# def new() do
# new([])
# end
#
# def new([]) do
# { FileInfo, nil, nil }
# end
#
# def new(opts) do
# { FileInfo, Keyword.get(opts, :atime), Keyword.get(opts, :mtime) }
# end
#
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
# the given key from the ordered dict, falling back to the
# default value if one does not exist.
selective = lc { k, v } inlist values do
quote do: Keyword.get(opts, unquote(k), unquote(v))
end
quote 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: :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
# Define converters method(s). For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define one method, to_keywords, which will return a Keyword
#
# [atime: nil, mtime: nil]
#
defp conversions(values) do
sorted = lc { k, _ } inlist values do
index = find_index(values, k, 0)
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
end
quote do
def to_keywords(record) do
unquote(:orddict.from_list(sorted))
end
end
end
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 readers. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define four methods:
#
# def :atime.(record) do
# elem(record, 1)
# end
#
# def :mtime.(record) do
# elem(record, 2)
# end
#
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 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
@moduledoc false
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def retrieve(name, from: string) do
file = to_char_list(string)
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
end
retrieve_record(name, realfile)
end
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def retrieve(name, from_lib: file) do
[app|path] = :filename.split(to_char_list(file))
case :code.lib_dir(to_char_list(app)) do
{ :error, _ } ->
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
end
end
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
records = retrieve_from_file(file)
if record = List.keyfind(records, name, 0) do
parse_record(record)
else
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_from_file(file) do
lc { :attribute, _, :record, record } inlist read_file(file), do: record
end
# Read a file and return its abstract syntax form that also
# includes record and other preprocessor modules. This is done
# by using Erlang's epp_dodger.
defp read_file(file) do
case :epp_dodger.quick_parse_file(file) do
{ :ok, form } ->
form
other ->
raise "Error parsing file #{to_binary(file)}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of second order tuples where the first element
# is the field and the second is its default value.
defp parse_record({ _name, fields }) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = :erl_eval.expr(cons, [])
list
end
defp parse_field({ :typed_record_field, record_field, _type }) do
parse_field(record_field)
end
defp parse_field({ :record_field, _, key }) do
{ :tuple, 0, [key, {:atom, 0, :nil}] }
end
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
end
defmodule Record.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) ]
end
# Skip the __exception__ for defexception.
def default_for(:__exception__, _default, _i) do
nil
end
# Define the default functions for each field.
def default_for(key, _default, i) do
bin_update = "update_" <> atom_to_binary(key)
update = binary_to_atom(bin_update)
quote do
def unquote(update).(function, record) do
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, function.(current))
end
end
end
# Define extensions based on the default type.
def extension_for(key, default, i) when is_list(default) do
bin_key = atom_to_binary(key)
prepend = :"prepend_#{bin_key}"
merge = :"merge_#{bin_key}"
quote do
def unquote(prepend).(value, record) do
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 + 1), record)
:erlang.setelement(unquote(i + 1), record, Keyword.merge(current, value))
end
end
end
def extension_for(key, default, i) when is_number(default) do
bin_key = atom_to_binary(key)
increment = :"increment_#{bin_key}"
quote do
def unquote(increment).(value // 1, record) do
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, current + value)
end
end
end
def extension_for(key, default, i) when is_boolean(default) do
bin_key = atom_to_binary(key)
toggle = :"toggle_#{bin_key}"
quote do
def unquote(toggle).(record) do
current = :erlang.element(unquote(i + 1), record)
:erlang.setelement(unquote(i + 1), record, not current)
end
end
end
def extension_for(_, _, _), do: nil
end
+343
View File
@@ -0,0 +1,343 @@
defmodule Regex do
@moduledoc %B"""
Regular expressions for Elixir built on top of the re module
in the Erlang Standard Library. More information can be found
on re documentation: http://www.erlang.org/doc/man/re.html
Regular expressions in Elixir can be created using Regex.compile!
or using the special form with `%r`:
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
# A regular expression with case insensitive options and handle unicode chars
%r/foo/iu
The re module provides several options, the one available in Elixir, followed by
their shortcut in parenthesis, are:
* unicode (u) - used when you want to match against specific unicode characters
* caseless (i) - add case insensitivity
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
`(*CRLF)` or `(*ANY)` according to re documentation
* multiline (m) - causes `^` and `$` to mark the beginning and end of each line.
You need to use `\A` and `\z` to match the end or beginning of the string
* extended (x) - whitespace characters are ignored except when escaped and
allow `#` to delimit comments
* 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:
* anchored - not available, use `^` or `\A` instead
* dollar_endonly - not available, use `\z` instead
* no_auto_capture - not available, use `?:` instead
* newline - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
Most of the functions in this module accept either a binary or a char list
as subject. The result is based on the argument (a binary will return
a binary, a char list will return a char list).
"""
defexception CompileError, message: "regex could not be compiled"
@doc """
Compiles the regular expression according to the given options.
It returns `{ :ok, regex }` in case of success,
`{ :error, reason }` otherwise.
"""
def compile(source, options // "") do
source = to_binary(source)
options = to_binary(options)
opts = translate_options(options)
re_opts = opts -- [:groups]
groups = if opts != re_opts, do: parse_groups(source)
case :re.compile(source, re_opts) do
{ :ok, compiled } ->
{ :ok, { Regex, compiled, source, options, groups } }
error ->
error
end
end
@doc """
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
def compile!(source, options // "") do
case compile(source, options) do
{ :ok, regex } -> regex
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
end
end
@doc """
Runs the regular expression against the given string
and returns the index (zero indexes) where the first
match occurs, nil otherwise.
## Examples
Regex.index %r/c(d)/, "abcd" #=> 3
Regex.index %r/e/, "abcd" #=> nil
"""
def index({ Regex, compiled, _, _, _ }, string) do
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
end
end
@doc """
Returns a boolean if there was a match or not.
## Examples
Regex.match? %r/foo/, "foo" #=> true
Regex.match? %r/foo/, "bar" #=> false
"""
def match?({ Regex, compiled, _, _, _ }, string) do
:re.run(string, compiled, [{ :capture, :none }]) == :match
end
@doc """
Runs the regular expression against the given string.
It returns a list with all matches or nil if no match ocurred.
## Examples
Regex.run %r/c(d)/, "abcd" #=> ["cd", "d"]
Regex.run %r/e/, "abcd" #=> nil
"""
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
(zero indexed) the match happened and the second is the length
of the match.
## Examples
Regex.indexes %r/c(d)/, "abcd" #=> [{2,2},{3,1}]
Regex.indexes %r/e/, "abcd" #=> nil
"""
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
compiled
end
@doc """
Returns the regex source as binary.
## Examples
Regex.source %r(foo) #=> "foo"
"""
def source({ Regex, _, source, _, _ }) do
source
end
@doc """
Returns the regex options as a list.
## Examples
Regex.opts %r(foo)m #=> 'm'
"""
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
the list is a binary, it means there were no captures. If the item is another
list, each element in this secondary list is a capture.
## Examples
Regex.scan %r/c(d|e)/, "abcd abce" #=> [["d"], ["e"]]
Regex.scan %r/c(?:d|e)/, "abcd abce" #=> ["cd", "ce"]
Regex.scan %r/e/, "abcd" #=> []
"""
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 } -> 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.
"""
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 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
Regex.replace(%r/d/, "abc", "d") #=> "abc"
Regex.replace(%r/b/, "abc", "d") #=> "adc"
Regex.replace(%r/b/, "abc", "[&]") #=> "a[b]c"
Regex.replace(%r/b/, "abc", "[\\&]") #=> "a[&]c"
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
"""
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 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 Macro.unescape_binary.
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(_), do: false
# Private Helpers
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(<<?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
+54 -70
View File
@@ -1,65 +1,3 @@
# Since Elixir does not (yet) support local macros,
# we need to wrap the step that extracts git information
# in this module.
defmodule System.GitCompiler do
@moduledoc false
defmacro generate do
quote do
@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.
"""
def build_info do
{ System.version,
unquote(get_head_sha),
unquote(get_date) }
end
end
end
# Tries to run `git rev-parse HEAD`. In case of success returns the
# commit sha, otherwise returns an empty string.
defp get_head_sha do
# The following failures are possible:
#
# 1) there is no `git` command
# 2) pwd is not a git repository
#
command = 'git rev-parse HEAD'
opts = [:stream, :exit_status, :use_stdio,
:stderr_to_stdout, :in, :eof]
port = :erlang.open_port {:spawn, command}, opts
output = read_port port
case output do
{ 0, data } ->
Regex.replace_all %r/\n/, to_binary(data), ""
_ ->
""
end
end
defp read_port(port, data // []) do
receive do
{^port, {:data, new_data}} ->
read_port port, [new_data|data]
{^port, :eof} ->
:erlang.port_close port
receive do
{^port, {:exit_status, exit_status}} ->
{exit_status, List.reverse data}
end
end
end
defp get_date do
list_to_binary :httpd_util.rfc1123_date
end
end
defmodule System do
@moduledoc """
The System module provides access to some variables used or
@@ -67,19 +5,39 @@ defmodule System do
with the VM or the host system.
"""
require System.GitCompiler
System.GitCompiler.generate
# 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 describe --always --tags')
Regex.replace %r/\n/, to_binary(data), ""
else
""
end
end
# Get the date at compilation time.
defmacrop get_date do
list_to_binary :httpd_util.rfc1123_date
end
@doc """
Returns Elixir's version as binary.
"""
def version, do: "0.5.0"
def version, do: "0.7.0"
@doc """
Returns a keywords list with version, git tag info and date.
"""
def build_info do
[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 """
@@ -98,11 +56,37 @@ defmodule System do
Executes `command` in a command shell of the target OS,
captures the standard output of the command and returns
the result as a binary.
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
def cmd(command) when is_list(command) do
:os.cmd(command)
end
def cmd(command) do
list_to_binary :os.cmd(to_char_list(command))
end
@doc """
This functions looks up an executable program given
its name using the environment variable PATH on Unix
and Windows.
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
def find_executable(command) when is_list(command) do
:os.find_executable(command) || nil
end
def find_executable(command) do
case :os.find_executable(to_char_list(command)) do
false -> nil
other -> list_to_binary(other)
end
end
@doc """
Returns a list of all environment variables. Each environment variable is
given as a single string of the format "VarName=Value", where VarName is the
@@ -151,19 +135,19 @@ defmodule System do
Get the stacktrace.
"""
def stacktrace do
filter_stacktrace Erlang.erlang.get_stacktrace
filter_stacktrace :erlang.get_stacktrace
end
## Helpers
# Filter stacktrace by removing internal BOOTSTRAP calls.
defp filter_stacktrace([{ Elixir.Builtin, :raise, _, _ }|t]), do: filter_stacktrace(t)
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: filter_stacktrace(t)
defp filter_stacktrace([{ _mod, :BOOTSTRAP, _, info }|t]),
do: filter_stacktrace([{ Elixir.Builtin, :defmodule, 2, info }|t])
do: filter_stacktrace([{ Kernel, :defmodule, 2, info }|t])
defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
defp filter_stacktrace([]), do: []
defp server_call(args) do
Erlang.gen_server.call(:elixir_code_server, args)
:gen_server.call(:elixir_code_server, args)
end
end
+2
View File
@@ -0,0 +1,2 @@
defmodule Tuple do
end
+42 -32
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>> in 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,21 +94,21 @@ 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
def decode(<<>>), do: <<>>
defp hex2dec(n) when n >= ?A and n <= ?F, do: n - ?A + 10
defp hex2dec(n) when n >= ?0 and n <= ?9, do: n - ?0
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
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)
@@ -170,7 +180,7 @@ defmodule URI do
# Regex.run returns empty strings sometimes. We want
# to replace those with nil for consistency.
defp nillify(l) do
lc s in l do
lc s inlist l do
if size(s) > 0 do
s
else
+18
View File
@@ -0,0 +1,18 @@
defmodule URI.Parser do
@moduledoc """
Defines the behavior for each URI.Parser.
Check URI.HTTP for a possible implementation.
"""
use Behaviour
@doc """
Responsible for parsing extra URL information.
"""
defcallback parse(uri_info)
@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
+27
View File
@@ -0,0 +1,27 @@
{erl_first_files, ["elixir_transform"]}.
{erl_opts, [
warn_unused_vars,
warn_export_all,
warn_shadow_vars,
warn_unused_import,
warn_unused_function,
warn_bif_clash,
warn_unused_record,
warn_deprecated_function,
warn_obsolete_guard,
strict_validation,
warn_exported_vars,
%% warn_export_vars,
%% warn_missing_spec,
%% warn_untyped_record,
%% warnings_as_errors,
no_debug_info
]}.
{yrl_opts, [
{report, true},
{verbose, false}
]}.
{require_otp_vsn,"R15"}.
-496
View File
@@ -1,496 +0,0 @@
defmodule Elixir.SpecialForms do
@moduledoc """
In this module we define Elixir special forms. Those are called
special forms because they cannot be overridden by the developer
and sometimes have lexical scope (like refer, require, import, etc).
"""
@doc """
Defines a new tuple.
## Examples
:{}.(1,2,3)
{ 1, 2, 3 }
"""
defmacro :{}.(args)
@doc """
Defines a new list.
## Examples
:[].(1,2,3)
[ 1, 2, 3 ]
"""
defmacro :[].(args)
@doc """
Defines a new bitstring.
## Examples
:<<>>.(1,2,3)
<< 1, 2, 3 >>
"""
defmacro :<<>>.(args)
@doc """
`refer` is used to setup aliases between modules.
## Examples
`refer` can be used to setup an alias for any module:
defmodule Math do
refer MyKeyword, as: Keyword
end
In the example above, we have set up `MyOrdict` to be referenced
as `Keyword`. So now, any reference to `Keyword` will be
automatically replaced by `MyKeyword`.
In case one wants to access the original `Keyword`, it can be done
by accessing __MAIN__:
Keyword.values #=> uses MyKeyword.values
__MAIN__.Keyword.values #=> uses Keyword.values
Notice that calling `refer` without the `as:` option automatically
sets an alias based on the last part of the module. For example:
refer Foo.Bar.Baz
Is the same as:
refer Foo.Bar.Baz, as: Baz
## Lexical scope
`import`, `require` and `refer` are called directives and all
have lexical scope. This means you can set up aliases inside
specific functions and it won't affect the overall scope.
"""
defmacro refer(module, opts)
@doc """
`require` is used to require the presence of external
modules so macros can be invoked.
## Examples
Notice that usually modules should not be required before usage,
the only exception is if you want to use the macros from a module.
In such cases, you need to explicitly require them.
Let's suppose you created your own `if` implementation in the module
`MyMacros`. If you want to invoke it, you need to first explicitly
require the `MyMacros`:
defmodule Math do
require MyMacros
MyMacros.if do_something, it_works
end
An attempt to call a macro that was not loaded will raise an error.
## Refer shortcut
`require` also accepts `as:` as an option so it automatically sets
up an alias. Please check `refer` for more information.
"""
defmacro require(module, opts)
@doc """
`import` allows one to easily access functions or macros from
others modules without using the qualified name.
## 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:
defmodule Math do
import Keyword, only: [values: 1]
def some_function do
# call values(orddict)
end
end
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.
In case you want to import only functions or macros, you can pass a
first argument selecting the scope:
import :macros, MyMacros
And you can then use `only` or `except` to filter the macros being
included.
## Lexical scope
It is important to notice that `import` is lexical. This means you
can import specific macros inside specific functions:
defmodule Math do
def some_function do
# 1) Disable `if/2` from Elixir.Builtin
import Elixir.Builtin, except: [if: 2]
# 2) Require the new `if` macro from MyMacros
import MyMacros
# 3) Use the new macro
if do_something, it_works
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.
## Refer/Require shortcut
All imported modules are also required by default. `import`
also accepts `as:` as an option so it automatically sets up
an alias. Please check `refer` for more information.
"""
defmacro import(module, opts)
@doc """
Returns the current module name as an atom or nil otherwise.
"""
defmacro __MODULE__
@doc """
Returns the current file name as a binary.
"""
defmacro __FILE__
@doc """
Returns the current line number as an integer.
"""
defmacro __LINE__
@doc """
Returns the current function as a tuple,
where the first element is the name as an atom
and the second is the arity as an integer.
"""
defmacro __FUNCTION__
@doc """
Allows you to get the representation of any expression.
## Examples
quote do: sum(1, 2, 3)
#=> { :sum, 0, [1, 2, 3] }
## Homoiconicity
Elixir is an homoiconic language. Any Elixir program can be
represented using its own data structures. The building block
of Elixir homoiconicity is a tuple with three elements, for example:
{ :sum, 1, [1, 2, 3] }
The tuple above represents a function call to sum passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
another tuple in the same representation;
* The second element of the tuple is always an integer
representing the line number;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, meaning
that it may be a variable.
## Macro literals
Besides the tuple described above, Elixir has a few literals that
when quoted return themselves. They are:
:sum #=> Atoms
1 #=> Integers
2.0 #=> Floats
[1,2] #=> Lists
"binaries" #=> Binaries
{key, value} #=> Tuple with two elements
## Hygiene
Elixir macros are hygienic regarding to variables. This means
a variable defined in a macro cannot affect the scope where
the macro is included. Consider the following example:
defmodule Hygiene do
defmacro no_interference do
quote do: a = 1
end
end
require Hygiene
a = 10
Hygiene.no_interference
a #=> 10
In the example above, `a` returns 10 even if the macro
is apparently setting it to 1 because the variables defined
in the macro does not affect the context the macro is
executed. If you want to set or get a variable, you can do
it with the help of the `var!` macro:
defmodule NoHygiene do
defmacro interference do
quote do: var!(a) = 1
end
end
require NoHygiene
a = 10
NoHygiene.interference
a #=> 11
Notice that references are not hygienic in Elixir unless
you explicitly access it via __MAIN__ to the reference name.
quote do
__MAIN__.Foo #=> Access the root Foo
Foo #=> Access the Foo reference in the current
module (if any is set), then fallback to root
end
## Options
`quote` also accepts some options as arguments. For example,
hygiene can be turned off via `hygiene: false` which is useful
when one is generating a code that should be inserted into
some function.
"""
defmacro quote(opts, do: contents)
@doc """
Unquotes the given expression from inside a macro.
## Examples
Imagine the situation you have a variable `name` and
you want to inject it inside some quote. The first attempt
would be:
value = 13
quote do: sum(1, value, 3)
Which would then return:
{ :sum, 0, [1, { :value, 0, quoted }, 3] }
Which is not the expected result. For this, we use unquote:
value = 13
quote do: sum(1, unquote(value), 3)
#=> { :sum, 0, [1, 13, 3] }
"""
defmacro unquote(expr)
@doc """
Unquotes the given list expanding its arguments. Similar
to unquote.
## Examples
values = [2,3,4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, 0, [1, 2, 3, 4, 5] }
"""
defmacro unquote_splicing(expr)
@doc """
Returns an anonymous function based on the given arguments.
## Examples
sum = fn(x, y) -> x + y end
sum.(1, 2) #=> 3
Notice that a function needs to be invoked using the dot between
the function and the arguments.
A function could also be defined using the `end` syntax, although
it is recommend to use it only with the stab operator in order to
avoid ambiguity. For example, consider this case:
Enum.map [1,2,3], fn x ->
x * 2
end
The example works fine because `->` binds to the closest function call,
which is `fn`, but if we replace it by `do/end`, it will fail:
Enum.map [1,2,3], fn(x) do
x * 2
end
The reason it fails is because do/end always bind to the farthest
function call.
## Function with multiple clauses
One may define a function which expects different clauses as long
as all clauses expects the same number of arguments:
fun = fn do
x, y when y < 0 ->
x - y
x, y ->
x + y
end
fun.(10, -10) #=> 20
fun.(10, 10) #=> 20
"""
defmacro fn(args)
@doc """
Handle annonymous recursive loops.
## Examples
list = [1,2,3]
loop list, [] do
[h|t], acc ->
recur t, [h*2|acc]
[], acc ->
acc
end
#=> [6,4,2]
Notice that all match clauses expects the same ammount
of arguments. Guards can also be given.
Recursion happens by calling recur with the same number
of arguments of each match clause. `recur` does not guarantee
that it will be tail recursive.
"""
defmacro loop(args)
@doc """
A function that forces the current loop to recur. See `loop/1`
for more information.
"""
defmacro recur(args)
@doc """
List comprehensions allow you to quickly build a list from another list:
lc n in [1,2,3,4], do: n * 2
#=> [2,4,6,8]
A comprehension accepts many generators and also filters. Filters must be given after the when clause:
# A comprehension with a generator and a filter
lc n in [1,2,3,4,5,6] when rem(n, 2) == 0, do: n
#=> [2,4,6]
# A comprehension with two generators
lc x in [1,2], y in [2,3], do: x*y
#=> [2,3,4,6]
Elixir provides generators for both lists and bitstrings:
# A list generator:
lc n in [1,2,3,4], do: n * 2
#=> [2,4,6,8]
# A bit string generator:
lc <<n>> in <<1,2,3,4>>, do: n * 2
#=> [2,4,6,8]
Bit string generators are quite useful when you need to organize bit string streams:
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
iex> lc <<r:8,g:8,b:8>> in pixels, do: {r,g,b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
Elixir does its best to hide the differences between list and bit string generators.
However, there is a special case due to Erlang limitation where we need to explicitly
tell Erlang that a list is being given as argument:
# This will fail because when Elixir sees that the left side
# of the in expression is a bit string, it expects the right side
# to be a bit string as well:
lc <<n>> in [<<1>>,<<2>>,<<3>>], do: n*2
#=> ** (ErlangError) erlang error {:bad_generator,[<<1>>,<<2>>,<<3>>]}
# You need to be explicit and use inlist:
lc inlist(<<n>>, [<<1>>,<<2>>,<<3>>]), do: n*2
#=> [2,4,6]
# For consistency, inbin is also available:
lc inbin(<<n>>, <<1,2,3>>), do: n*2
#=> [2,4,6]
Notice that although comprehensions uses `when` to specify filters, filters are not
guards and therefore accept any expression (they are not limited as guards).
"""
defmacro lc(args)
@doc """
Defines a bit comprehension. It follows the same syntax as
a list comprehension but expects each element returned to
be a bitstring. For example, here is how to remove all
spaces from a string:
bc <<c>> in " hello world " when c != ?\s, do: <<c>>
"helloworld"
"""
defmacro bc(args)
@doc """
Keeps one of the given expressions depending in the context
of evaluation is a guard or not. This is useful when creating
macro that should work both inside and outside guards but
still hold some characteristics.
## Example
A good example is the `is_exception/1` macro defined in Elixir:
defmacro is_exception(thing) do
quote do
quote do
is_tuple(unquote(thing)) and elem(unquote(thing), 2) == :__exception__
else
result = unquote(thing)
is_tuple(result) and elem(result, 2) == :__exception__
end
end
end
Notice that if inside a guard, we unquote the same element twice.
This will cause the same element to be evaluted twice, but this is
fine for guards since we cannot assign variables in guards and
we cannot call expressions inside guards. However, when outside
of a guard, evaluating the arguments twice can be harmful and
unexpected, for this reason, we save the result in a variable.
In the example above, `in_guard` is allowing us to customize
the same macro to work inside and outside guards.
"""
defmacro in_guard(do: do_block, else: else_block)
end
+44 -28
View File
@@ -1,10 +1,12 @@
-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
@@ -12,7 +14,8 @@
start(_Type, _Args) ->
%% Set the shell to unicode so printing inside files work
io:setopts([{encoding,unicode}]),
io:setopts(standard_io, [{encoding,unicode}]),
io:setopts(standard_error, [{encoding,unicode}]),
elixir_sup:start_link([]).
stop(_S) ->
@@ -21,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
@@ -36,36 +45,37 @@ start_app() ->
start_cli() ->
start_app(),
'__MAIN__.Elixir.CLI':process_argv(init:get_plain_arguments()).
'Elixir.Kernel.CLI':process_argv(init:get_plain_arguments()).
%% EVAL HOOKS
scope_for_eval(Opts) ->
Filename = case orddict:find(file, Opts) of
{ ok, F } -> to_char_list(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 -> []
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, L } -> L;
false -> nil
end,
#elixir_scope{filename=Filename,local=Local}.
#elixir_scope{file=File,local=Local}.
%% String evaluation
eval(String, Binding) -> eval(String, Binding, []).
eval(String, Binding, Opts) ->
case orddict:find(line, Opts) of
{ ok, Line } -> [];
error -> Line = 1
case lists:keyfind(line, 1, Opts) of
false -> Line = 1;
{ line, Line } -> []
end,
eval(String, Binding, Line, scope_for_eval(Opts)).
eval(String, Binding, Line, #elixir_scope{filename=Filename} = S) ->
Forms = elixir_translator:forms(String, Line, Filename),
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, []),
eval_forms(Forms, Binding, S).
%% Quoted evaluation
@@ -73,9 +83,9 @@ eval(String, Binding, Line, #elixir_scope{filename=Filename} = S) ->
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)).
@@ -86,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 };
@@ -97,19 +113,19 @@ eval_forms(Tree, Binding, RawScope) ->
%% INTERNAL HELPERS
to_char_list(Bin) when is_binary(Bin) -> binary_to_list(Bin);
to_char_list(List) when is_list(List) -> List.
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).
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
case atom_to_list(Var) of
"_EX" ++ _ -> final_binding(T, Acc, Binding, Vars);
"_@" ++ _ -> 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;
@@ -118,7 +134,7 @@ final_binding([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
normalize_binding(Binding) ->
Keyword = orddict:from_list(Binding),
case orddict:find('_EXMODULE', Keyword) of
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_EXMODULE', nil, Keyword)
end.
_ -> orddict:store('_@MODULE', nil, Keyword)
end.
+84
View File
@@ -0,0 +1,84 @@
-module(elixir_aliases).
-export([first/1, last/1, concat/1, safe_concat/1, lookup/2,
format_error/1, ensure_loaded/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Ensure a module is loaded before its usage.
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
ok;
ensure_loaded(Line, Ref, S) ->
try
Ref:module_info(compile)
catch
error:undef ->
Kind = case lists:member(Ref, S#elixir_scope.scheduled) of
true -> scheduled_module;
false -> unloaded_module
end,
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
end.
%% Receives an atom and returns the first alias.
first(Atom) ->
First = first(atom_to_list(Atom), []),
list_to_atom("Elixir-" ++ First).
first("Elixir-" ++ Rest, []) -> first(Rest, []);
first([$-|_], Acc) -> lists:reverse(Acc);
first([H|T], Acc) -> first(T, [H|Acc]);
first([], Acc) -> lists:reverse(Acc).
%% Receives an atom and returns the last alias.
last(Atom) ->
Last = last(lists:reverse(atom_to_list(Atom)), []),
list_to_atom("Elixir-" ++ Last).
last([$-|_], Acc) -> Acc;
last([H|T], Acc) -> last(T, [H|Acc]);
last([], Acc) -> Acc.
%% Receives a list of atoms representing modules
%% and concatenate them.
concat(Args) -> list_to_atom(raw_concat(Args)).
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
raw_concat(['Elixir'|Args]) -> do_concat(Args);
raw_concat(Args) -> do_concat(Args).
do_concat(Args) ->
Aliases = [to_partial(Arg) || Arg <- Args, Arg /= nil],
"Elixir" ++ lists:concat(Aliases).
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
to_partial(Arg) when is_atom(Arg) -> to_partial(atom_to_list(Arg));
to_partial("Elixir-" ++ Arg) -> dot_to_dash([$-|Arg]);
to_partial([$-|_] = Arg) -> dot_to_dash(Arg);
to_partial(Arg) when is_list(Arg) -> [$-|dot_to_dash(Arg)].
dot_to_dash(List) ->
[case X of
$. -> $-;
_ -> X
end || X <- List].
%% Lookup an alias in the current scope
lookup(Else, Dict) ->
case orddict:find(Else, Dict) of
{ ok, Value } when Value /= Else -> lookup(Value, Dict);
_ -> Else
end.
%% Errors
format_error({unloaded_module, Module}) ->
io_lib:format("module ~s is not loaded and could not be found", [elixir_errors:inspect(Module)]);
format_error({scheduled_module, Module}) ->
io_lib:format("module ~s is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
[elixir_errors:inspect(Module)]).
@@ -4,7 +4,7 @@
-export([
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
-import(elixir_variables, [umergec/2]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
%% Get pairs from a clause.
@@ -13,22 +13,22 @@ 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, _ } ->
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "expected pairs with -> for key ~s", [Key]);
{ Key, _ } ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
_ ->
[]
end.
% Function for translating assigns.
assigns(Fun, Args, #elixir_scope{assign=false} = S) ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{assign=true, temp_vars=dict:new()}),
{ Result, NewS#elixir_scope{assign=false} };
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= assign ->
{ 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).
@@ -40,12 +40,17 @@ assigns_block(Line, Fun, BareArgs, Exprs, S) ->
assigns_block(Line, Fun, Args, Exprs, Guards, S).
assigns_block(Line, Fun, Args, Exprs, Guards, S) ->
{ TArgs, SA } = elixir_clauses:assigns(Fun, Args, S),
{ TExprs, SE } = elixir_translator:translate(Exprs, SA),
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
FArgs = listify(TArgs),
SG = SA#elixir_scope{guard=true},
FGuards = [translate_guard(Line, Guard, SG) || Guard <- Guards],
SG = SA#elixir_scope{context=guard, extra_guards=nil},
Extra = SA#elixir_scope.extra_guards,
FGuards = case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end,
% Uncompact expressions from the block.
case TExprs of
@@ -57,19 +62,10 @@ assigns_block(Line, Fun, Args, Exprs, Guards, S) ->
% Translate/Extract guards from the given expression.
translate_guard(Line, Guard, S) ->
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))].
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))|Extra].
extract_guards({ 'when', _, [Left, Right] }) ->
Clauses = extract_or_clauses(Right, []),
case Left of
{ 'in', Line, [Var, _] } ->
{ Var, [{ 'and', Line, [Left, Clause] } || Clause <- Clauses] };
_ ->
{ Left, Clauses }
end;
extract_guards({ 'in', _, [Var, _] } = Expr) -> { Var, [Expr] };
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right, []) };
extract_guards(Else) -> { Else, [] }.
extract_or_clauses({ 'when', _, [Left, Right] }, Acc) -> extract_or_clauses(Right, [Left|Acc]);
@@ -92,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] ->
@@ -110,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 };
@@ -123,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_variables:build_erl(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) ->
@@ -172,10 +168,10 @@ each_clause(Line, { 'after', [Condition], Expr }, S) ->
{ { clause, Line, [TCondition], [], TBody }, SB };
each_clause(Line, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "too many arguments given for ~s", [Key]);
elixir_errors:syntax_error(Line, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
each_clause(Line, { Key, _, _ }, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "invalid key ~s", [Key]).
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid key ~s", [Key]).
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
@@ -203,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_variables:build_erl(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.
+88
View File
@@ -0,0 +1,88 @@
-module(elixir_code_server).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-behavior(gen_server).
-record(elixir_code_server, {
argv=[],
loaded=[],
at_exit=[],
compiler_options=[{docs,true}]
}).
start_link() ->
{ ok, _ } = gen_server:start_link({local, elixir_code_server}, ?MODULE, [], []).
init(_args) ->
process_flag(trap_exit, true),
{ ok, #elixir_code_server{} }.
handle_call({ acquire, Path }, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
{ ok, true } ->
{ reply, loaded, Config };
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
Queued = orddict:store(Path, { Ref, [From|List] }, Current),
{ reply, { queued, Ref }, Config#elixir_code_server{loaded=Queued} };
error ->
Queued = orddict:store(Path, { make_ref(), [] }, Current),
{ reply, proceed, Config#elixir_code_server{loaded=Queued} }
end;
handle_call({ at_exit, AtExit }, _From, Config) ->
{ reply, ok, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
handle_call({ argv, Argv }, _From, Config) ->
{ reply, ok, Config#elixir_code_server{argv=Argv} };
handle_call({ compiler_options, Options }, _From, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ reply, ok, Config#elixir_code_server{compiler_options=Final} };
handle_call(loaded, _From, Config) ->
{ reply, [F || { F, true } <- Config#elixir_code_server.loaded], Config };
handle_call(at_exit, _From, Config) ->
{ reply, Config#elixir_code_server.at_exit, Config };
handle_call(argv, _From, Config) ->
{ reply, Config#elixir_code_server.argv, Config };
handle_call(compiler_options, _From, Config) ->
{ reply, Config#elixir_code_server.compiler_options, Config };
handle_call(_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 }.
handle_info(_Request, Config) ->
{ noreply, Config }.
terminate(Reason, Config) ->
io:format("[FATAL] ~p crashed:\n~p~n", [?MODULE, Reason]),
io:format("[FATAL] ~p snapshot:\n~p~n", [?MODULE, Config]),
ok.
code_change(_Old, Config, _Extra) ->
{ ok, Config }.
+235
View File
@@ -0,0 +1,235 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/5]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Public API
%% Get compilation options.
get_opt(Key) -> get_opt(Key, get_opts()).
get_opt(Key, Dict) ->
case lists:keyfind(Key, 1, Dict) of
false -> false;
{ Key, Value } -> Value
end.
get_opts() ->
gen_server:call(elixir_code_server, compiler_options).
%% Compiles the given string.
string(Contents, File) when is_list(Contents), is_binary(File) ->
Previous = get(elixir_compiled),
try
put(elixir_compiled, []),
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
eval_forms(Forms, 1, File, [], #elixir_scope{file=File}),
lists:reverse(get(elixir_compiled))
after
put(elixir_compiled, Previous)
end.
%% Compile a file, return a tuple of module names and binaries.
file(Relative) when is_binary(Relative) ->
File = filename:absname(Relative),
{ ok, Bin } = file:read_file(File),
string(unicode:characters_to_list(Bin), File).
%% Compiles a file to the given path (directory).
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
Lists = file(File),
[binary_to_path(X, Path) || X <- Lists],
Lists.
%% Evaluates the contents/forms by compiling them to an Erlang module.
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=nil} = S) ->
eval_forms(Forms, Line, Module, nil, Vars, S);
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=Value} = S) ->
eval_forms(Forms, Line, Module, Value, Vars, S).
eval_forms(Forms, Line, RawModule, Value, Vars, S) ->
case (Value == nil) andalso allows_fast_compilation(Forms) of
true -> eval_compilation(Forms, Vars, S);
false -> code_loading_compilation(Forms, Line, RawModule, Value, Vars, S)
end.
eval_compilation(Forms, Vars, S) ->
Binding = [{ Var, Value } || { _, Var, Value } <- Vars],
{ Result, _Binding, FS } = elixir:eval_forms(Forms, [{'_@MODULE',nil}|Binding], S),
{ Result, FS }.
code_loading_compilation(Forms, Line, RawModule, Value, Vars, S) ->
Module = escape_module(RawModule),
{ Exprs, FS } = elixir_translator:translate(Forms, S),
ModuleForm = module_form(Exprs, Line, S#elixir_scope.file, Module, Vars),
Args = [X || { _, _, X } <- Vars],
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(Mod, _) ->
Res = Mod:'BOOTSTRAP'(Value, Args),
code:purge(Module),
code:delete(Module),
Res
end), FS }.
%% Internal API
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, S, Callback) ->
Options = case get_opt(debug_info) of
true -> [debug_info];
_ -> []
end,
module(Forms, S#elixir_scope.file, Options, false, Callback).
module(Forms, File, Options, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Listname = binary_to_list(File),
case compile:forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} ->
format_warnings(Bootstrap, File, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, File, Warnings),
format_errors(File, Errors)
end.
%% Compile core files for bootstrap.
%% Invoked from the Makefile.
core() ->
elixir:start_app(),
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()],
AllLists = [filelib:wildcard(Wildcard) || Wildcard <- core_list()],
Files = lists:append(AllLists) -- core_main(),
[core_file(File) || File <- 'Elixir.List':uniq(Files)].
%% HELPERS
no_auto_import() ->
{ attribute, 0, compile, {
no_auto_import, erlang:module_info(exports) } }.
module_form(Exprs, Line, File, Module, Vars) when
is_binary(File), is_list(Exprs), is_integer(Line), is_atom(Module) ->
Cons = lists:foldr(fun({ _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
Args = [{ var, Line, '_@MODULE'}, Cons],
[
{ attribute, Line, file, { binary_to_list(File), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ 'BOOTSTRAP', 2 }] },
{ function, Line, 'BOOTSTRAP', length(Args), [
{ clause, Line, Args, [], Exprs }
] }
].
%% Fast compilation is available?
allows_fast_compilation([{defmodule,_,_}|T]) -> allows_fast_compilation(T);
allows_fast_compilation([]) -> true;
allows_fast_compilation(_) -> false.
%% Escape the module name, removing slashes, dots,
%% so it can be loaded by Erlang.
escape_module(Module) when is_atom(Module) ->
escape_module(atom_to_list(Module));
escape_module(Module) when is_binary(Module) ->
escape_module(binary_to_list(Module));
escape_module(Module) when is_list(Module) ->
list_to_atom(escape_each(Module)).
escape_each([H|T]) when H >= $A, H =< $Z; H >= $a, H =< $z; H >= $0, H =< $9; H == $- ->
[H|escape_each(T)];
escape_each([_|T]) ->
[$_|escape_each(T)];
escape_each([]) -> [].
%% Receives a module Binary and outputs it in the given path.
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
ok = file:write_file(Path, Binary),
Path.
%% CORE FILES COMPILATION
core_file(File) ->
try
Lists = file(list_to_binary(File)),
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~s~n", [File])
catch
Kind:Reason ->
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
exit(1)
end.
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"
].
core_main() ->
[
"lib/elixir/lib/kernel.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/behaviour.ex"
].
%% ERROR HANDLING
format_errors(_File, []) ->
exit({nocompile, "compilation failed but no error was raised"});
format_errors(File, Errors) ->
lists:foreach(fun ({_, Each}) ->
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
end, Errors).
format_warnings(Bootstrap, File, Warnings) ->
lists:foreach(fun ({_, Each}) ->
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
end, Warnings).
@@ -10,6 +10,7 @@
unwrap_stored_definitions/1,
format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Table management functions. Called internally.
@@ -44,12 +45,12 @@ reset_last(Module) ->
%% If we just analyzed the compiled structure (i.e. the function availables
%% before evaluating the function body), we would see both definitions.
wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
MetaS = elixir_variables:serialize_scope(S),
MetaS = elixir_scope:serialize(S),
Invoke = [
{atom, Line, Kind},
{integer, Line, Line},
{var, Line, '_EXMODULE'},
{var, Line, '_@MODULE'},
Name,
Args,
Guards,
@@ -62,109 +63,127 @@ 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) ->
S = elixir_variables:deserialize_scope(RawS),
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "cannot define function outside module, invalid scope for ~s", [Kind]);
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, Body),
DS = elixir_variables:deserialize_scope(RawS),
S = DS#elixir_scope{function={Name,Arity}, module=Module},
case RawExpr of
skip_definition -> Expr = nil;
[{ do, Expr }] -> [];
_ -> Expr = { 'try', Line, [RawExpr] }
end,
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),
Filename = TS#elixir_scope.filename,
FunctionTable = table(Module),
CO = elixir_compiler:get_opts(),
compile_docs(Kind, Line, Module, Name, Arity, TS, CO),
Location = retrieve_file(Module, CO),
Stack = S#elixir_scope.macro,
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, Filename, Location,
Stack, FunctionTable, length(Defaults), Function)
store_each(CheckClauses, Kind, File, Location,
Stack, Table, length(Defaults), Function)
end,
%% Store defaults
[store_each(false, Kind, Filename, Location, Stack, FunctionTable, 0,
function_for_clause(Name, Default)) || Default <- Defaults],
[store_each(false, Kind, File, Location, Stack, Table, 0,
function_for_default(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity }.
%% Compile the documentation related to the module.
def_body(_Line, nil) -> nil;
def_body(_Line, [{ do, Expr }]) -> Expr;
def_body(Line, Else) -> { 'try', Line, [Else] }.
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
elixir_def_overridable:store(Module, Function, true);
%% @on_definition
compile_super(_Module, _S) -> [].
compile_docs(Kind, Line, Module, Name, Arity, S, CO) ->
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO) ->
case elixir_compiler:get_opt(internal, CO) of
true -> [];
true ->
ok;
_ ->
case '__MAIN__.Module':compile_doc(Module, Line, Kind, { Name, Arity }) of
{ error, Message } -> elixir_errors:handle_file_warning(S#elixir_scope.filename,
{ Line, ?MODULE, { Message, { Name, Arity } } });
_ -> []
end
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 '__MAIN__.Module':read_data(Module, file) of
case 'Elixir.Module':get_attribute(Module, file) of
nil -> [];
Else ->
'__MAIN__.Module':merge_data(Module, [{file,nil}]),
if
is_binary(Else) -> { Else, 1 };
is_tuple(Else) -> Else
end
'Elixir.Module':delete_attribute(Module, file),
Else
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, RawArgs, RawGuards, Expr, S) ->
{ Args, Guards } = lists:mapfoldl(fun
({ 'in', _, [Left, _] } = X, Acc) ->
{ Left, add_to_guards(Line, X, Acc) };
(X, Acc) ->
{ X, Acc }
end, RawGuards, RawArgs),
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),
Arity = length(Args),
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, Args, S),
%% Macros receive a special argument on invocation. Notice it does
%% not affect the arity of the stored function, but the clause
%% already contains it.
ExtendedArgs = case IsMacro of
true -> [{ '_@CALLER', Line, nil }|Args];
false -> Args
end,
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
{ TClause, TS } = elixir_clauses:assigns_block(Line,
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
FClause = case TS#elixir_scope.name_args of
%% Add names to args
NClause = case TS#elixir_scope.name_args of
true ->
FArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
setelement(3, TClause, FArgs);
NArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
setelement(3, TClause, NArgs);
false -> TClause
end,
%% Set __CALLER__ if used
FClause = case IsMacro andalso TS#elixir_scope.caller of
true ->
FBody = { 'match', Line,
{ 'var', Line, '__CALLER__' },
?ELIXIR_WRAP_CALL(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
},
setelement(5, NClause, [FBody|element(5, NClause)]);
false -> NClause
end,
Function = { function, Line, Name, Arity, [FClause] },
{ Function, Defaults, TS }.
is_macro(defmacro) -> true;
is_macro(defmacrop) -> true;
is_macro(_) -> false.
% Unwrap the functions stored in the functions table.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
@@ -182,7 +201,7 @@ unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Fu
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
{ Name, Arity } = Tuple = element(1, Fun),
Macro = { ?ELIXIR_MACRO(Name), Arity },
Macro = { ?ELIXIR_MACRO(Name), Arity + 1 },
unwrap_stored_definition(
T, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
@@ -202,7 +221,8 @@ unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Fu
);
unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
{ Exports, Private, Def, Defmacro, Defmacrop, lists:reverse(Tail ++ Functions) }.
{ Exports, Private, ordsets:from_list(Def), ordsets:from_list(Defmacro),
ordsets:from_list(Defmacrop), lists:reverse(Tail ++ Functions) }.
%% Helpers
@@ -221,67 +241,58 @@ function_for_stored_definition({{Name, Arity}, _, Line, _, Location, _, _, Claus
]
}.
function_for_clause(Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
function_for_default(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
when Kind == defmacro; Kind == defmacrop ->
{ function, Line, Name, length(Args) - 1, [Clause] };
function_for_default(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
{ function, Line, Name, length(Args), [Clause] }.
%% Store each definition in the table.
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, Filename, Location, Stack, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
store_each(Check, Kind, File, Location, Stack, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
case ets:lookup(Table, {Name, Arity}) of
[{{Name, Arity}, StoredKind, _, _, StoredLocation, StoredStack, StoredDefaults, StoredClauses}] ->
FinalLocation = StoredLocation,
FinalDefaults = Defaults + StoredDefaults,
FinalClauses = Clauses ++ StoredClauses,
check_valid_kind(Line, Filename, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, Filename, Name, Arity, Defaults),
Check andalso (Stack == StoredStack) andalso check_valid_clause(Line, Filename, Name, Arity, Table);
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
Check andalso (Stack == StoredStack) andalso check_valid_clause(Line, File, Name, Arity, Table);
[] ->
FinalLocation = Location,
FinalDefaults = Defaults,
FinalClauses = Clauses,
Check andalso ets:insert(Table, { last, { Name, Arity } })
end,
ets:insert(Table, {{Name, Arity}, Kind, Line, Filename, FinalLocation, Stack, FinalDefaults, FinalClauses}).
ets:insert(Table, {{Name, Arity}, Kind, Line, File, FinalLocation, Stack, FinalDefaults, FinalClauses}).
%% Validations
check_valid_kind(_Line, _Filename, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, Filename, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, Filename, ?MODULE,
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE,
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
check_valid_clause(Line, Filename, Name, Arity, Table) ->
check_valid_clause(Line, File, Name, Arity, Table) ->
case ets:lookup_element(Table, last, 2) of
{Name,Arity} -> [];
[] -> [];
{ElseName, ElseArity} ->
elixir_errors:handle_file_warning(Filename, { Line, ?MODULE,
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
{ changed_clause, { { Name, Arity }, { ElseName, ElseArity } } } })
end.
check_valid_defaults(_Line, _Filename, _Name, _Arity, 0) -> [];
check_valid_defaults(Line, Filename, Name, Arity, _) ->
elixir_errors:handle_file_warning(Filename, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
%% Helpers
add_to_guards(_Line, Expr, []) ->
[Expr];
add_to_guards(Line, Expr, Clauses) ->
[{ 'and', Line, [Expr, Clause] } || Clause <- Clauses].
check_valid_defaults(_Line, _File, _Name, _Arity, 0) -> [];
check_valid_defaults(Line, File, Name, Arity, _) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
%% Format errors
format_error({clauses_with_docs,{Name,Arity}}) ->
io_lib:format("function ~s/~B has default values and multiple clauses, it is recommended to use a separate clause for declaring defalts", [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]);
io_lib:format("function ~s/~B has default values and multiple clauses, use a separate clause for declaring defaults", [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]);
@@ -3,7 +3,6 @@
-export([unpack/4]).
-include("elixir.hrl").
% Unpack default args from the given clause. Invoked by elixir_translate.
unpack(Kind, Name, Args, S) ->
unpack_each(Kind, Name, Args, [], [], S).
@@ -14,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),
@@ -36,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.
@@ -50,7 +50,7 @@ extract_defaults([], NewArgs, NewInvoke) ->
build_match([], _Line, Acc) -> Acc;
build_match([_|T], Line, Acc) ->
Var = { ?ELIXIR_ATOM_CONCAT(["_EX", length(T)]), Line, nil },
Var = { ?ELIXIR_ATOM_CONCAT(["_@", length(T)]), Line, nil },
build_match(T, Line, [Var|Acc]).
% Given the invoked function name based on the kind
@@ -8,45 +8,55 @@
]).
-include("elixir.hrl").
%% Reading
%% Used by elixir_dispatch, returns false if no macro is found
macro_for(_Tuple, _All, nil) -> false;
macro_for(_Tuple, _All, #elixir_scope{module=[]}) -> false;
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
case ets:lookup(elixir_def:table(Module), Tuple) of
macro_for(Tuple, All, Module) ->
try ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, Kind, Line, _, _, _, _, Clauses}] when Kind == defmacro; All, Kind == defmacrop ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result;
_ -> false
get_function(Line, Module, Clauses);
_ ->
false
catch
error:badarg -> false
end.
%% Used on runtime by rewritten clauses, raises an error if function is not found
function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, _, Line, _, _, _, _, Clauses}] ->
% elixir_def_local:record(Line, Tuple, false, Module),
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result;
get_function(Line, Module, Clauses);
_ ->
[_|T] = erlang:get_stacktrace(),
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
end.
%% Helpers
%% TODO: Consider caching functions in a table for performance.
get_function(Line, Module, Clauses) ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result.
%% TODO: Consider caching functions in a table for performance.
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
Remote = { remote, Line,
{ atom, Line, ?MODULE },
{ atom, Line, function_for }
},
Arity = { integer, Line, length(Args) },
Name = { atom, Line, rewrite_name(atom_to_list(RawName), RawName) },
FunCall = { call, Line, Remote, [{ atom, Line, Module }, Name, Arity] },
%% If we have a macro, its arity in the table is
%% actually one less than in the function call
{ Name, Arity } = case atom_to_list(RawName) of
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
_ -> { RawName, length(Args) }
end,
FunCall = { call, Line, Remote, [
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
] },
{ call, Line, FunCall, Args };
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
@@ -57,13 +67,10 @@ rewrite_clause(List, Module) when is_list(List) ->
rewrite_clause(Else, _) -> Else.
rewrite_name("MACRO-" ++ Rest, _) -> list_to_atom(Rest);
rewrite_name(_, Name) -> Name.
%% Error handling
check_unused_local_macros(Filename, Recorded, PMacros) ->
[elixir_errors:handle_file_warning(Filename,
check_unused_local_macros(File, Recorded, PMacros) ->
[elixir_errors:handle_file_warning(File,
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line } <- PMacros, not lists:member(Fun, Recorded)].
format_error({unused_macro,{Name, Arity}}) ->
@@ -1,22 +1,15 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([define/3, store_pending/1, is_defined/2, ensure_defined/4,
-export([store_pending/1, is_defined/2, ensure_defined/4,
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), overridable, 2).
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
overridable(Module, Value) ->
ets:insert(elixir_module:data_table(Module), { overridable, Value }).
%% Add new function to the overridable dictionary.
define(Module, Tuple, Args) ->
Old = overridable(Module),
New = [{ Tuple, { 1, [Args] } }],
Abstract = orddict:merge(fun(_K, { Count, V1 }, _V2) -> { Count + 1, [Args|V1] } end, Old, New),
overridable(Module, Abstract).
ets:insert(elixir_module:data_table(Module), { '__overridable', Value }).
%% Check if an overridable function is defined.
@@ -28,9 +21,9 @@ is_defined(Module, Tuple) ->
end.
ensure_defined(Line, Module, Tuple, S) ->
case elixir_def_overridable:is_defined(Module, Tuple) of
case is_defined(Module, Tuple) of
true -> [];
_ -> elixir_errors:form_error(Line, S#elixir_scope.filename, ?MODULE, { no_super, Module, Tuple })
_ -> elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
end.
%% Retrieve args defined for the given arity.
@@ -52,7 +45,7 @@ assign_args(Line, X, Acc, _) ->
{ Match, Acc + 1 }.
super_arg(Counter) ->
?ELIXIR_ATOM_CONCAT(['_EXS', Counter]).
?ELIXIR_ATOM_CONCAT(['_@S', Counter]).
%% Gets the name based on the function and stored overridables
@@ -61,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
@@ -70,7 +63,7 @@ store(Module, Function, GenerateName) ->
{ Count, [H|T] } = orddict:fetch(Function, Overridable),
overridable(Module, orddict:store(Function, { Count, T }, Overridable)),
{ { Name, Arity }, Kind, Line, Filename, Location, Stack, Defaults, Clauses } = H,
{ { Name, Arity }, Kind, Line, File, Location, Stack, Defaults, Clauses } = H,
{ FinalKind, FinalName } = case GenerateName of
true -> { defp, name(Module, Function, Overridable) };
@@ -78,21 +71,21 @@ store(Module, Function, GenerateName) ->
end,
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, Filename, Location,
elixir_def:store_each(false, FinalKind, File, Location,
Stack, elixir_def:table(Module), Defaults, Def).
%% Store pending declarations that were not manually made concrete.
store_pending(Module) ->
[store(Module, X, false) || { X, { _, [_|_] } } <- overridable(Module),
not '__MAIN__.Module':'function_defined?'(Module, X)].
not 'Elixir.Module':'defines?'(Module, X)].
%% Error handling
format_error({ no_super, Module, { Name, Arity } }) ->
Bins = [ format_fa(X) || { X, { _, [_|_] } } <- overridable(Module)],
Joined = '__MAIN__.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~s/~B in module ~p. Overridable functions available are: ~s",
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
[Name, Arity, elixir_errors:inspect(Module), Joined]).
format_fa({ Name, Arity }) ->
@@ -3,19 +3,22 @@
%% by elixir_import and therefore assumes it is normalized (ordsets)
-module(elixir_dispatch).
-export([default_macros/0, default_functions/0, default_requires/0,
dispatch_require/6, dispatch_imports/5,
dispatch_require/6, dispatch_import/5,
require_function/5, import_function/4,
format_error/1]).
expand_import/8, expand_require/8,
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
-import(ordsets, [is_element/2]).
-define(BUILTIN, '__MAIN__.Elixir.Builtin').
-define(BUILTIN, 'Elixir.Kernel').
default_functions() ->
[ { ?BUILTIN, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
default_macros() ->
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
default_requires() ->
[ ?BUILTIN ].
[ ?BUILTIN, 'Elixir.Kernel.Typespec' ].
%% Function retrieval
@@ -28,7 +31,7 @@ import_function(Line, Name, Arity, S) ->
_ -> false
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, S),
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
remote_function(Line, Receiver, Name, Arity, S)
end.
@@ -40,32 +43,27 @@ require_function(Line, Receiver, Name, Arity, S) ->
false -> remote_function(Line, Receiver, Name, Arity, S)
end.
%% Dispatch based on scope's imports
%% Function dispatch
dispatch_import(Line, Name, Args, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
dispatch_imports(Line, Name, Args, S, Callback) ->
Arity = length(Args),
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case find_dispatch(Tuple, S#elixir_scope.macros) of
false ->
Fun = (S#elixir_scope.function /= Tuple) andalso elixir_def_local:macro_for(Tuple, true, S),
case Fun of
false -> Callback();
_ ->
Receiver = S#elixir_scope.module,
elixir_import:record(import, Tuple, Receiver, S),
dispatch_macro_fun(Line, Fun, Receiver, Name, Arity, Args, S)
end;
?BUILTIN ->
elixir_import:record(import, Tuple, ?BUILTIN, S),
dispatch_builtin_macro(Line, Tuple, Args, S);
Receiver ->
elixir_import:record(import, Tuple, Receiver, S),
dispatch_macro(Line, Receiver, Name, Arity, Args, S)
case expand_import(Line, Tuple, Args, Module, S#elixir_scope.function,
S#elixir_scope.requires, S#elixir_scope.macros, S) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
elixir_macros:translate({ Name, Line, Args }, S);
{ ok, Receiver, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, S),
elixir_import:record(import, Tuple, Receiver, Module),
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
@@ -73,69 +71,109 @@ dispatch_imports(Line, Name, Args, S, Callback) ->
elixir_translator:translate_each({ { '.', Line, [Endpoint, Name] }, Line, Args }, S)
end.
%% Dispatch based on scope's require
dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
dispatch_require(Line, ?BUILTIN, Name, Args, S, Callback) ->
Arity = length(Args),
Tuple = {Name, Arity},
case is_element(Tuple, in_erlang_functions()) of
case (Receiver == Module) andalso is_element(Tuple, in_erlang_functions()) of
true ->
elixir_translator:translate_each({ { '.', Line, [erlang, Name] }, Line, Args }, S);
false ->
case dispatch_builtin_macro(Line, Tuple, Args, S) of
false -> Callback();
Else -> Else
case expand_require(Line, Receiver, Tuple, Args, Module,
S#elixir_scope.function, S#elixir_scope.requires, S) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_macros:translate({ Name, Line, Args }, S);
{ ok, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
end
end.
%% Macros expansion
expand_import(Line, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
case find_dispatch(Tuple, Macros) of
false ->
Fun = (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, Module),
case Fun of
false -> { error, noexpansion };
_ ->
elixir_import:record(import, Tuple, Module, Module),
{ ok, Module, expand_macro_fun(Line, Fun, Module, Name, Args, Module, Requires, SEnv) }
end;
?BUILTIN ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
{ ok, ?BUILTIN, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, Receiver, expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) }
end.
expand_require(Line, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, _Function, Requires, SEnv) ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
case is_element(Tuple, in_elixir_macros()) of
true -> { ok, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) };
false -> { error, noexpansion }
end
end;
dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
Arity = length(Args),
Tuple = {Name, Arity},
Fun = (S#elixir_scope.module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, false, S),
expand_require(Line, Receiver, { Name, Arity } = Tuple, Args, Module, Function, Requires, SEnv) ->
Fun = (Module == Receiver) andalso (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, false, Module),
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> dispatch_macro(Line, Receiver, Name, Arity, Args, S);
false -> Callback()
true -> { ok, expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) };
false -> { error, noexpansion }
end;
_ ->
elixir_import:record(import, Tuple, Receiver, S),
dispatch_macro_fun(Line, Fun, Receiver, Name, Arity, Args, S)
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) }
end.
%% HELPERS
%% Expansion helpers
dispatch_builtin_macro(Line, { Name, Arity } = Tuple, Args, S) ->
case is_element(Tuple, in_erlang_macros()) of
true -> elixir_macros:translate_macro({ Name, Line, Args }, S);
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
case (Receiver == Module) or is_element(Receiver, Requires) of
true -> ok;
false ->
case is_element(Tuple, in_elixir_macros()) of
true -> dispatch_macro(Line, ?BUILTIN, Name, Arity, Args, S);
false -> false
end
end.
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
elixir_errors:form_error(Line, elixir_scope:filename(SEnv), ?MODULE, Tuple)
end,
dispatch_macro(Line, Receiver, Name, Arity, Args, S) ->
Macro = ?ELIXIR_MACRO(Name),
dispatch_macro_fun(Line, fun Receiver:Macro/Arity, Receiver, Name, Arity, Args, S).
SArg = {Line,SEnv},
dispatch_macro_fun(Line, Fun, Receiver, Name, Arity, Args, S) ->
ensure_required(Line, Receiver, Name, Arity, S),
Tree = try
apply(Fun, Args)
try
apply(Fun, [SArg|Args])
catch
Kind:Reason ->
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.filename }, { line, Line }] },
erlang:raise(Kind, Reason, insert_before_dispatch_macro(Info, erlang:get_stacktrace()))
end,
Info = { Receiver, Name, length(Args), [{ file, elixir_scope:filename(SEnv) }, { line, Line }] },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
end.
expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
ProperName = ?ELIXIR_MACRO(Name),
ProperArity = Arity + 1,
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv).
translate_expansion(Line, Tree, Receiver, Name, Arity, S) ->
NewS = S#elixir_scope{macro=[{Line,Receiver,Name,Arity}|S#elixir_scope.macro]},
{ TTree, TS } = elixir_translator:translate_each(elixir_quote:linify(Line, Tree), NewS),
{ TTree, TS#elixir_scope{macro=S#elixir_scope.macro} }.
%% Helpers
find_dispatch(Tuple, [{ Name, Values }|T]) ->
case is_element(Tuple, Values) of
true -> Name;
@@ -144,32 +182,24 @@ find_dispatch(Tuple, [{ Name, Values }|T]) ->
find_dispatch(_Tuple, []) -> false.
%% Insert call site into backtrace right after dispatch macro
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
insert_before_dispatch_macro(Info, [{ elixir_dispatch, dispatch_macro_fun, _, _ }|_] = T) ->
[Info|T];
munge_stacktrace(Info, [{ elixir_dispatch, expand_macro_fun, _, _ }|_], _) ->
[Info];
insert_before_dispatch_macro(Info, [H|T]) ->
[H|insert_before_dispatch_macro(Info, T)];
munge_stacktrace(Info, [H|T], S) ->
[H|munge_stacktrace(Info, T, S)];
insert_before_dispatch_macro(_, []) ->
munge_stacktrace(_, [], _) ->
[].
%% ERROR HANDLING
ensure_required(_Line, Receiver, _Name, _Arity, #elixir_scope{module=Receiver}) -> ok;
ensure_required(Line, Receiver, Name, Arity, S) ->
Requires = S#elixir_scope.requires,
case is_element(Receiver, Requires) of
true -> ok;
false ->
Tuple = { unrequired_module, { Receiver, Name, Arity, Requires } },
elixir_errors:form_error(Line, S#elixir_scope.filename, ?MODULE, Tuple)
end.
format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) ->
io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~p",
[elixir_errors:inspect(Receiver), Name, Arity, [elixir_errors:inspect(R) || R <- Required]]).
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~s",
[elixir_errors:inspect(Receiver), Name, Arity, String]).
%% INTROSPECTION
@@ -200,7 +230,7 @@ get_optional_macros(Receiver) ->
{ error, _ } -> []
end.
%% Functions imported from Elixit.Builtin module. Sorted on compilation.
%% Functions imported from Kernel module. Sorted on compilation.
in_elixir_functions() ->
try
@@ -209,7 +239,7 @@ in_elixir_functions() ->
error:undef -> []
end.
%% Macros imported from Elixit.Builtin module. Sorted on compilation.
%% Macros imported from Kernel module. Sorted on compilation.
in_elixir_macros() ->
try
@@ -224,9 +254,7 @@ in_erlang_functions() ->
{ abs, 1 },
{ atom_to_binary, 2 },
{ atom_to_list, 1 },
% Those are allowed in guard clauses, so we need to bring them back.
% { binary_part, 2 },
% { binary_part, 3 },
{ binary_part, 3 },
{ binary_to_atom, 2 },
{ binary_to_existing_atom, 2 },
{ binary_to_list, 1 },
@@ -248,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 },
@@ -280,6 +309,7 @@ in_erlang_functions() ->
% { now, 0 },
{ pid_to_list, 1 },
{ round, 1 },
{ self, 0 },
{ size, 1 },
{ spawn, 1 },
{ spawn, 3 },
@@ -299,6 +329,7 @@ in_erlang_functions() ->
%% Macros implemented in Erlang. MUST BE SORTED.
in_erlang_macros() ->
[
{'!',1},
{'!=',2},
{'!==',2},
{'*',2},
@@ -317,7 +348,6 @@ in_erlang_macros() ->
{'>',2},
{'>=',2},
{'@',1},
{access,2},
{'and',2},
{apply,2},
{apply,3},
@@ -335,12 +365,14 @@ in_erlang_macros() ->
{defp,1},
{defp,2},
{defp,4},
{function,1},
{function,2},
{function,3},
{in,2},
{'not',1},
{'or',2},
{'receive',1},
{'try',1},
{use,1},
{use,2},
{'var!',1},
{'xor',2}
].
+175
View File
@@ -0,0 +1,175 @@
% A bunch of helpers to help to deal with errors in Elixir source code.
% This is not exposed in the Elixir language.
-module(elixir_errors).
-export([syntax_error/3, syntax_error/4, inspect/1,
form_error/4, parse_error/4, assert_module_scope/3,
assert_no_function_scope/3, assert_function_scope/3,
assert_no_assign_scope/3, assert_no_guard_scope/3,
assert_no_assign_or_guard_scope/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4, file_format/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Handle inspecting for exceptions
inspect(Atom) when is_atom(Atom) ->
case atom_to_list(Atom) of
"Elixir-" ++ Rest -> [to_dot(R) || R <- Rest];
Else -> Else
end;
inspect(Other) -> Other.
to_dot($-) -> $.;
to_dot(L) -> L.
%% Raised during macros translation.
syntax_error(Line, File, Message) when is_list(Message) ->
syntax_error(Line, File, iolist_to_binary(Message));
syntax_error(Line, File, Message) when is_binary(Message) ->
raise(Line, File, 'Elixir.SyntaxError', Message).
syntax_error(Line, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Line, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
%% Raised on tokenizing/parsing
parse_error(Line, File, Error, []) ->
Message = case Error of
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
_ -> iolist_to_binary(Error)
end,
raise(Line, File, 'Elixir.TokenMissingError', Message);
parse_error(Line, File, "syntax error before: ", "'end'") ->
raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
parse_error(Line, File, Error, Token) ->
BinError = if
is_atom(Error) -> atom_to_binary(Error, utf8);
true -> iolist_to_binary(Error)
end,
BinToken = if
Token == [] -> <<>>;
true -> iolist_to_binary(Token)
end,
Message = <<BinError / binary, BinToken / binary >>,
raise(Line, File, 'Elixir.SyntaxError', Message).
%% Raised during compilation
form_error(Line, File, Module, Desc) ->
Message = iolist_to_binary(format_error(Module, Desc)),
raise(Line, File, 'Elixir.CompileError', Message).
%% Shows a deprecation message
deprecation(Line, File, Message) -> deprecation(Line, File, Message, []).
deprecation(Line, File, Message, Args) ->
io:format(file_format(Line, File, io_lib:format(Message, Args))).
%% Handle warnings and errors (called during module compilation)
%% Ignore on bootstrap
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
%% Ignore always
handle_file_warning(_, _File, { _Line, sys_core_fold, useless_building }) -> [];
%% This is an Erlang bug, it considers { tuple, _ }.call to always fail
handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% Rewrite
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~s function ~s/~B (for ~s ~s)",
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
io:format(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
Raw = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
Message = case erlang:function_exported(Module, behavior_info, 1) of
true -> Raw ++ " (maybe you meant behaviour_info instead of behavior_info?)";
false -> Raw
end,
io:format(file_format(Line, File, Message));
%% Default behavior
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
io:format(file_format(Line, File, Message)).
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc).
%% Assertions
assert_no_function_scope(_Line, _Kind, #elixir_scope{function=nil}) -> [];
assert_no_function_scope(Line, Kind, S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside a function", [Kind]).
assert_no_assign_or_guard_scope(Line, Kind, S) ->
assert_no_assign_scope(Line, Kind, S),
assert_no_guard_scope(Line, Kind, S).
assert_no_assign_scope(Line, Kind, #elixir_scope{context=assign} = S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside assign", [Kind]);
assert_no_assign_scope(_Line, _Kind, _S) -> [].
assert_no_guard_scope(Line, Kind, #elixir_scope{context=guard} = S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside guard", [Kind]);
assert_no_guard_scope(_Line, _Kind, _S) -> [].
assert_module_scope(Line, Kind, #elixir_scope{module=nil,file=File}) ->
syntax_error(Line, File, "cannot invoke ~s outside module", [Kind]);
assert_module_scope(_Line, _Kind, #elixir_scope{module=Module}) -> Module.
assert_function_scope(Line, Kind, #elixir_scope{function=nil,file=File}) ->
syntax_error(Line, File, "cannot invoke ~s outside function", [Kind]);
assert_function_scope(_Line, _Kind, #elixir_scope{function=Function}) -> Function.
%% Helpers
raise(Line, File, Kind, Message) ->
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
erlang:raise(error, Exception, Stacktrace).
file_format(Line, File, Message) ->
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
format_error([], Desc) ->
io_lib:format("~p", [Desc]);
format_error(Module, 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.
@@ -14,11 +14,15 @@ build_table(Module) ->
delete_table(Module) ->
ets:delete(table(Module)).
record(_Kind, _Tuple, _Receiver, #elixir_scope{module=[]}) ->
[];
record(_Kind, _Tuple, _Receiver, nil) ->
false;
record(import, Tuple, Receiver, #elixir_scope{module=Module}) ->
ets:insert(table(Module), { Tuple, Receiver }).
record(import, Tuple, Receiver, Module) ->
try
ets:insert(table(Module), { Tuple, Receiver })
catch
error:badarg -> false
end.
recorded_locals(Module) ->
Table = table(Module),
@@ -27,26 +31,28 @@ recorded_locals(Module) ->
ets:match_delete(Table, Match),
lists:append(Result).
%% Update the scope to consider the imports for reference
%% Update the scope to consider the imports for aliases
%% 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,
@@ -61,40 +67,67 @@ import(Line, Ref, Opts, Selector, S) ->
%% Calculates the imports based on only and except
calculate(Line, Key, Opts, Old, AvailableFun, S) ->
Filename = S#elixir_scope.filename,
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, Filename, ?MODULE, Tuple);
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;
_ ->
ensure_no_conflicts(Line, Filename, Final, keydelete(Key, S#elixir_scope.macros)),
ensure_no_conflicts(Line, Filename, Final, keydelete(Key, S#elixir_scope.functions)),
ensure_no_in_erlang_macro_conflict(Line, Filename, Key, Final, internal_conflict),
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.macros)),
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.functions)),
ensure_no_in_erlang_macro_conflict(Line, File, Key, Final, internal_conflict),
[{ 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) ->
@@ -117,7 +150,7 @@ get_macros(Line, Module, S) ->
catch
error:undef ->
Tuple = { no_macros, Module },
elixir_errors:form_error(Line, S#elixir_scope.filename, ?MODULE, Tuple)
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, Tuple)
end.
get_optional_macros(Module) ->
@@ -137,13 +170,13 @@ get_optional_macros(Module) ->
%% Elixir "implemented in Erlang" macro. Checking if a local
%% conflicts with an import is automatically done by Erlang.
ensure_no_local_conflict(Line, Filename, Module, AllDefined) ->
ensure_no_in_erlang_macro_conflict(Line, Filename, Module, AllDefined, local_conflict).
ensure_no_local_conflict(Line, File, Module, AllDefined) ->
ensure_no_in_erlang_macro_conflict(Line, File, Module, AllDefined, local_conflict).
%% Find conlicts in the given list of functions with
%% the recorded set of imports.
ensure_no_import_conflict(Line, Filename, Module, AllDefined) ->
ensure_no_import_conflict(Line, File, Module, AllDefined) ->
Table = table(Module),
Matches = [X || X <- AllDefined, ets:member(Table, X)],
@@ -151,7 +184,7 @@ ensure_no_import_conflict(Line, Filename, Module, AllDefined) ->
[{Name,Arity}|_] ->
Key = ets:lookup_element(Table, {Name, Arity }, 2),
Tuple = { import_conflict, { Key, Name, Arity } },
elixir_errors:form_error(Line, Filename, ?MODULE, Tuple);
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
[] ->
ok
end.
@@ -159,7 +192,7 @@ ensure_no_import_conflict(Line, Filename, Module, AllDefined) ->
%% Ensure the given functions don't clash with any
%% of Elixir non overridable macros.
ensure_no_in_erlang_macro_conflict(Line, Filename, Key, [{Name,Arity}|T], Reason) ->
ensure_no_in_erlang_macro_conflict(Line, File, Key, [{Name,Arity}|T], Reason) ->
Values = lists:filter(fun({X,Y}) ->
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
end, non_overridable_macros()),
@@ -167,27 +200,27 @@ ensure_no_in_erlang_macro_conflict(Line, Filename, Key, [{Name,Arity}|T], Reason
case Values /= [] of
true ->
Tuple = { Reason, { Key, Name, Arity } },
elixir_errors:form_error(Line, Filename, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Line, Filename, Key, T, Reason)
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Line, File, Key, T, Reason)
end;
ensure_no_in_erlang_macro_conflict(_Line, _Filename, _Key, [], _) -> ok.
ensure_no_in_erlang_macro_conflict(_Line, _File, _Key, [], _) -> ok.
%% Find conlicts in the given list of functions with the set of imports.
%% Used internally to ensure a newly imported fun or macro does not
%% conflict with an already imported set.
ensure_no_conflicts(Line, Filename, Functions, [{Key,Value}|T]) ->
ensure_no_conflicts(Line, File, Functions, [{Key,Value}|T]) ->
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
case Filtered of
[{Name,Arity}|_] ->
Tuple = { already_imported, { Key, Name, Arity } },
elixir_errors:form_error(Line, Filename, ?MODULE, Tuple);
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
[] ->
ensure_no_conflicts(Line, Filename, Functions, T)
ensure_no_conflicts(Line, File, Functions, T)
end;
ensure_no_conflicts(_Line, _Filename, _Functions, _S) -> ok.
ensure_no_conflicts(_Line, _File, _Functions, _S) -> ok.
%% ERROR HANDLING
@@ -232,13 +265,22 @@ intersection([], _All) -> [].
%% Internal funs that are never imported etc.
internal_funs() ->
[
{ module_info, 0 },
{ module_info, 1 },
{ '__info__', 1 },
{ '__using__', 2 }
].
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.
@@ -248,29 +290,32 @@ non_overridable_macros() ->
{'=',2},
{'__op__',2},
{'__op__',3},
{'__ambiguousop__','*'},
{'__scope__',2},
{'__block__','*'},
{'->','2'},
{'<<>>','*'},
{'{}','*'},
{'[]','*'},
{'alias',1},
{'alias',2},
{'require',1},
{'require',2},
{'import',1},
{'import',2},
{'import',3},
{'__ENV__',0},
{'__CALLER__',0},
{'__MODULE__',0},
{'__FILE__',0},
{'__LINE__',0},
{'__FUNCTION__',0},
{'__ref__',1},
{'__aliases__','*'},
{'quote',1},
{'quote',2},
{'unquote',1},
{'unquote_splicing',1},
{'fn','*'},
{'loop','*'},
{'recur','*'},
{'super','*'},
{'super?',0},
{'bc','*'},
{'lc','*'}
].
].

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