Compare commits

...
701 Commits
Author SHA1 Message Date
José Valim 05beafd5bb Release v0.12.1 2014-01-04 10:37:46 +01:00
José Valim cba7c5bd80 Only expand __ENV__.call if it is a known function 2014-01-03 22:39:48 +01:00
José Valim 9b22b6d883 Ensure local is disabled on module definitions 2014-01-03 17:57:24 +01:00
José Valim 2a6940014a Move environment cache to locals tracker, which is per module 2014-01-02 20:44:32 +01:00
José + Eric 2a1941112e Cache the environment for function definition
This speeds up compilation and reduces memory consumption. For
example, Ecto test suite consumes consistently 30% less memory
and loads 20% faster.
2014-01-02 18:10:59 +01:00
José Valim 5e2ea082d1 Deprecate __FILE__ in favor of __DIR__ or __ENV__.file 2014-01-02 18:10:59 +01:00
José Valim ba33806cc3 Remove old deprecated functions 2014-01-02 18:10:58 +01:00
Eric Meadows-Jonsson 62eeb97eb8 Improve typespec docs 2014-01-02 14:35:54 +01:00
Eric Meadows-Jonsson 0a24252fca Properly handle tuples in typespecs 2014-01-02 14:35:54 +01:00
José Valim 243f4e6b0a Merge pull request #1976 from knewter/feature/file_module_doc_tweaks
Fix a couple of typos in the File module
2014-01-02 04:53:38 -08:00
Josh Adams 3bc277f525 Fix a couple of typos in the File module 2014-01-02 06:40:23 -06:00
José Valim f0bfdfaf19 Require arguments to be expanded to simplify relative symlink logic 2014-01-01 14:20:41 +01:00
José Valim 2979430137 Merge pull request #1959 from jni-/mix_rel_symlink
Using relative symlinks, fixes issue #1953
2014-01-01 04:58:58 -08:00
José Valim 1c4b759c4e Bring back no name and simplify var merging 2014-01-01 12:22:49 +01:00
Eric Meadows-Jonsson f67022267f Reset tags for every test 2013-12-31 20:25:32 +01:00
Eric Meadows-Jönsson febee82e9e Merge pull request #1974 from ericmj/exunit-teardown
ex_unit teardown should run even if setup fails
2013-12-31 09:59:26 -08:00
José Valim 66d8cdc56e Update CHANGELOG and error messages 2013-12-31 18:55:45 +01:00
Eric Meadows-Jonsson 8d59c007c8 ex_unit teardown should run even if setup fails 2013-12-31 18:51:56 +01:00
José Valim 3a0b4988fc Shut down GenFSM on unknown messages 2013-12-31 18:35:22 +01:00
José Valim b7bbe13c91 Remove non ansi handling from IO.ANSI.Docs 2013-12-31 17:59:24 +01:00
José Valim 4cbe174c13 Merge pull request #1971 from rcillo/rd-improve-helpers-r-error-message
Improve error message for IEx.Helpers.r when module does not exist.
2013-12-31 08:49:47 -08:00
José Valim bbe63e9a93 Merge pull request #1975 from thmzlt/ansi_docs
Move IEx.ANSIDocs to IO.ANSI.Docs and decouple it from IEx
2013-12-31 08:47:19 -08:00
José Valim 6f0f0d5136 Merge pull request #1970 from chrismcg/gen_server_stops_on_unknown_requests
Stop GenServer on unknown call/cast request
2013-12-31 08:45:19 -08:00
José Valim e055c2ccf2 Merge pull request #1961 from justinj/float-parse-negative-0
Fix Float.parse to handle numbers of the form "-0.x", fixes issue #1960
2013-12-31 08:44:53 -08:00
José Valim 08d61e4b25 Merge pull request #1954 from bitwalker/mix
Ensure deps.get updates origin if lock origin and dep origin do not match.
2013-12-31 08:43:55 -08:00
José Valim 9c9d8e855e Merge pull request #1943 from ericmj/doctest-inspect
Add raw string compare in doctests for #Module<...>
2013-12-31 08:39:26 -08:00
José Valim bb7b990055 Remove trailing whitespace 2013-12-31 17:38:51 +01:00
José Valim 0aee0805a9 Merge pull request #1933 from Enders/elixirc_patterns
Elixirc: fail when one of the provided file pattern is missing
2013-12-31 08:33:15 -08:00
José Valim 9afc1043c9 Move filters functionality to ExUnit.Filters 2013-12-31 17:31:20 +01:00
José Valim 1622eb63b4 Merge pull request #1909 from vanstee/exunit-support-filters
Support ExUnit filters
2013-12-31 08:04:05 -08:00
José Valim d769f8f35e Improve docs for case, try, receive and ^ 2013-12-31 17:01:17 +01:00
José Valim 9cd56804ea Use a main counter for hygienic variables 2013-12-31 14:20:24 +01:00
José Valim 6d17b42beb Ensure variables in remote do not leak to args 2013-12-31 11:54:15 +01:00
José Valim ac546881e8 Be more conservative on how counters are passed forward to decrease var copy 2013-12-31 11:45:53 +01:00
José Valim 77aca31ffe Remove expansion counter 2013-12-31 10:40:04 +01:00
Thomaz Leite 9395bb105d Define IEx.dont_display_result in IO.ANSI.Docs
This removes all remaining references to IEx from IO.ANSI.Docs and makes the dont_display_result function more generic by replacing the "in iex" bit with "in output".
2013-12-30 22:06:07 -03:00
Thomaz Leite 47feb1b6a0 Fix other references to IO.ANSI.Docs 2013-12-30 21:57:24 -03:00
Thomaz Leite f98afc1d83 Fix IO.ANSI.Docs and its corresponding test 2013-12-30 21:57:10 -03:00
Thomaz Leite 6b407a339b Move IEx.ANSIDocs > IO.ANSI.Docs 2013-12-30 19:47:11 -03:00
Thomaz Leite c91d2d9c2b Add documentation for ANSIDocs printing functions 2013-12-30 19:38:12 -03:00
Thomaz Leite f82fbdcaed Add default color options for ANSIDocs 2013-12-30 19:38:12 -03:00
Thomaz Leite c6817c80b0 Remove IEx options from default args in ANSIDocs 2013-12-30 19:38:12 -03:00
Thomaz Leite 6fc468bddb Refactor ANSIDocs.write helper 2013-12-30 19:38:12 -03:00
Thomaz Leite 5b6fc9b415 Use parameterized colors in ANSIDocs for inline handling 2013-12-30 19:38:12 -03:00
Thomaz Leite 5c45e4aed4 Pass colors parameter to inline handling functions in ANSIDocs 2013-12-30 19:38:12 -03:00
Thomaz Leite 5fa617a8aa Use parameterized colors in ANSIDocs for code 2013-12-30 19:38:12 -03:00
Thomaz Leite 387dd72d54 Use parameterized colors in ANSIDocs for body headings 2013-12-30 19:38:12 -03:00
Thomaz Leite 91bbfa52ba Pass colors parameter to process* functions in ANSIDocs 2013-12-30 19:38:12 -03:00
Thomaz Leite b80fe27705 Use parameterized colors in ANSIDocs for headings
Instead of calling IEx.Options.get(:colors) directly, set it as default value for the colors parameter.
This way, when the rest of the module start using the parameterized colors, it can be decoupled from IEx.Options (and IEx.color).
2013-12-30 19:38:12 -03:00
Ricardo de Cillo dbcb3717d6 Add the failed module to the exception message.
This way it makes clearer what module we are referring to.
2013-12-30 20:17:01 -02:00
José Valim 7836493d23 Do not leak clause heads, closes #1948 2013-12-30 23:12:44 +01:00
José Valim d27c7b0e1f Always return #elixir_env from callbacks 2013-12-30 22:31:14 +01:00
Ricardo de Cillo 26bc9d9d61 Make sure the given module is loaded before trying reload it. 2013-12-30 19:05:04 -02:00
José Valim 684cf05d75 Skip merge when arg is a literal 2013-12-30 16:41:20 +01:00
José Valim 417c719c78 Speed up how variables are merged by keeping a counter 2013-12-30 15:48:24 +01:00
José Valim dc63e25995 Simplify erlang translation var building 2013-12-30 14:33:14 +01:00
José Valim 74e9f76aaa Update CHANGELOG 2013-12-30 12:55:05 +01:00
José Valim 40d383faae Merge pull request #1973 from elixir-lang/jv-exp
Split elixir expansion from translation in the compiler
2013-12-30 03:51:41 -08:00
José Valim eaac3bbd76 Ensure aliases dispatch to the proper function, guarantee files are binaries 2013-12-30 12:38:36 +01:00
José Valim ae12432294 Ensure warnings are recorded only if there are no errors 2013-12-30 12:22:24 +01:00
José Valim d0a6792bdf Temporarily disable case clause rewrite optimization 2013-12-30 12:16:41 +01:00
José Valim a5b67f3084 Add missing elixir_counter 2013-12-30 12:10:17 +01:00
José Valim 6e48e5dfcb Ensure we run lexical warnings for dynamic modules 2013-12-30 12:10:00 +01:00
José Valim 83c93beaa6 Get rid of environment macro counter in favor of a global one 2013-12-30 11:45:23 +01:00
Ricardo de Cillo ddd4afbdba Improve error message for IEx.Helpers.r when module does not exist.
Before this commit, when trying to reload an unloaded module, the
user would see an error like:

iex(1)> r InexistentModule
** (UndefinedFunctionError) undefined function: InexistentModule.module_info/1
    InexistentModule.module_info(:compile)
    lib/iex/lib/iex/helpers.ex:352: IEx.Helpers.source/1
    lib/iex/lib/iex/helpers.ex:311: IEx.Helpers.do_r/1
    lib/iex/lib/iex/helpers.ex:304: IEx.Helpers.r/1

After this commit, the user should see


iex(1)> r InexistentModule
** (ArgumentError) Unloaded module Elixir.InexistentModule. Try loading the module's beam file with `IEx.Helpers.l\1`
    lib/iex/lib/iex/helpers.ex:312: IEx.Helpers.do_r/1
    lib/iex/lib/iex/helpers.ex:304: IEx.Helpers.r/1

Which is more informative.
2013-12-30 01:56:55 -02:00
Chris McGrath a2cc2623a8 Stop GenServer on unknown call/cast request 2013-12-29 22:12:45 +00:00
José Valim d4da5be4ee Remove more duplication in between expansion and translation 2013-12-29 16:40:11 +01:00
José Valim b03e2173a5 Rename translate_each to translate and temp_vars to match_vars 2013-12-29 16:08:58 +01:00
José Valim f547ef82e2 Clean up literal handling and add elixir_bitstring 2013-12-29 15:41:39 +01:00
José Valim 254065d3c4 Clean up synax_error, asstions and merge mechanisms 2013-12-29 14:22:45 +01:00
José Valim 29b38ab9e0 Discard old dispatch 2013-12-29 12:23:38 +01:00
José Valim 0e627c9b09 Use expansion on function bodies 2013-12-29 10:54:10 +01:00
José Valim f8b019c7c8 Move to the new expansion on the front-end 2013-12-28 23:14:55 +01:00
José Valim c4419268b8 Move module compilation to rely on elixir_exp 2013-12-28 15:01:31 +01:00
José Valim ade26dedd8 Work on Elixir expressions rather than forms 2013-12-28 11:47:44 +01:00
José Valim 574e92fc57 Also expand binaries 2013-12-27 15:52:17 +01:00
Justin Jaffray 8c6e31bda0 Pattern match just on "-" 2013-12-25 13:25:36 -05:00
Justin Jaffray 70d7a28a36 Fix Float.parse to handle numbers of the form "-0.x"
Since Float.parse uses Integer.parse, the -0 would be
parsed independently of what came after the decimal
point and would become 0
2013-12-25 10:35:24 -05:00
José Valim 78def5da6e Raise better message on invalid or reserved codepoints 2013-12-25 14:44:01 +01:00
José Valim ffe2cc74b8 Expand try 2013-12-25 13:20:49 +01:00
José Valim 99b1d9b677 Merge pull request #1958 from mururu/rm-unneeded-assert
Remove unneeded assert in assert_raise
2013-12-25 01:56:47 -08:00
Yuki Ito 869378d1ca Remove unneeded assert in assert_raise 2013-12-25 17:48:58 +09:00
Jni f306ca5a49 Using relative symlinks, fixes issue #1953 2013-12-25 00:22:39 -05:00
José Valim b39c762557 Rename redundant is_ prefix in internal APIs 2013-12-24 23:06:59 +01:00
José Valim 98e15914eb Expand receive 2013-12-24 14:23:22 +01:00
José Valim 8c47368fff Expand case 2013-12-24 13:48:58 +01:00
Paul Schoenfelder 4176480f39 Combine get_rev and get_origin into one system call 2013-12-23 12:48:41 -06:00
José Valim f81a15b7cb Expand fn 2013-12-23 18:23:08 +01:00
José Valim b3ba12593d Merge pull request #1956 from diogovk/master
fix bug in code sample for Stream.resource
2013-12-23 07:10:54 -08:00
José Valim 51aef55c83 Change how -> is represented in the AST
The previous representation was very hard to compose
and very often required the AST nodes to be created
manually.

The current approach is easier to compose because
the node is represented as any other operator as long
as it is wrapped in parentheses.

For example, imagine we have pairs of patterns and
the code to be executed for that pattern, and we want
to inject them into a case. It can now be writen as:

    pairs = Enum.map pairs, fn { pattern, expr } ->
      quote do: (unquote(pattern) -> unquote(expr))
    end

    quote do
      case unquote(condition), do: unquote(pairs)
    end

Compare to the previous implementation:

    pairs = Enum.map pairs, fn { pattern, expr } ->
      { [pattern], [], expr }
    end

    pairs = { :->, [], pairs }

    quote do
      case unquote(condition), do: unquote(pairs)
    end
2013-12-23 16:10:33 +01:00
Diogo Vinicius Kersting ec41e0fe1e fix bug in code sample for Stream.resource 2013-12-23 12:30:30 -02:00
José Valim 2d36a76b26 Move capture to elixir_exp 2013-12-23 14:59:31 +01:00
José Valim 9f5e7c4a41 Fix expansion of =, inbits and inlist regarding rhs lexical info 2013-12-23 13:00:09 +01:00
José Valim e98f3eae10 Use spawn_monitor instead of spawn_link to avoid EXIT
messages leaking into the mailbox.
2013-12-23 11:30:32 +01:00
José Valim 5b5075b2c7 Merge pull request #1955 from ma2gedev/fix_uri_docs
fix typo in URI docs
2013-12-23 00:04:10 -08:00
Takayuki Matsubara 238baa14fa fix typo in URI docs
URI.decode_query returns a `HashDict`
2013-12-23 13:57:50 +09:00
Paul Schoenfelder 620532049c Move test for matching origins to tail of lock_status check 2013-12-22 19:34:03 -06:00
Paul Schoenfelder 67566f155b During update, always set the origin url to the lock url 2013-12-22 19:23:19 -06:00
Paul Schoenfelder d976937486 Flag lock as outdated if origins do not match 2013-12-22 19:21:42 -06:00
Eric Meadows-Jonsson 3f4410ff68 Make sure IEx r helper does not return a record
The tuple r/1 returns can be a record and IEx will try to inspect it.
Tag the tuple so it will be inspected as a tuple.
2013-12-22 19:50:20 +01:00
José Valim 4c26585ef6 Simplify and fix bugs around lexical scope on try 2013-12-22 12:34:49 +01:00
José Valim 6fa7d64814 Fix build 2013-12-22 12:08:09 +01:00
José Valim 67eeedfb1d Expand requires and imports 2013-12-22 11:31:25 +01:00
José Valim d0a823c8b0 Expand comprehensions 2013-12-22 10:01:35 +01:00
José Valim df9d535769 Delay some error handling to translation 2013-12-22 09:37:07 +01:00
José Valim 219ded1b91 Rename DispatchTracker to LocalsTracker 2013-12-22 09:20:41 +01:00
José Valim a0eef07958 No need to require for requires for all expansions 2013-12-21 22:00:42 +01:00
José Valim c7af8bc2cc Improve compilation times and memory consumption
This commit includes three changes:

1. Previously, the parallel compiler released at once all processes
   depending on a particular module. This commit ensures they are
   released according to the number of cores available. This ensures
   we have fewer modules being compiled at once

2. Garbage collect the current process every time we are waiting
   for another module to be compiled

3. Do not compile a module for files that simply define modules
2013-12-21 20:06:50 +01:00
Eric Meadows-Jonsson 349873ab63 Handle unary ops when converting typespec to ast 2013-12-20 14:01:50 +01:00
José Valim f2cf4a393f Expand super 2013-12-20 11:36:25 +01:00
José Valim 2c1c504fc5 Expand ^var 2013-12-20 11:26:50 +01:00
José Valim 625a27fac2 Implement pending tests 2013-12-20 11:18:28 +01:00
José Valim 5164073bf0 Handle remote calls 2013-12-20 11:09:01 +01:00
Eric Meadows-Jonsson 66dd76e42b Fix quote docs
Closes #1949.
2013-12-20 01:02:57 +01:00
José Valim bb05011dca Expand quote 2013-12-19 20:42:33 +01:00
José Valim c296e62543 Expand tuples and other special forms 2013-12-19 15:58:13 +01:00
José Valim 8847f8ed68 Start locals expansion 2013-12-19 15:33:13 +01:00
vanstee b536a2ac7c Write in-depth docs on how to use filters as flags 2013-12-18 14:58:47 -05:00
José Valim a33cbbe6b1 Simplify import dispatching 2013-12-18 17:24:32 +01:00
Eric Meadows-Jonsson afb7efd11b Add raw string compare in doctests for #Module<...>
Will now do raw string compare for doctests of #Module<...> format:

    iex> HashDict.new a: 0, b: 1, c: 2
    #HashDict<[a: 0, b: 1, c: 2]>
2013-12-18 15:52:23 +01:00
José Valim 613298c4ab Add variables expansion 2013-12-18 12:32:40 +01:00
José Valim d39401e2d3 Start expanding aliases on their own 2013-12-18 10:40:42 +01:00
vanstee a5602a71fa Update docs to define include and exclude filters 2013-12-17 01:48:41 -05:00
vanstee 0bd45a090a Display include and exclude filters separately 2013-12-17 01:39:55 -05:00
vanstee 68f570c0a5 Fix bug in applying multiple filters 2013-12-17 00:08:42 -05:00
vanstee f0456b6377 Refactor filter evaluation
Thanks for the help @josevalim and @christhekeele
2013-12-17 00:05:03 -05:00
José Valim dfeeeacb8c Revert "Merge pull request #1940 from vanstee/travis-r16b03"
Travis does not support R16B03 yet.
2013-12-16 21:42:45 +01:00
José Valim 72d683a86a Merge pull request #1940 from vanstee/travis-r16b03
Run tests against erlang R16B03
2013-12-16 12:24:04 -08:00
vanstee ee337bbfb8 Run tests against erlang R16B03 2013-12-16 15:04:17 -05:00
vanstee d4729a3795 Add support for include and exclude flags 2013-12-16 14:48:37 -05:00
José Valim 352a2d8851 Merge pull request #1938 from jwarwick/doc_typos
Fixed doc typos in Kernel and String
2013-12-16 00:13:17 -08:00
vanstee c7da787265 Support separate inclusion and exclusion filters 2013-12-15 21:12:27 -05:00
José Valim 159ba856a6 Bump to v0.12.1-dev 2013-12-15 23:16:05 +01:00
José Valim 6d3624147e Release v0.12.0 2013-12-15 23:01:14 +01:00
José Valim f1e647f04b Update CHANGELOG 2013-12-15 22:14:38 +01:00
José Valim 90d72c6a5e Support + for lists in ANSIDocs too 2013-12-15 22:12:14 +01:00
José Valim 5ce7ee2e3b Ensure arithmetic constants are supported in function heads 2013-12-15 22:10:33 +01:00
José Valim c1bcf5dab2 Support - lists in ANSIDocs, closes #1937 2013-12-15 22:02:57 +01:00
José Valim 11ab91c0fb Merge pull request #1928 from ericmj/typespec-kw-guard
Change typespec guards to keyword list
2013-12-15 12:48:32 -08:00
John Warwick 6c84492618 Fixed doc typos in Kernel and String 2013-12-15 14:19:07 +01:00
José Valim 91dc65b7ee Merge pull request #1935 from jwarwick/regex_typo
Added links to Regex docs
2013-12-15 00:41:49 -08:00
John Warwick 2ec9c24b3e Added links to Regex docs 2013-12-14 23:51:31 +01:00
vanstee eab8b46879 Support filters for multiple tags 2013-12-14 13:34:38 -05:00
vanstee 67e0367987 Match filters with a case 2013-12-14 13:33:21 -05:00
Guillaume Grenet cd43f618e0 CLI/Compile: added a test to showcase the new behaviour 2013-12-14 18:45:56 +08:00
Guillaume Grenet 3687b0f104 CLI/compile: fail when some patterns aren't matched 2013-12-14 18:37:35 +08:00
José Valim b1d245651d Merge pull request #1932 from pminten/correct-stream-take-every-spec
Correct the function name in Stream.take_every spec
2013-12-14 02:13:51 -08:00
Peter Minten 020a37785a Correct the function name in Stream.take_every spec 2013-12-14 11:12:53 +01:00
José Valim 14b3dff8e7 Merge pull request #1930 from jwarwick/stream_docs
Cleaning up Stream docs
2013-12-13 11:28:56 -08:00
John Warwick 1567af2f12 Cleaning up Stream docs 2013-12-13 18:44:47 +01:00
José Valim 35f599d184 Merge pull request #1927 from bitwalker/master
Add Float.round function
2013-12-13 09:19:44 -08:00
José Valim c361e0a8a6 Ensure suspensions are closed on failures 2013-12-13 18:11:43 +01:00
Paul Schoenfelder 38c60776f6 Add Float.round function
Float.round allows rounding floats to arbitrary precision, as well
as specifying how midpoint rounding is performed (up/down).
2013-12-13 09:24:59 -06:00
Eric Meadows-Jonsson 9bedb0faaf Change typespec guards to keyword list
Previous syntax:
@spec fun(a) :: b when is_var(a) and is_subtype(b, [a])
New syntax:
@spec fun(a) :: b when a: var, b: [a]
2013-12-13 13:12:57 +01:00
Eric Meadows-Jonsson 965915bffa Dont add [] for last arg keyword lists in tuples, lists and when/2 2013-12-13 13:12:43 +01:00
Eric Meadows-Jonsson a711bef57f Dont require [] for keyword list operand in when/2 2013-12-13 13:12:25 +01:00
José Valim a736b55499 Update CHANGELOG and remove warnings 2013-12-13 11:58:38 +01:00
José Valim 8720c56291 Ensure c helper works with full path 2013-12-13 11:43:20 +01:00
José Valim 2c5650ca9d Merge pull request #1926 from jwarwick/enum_docs
Fixing typos in Enum/Enumerable docs
2013-12-13 01:15:02 -08:00
John Warwick f3ae3b0df5 Fixing typos in Enum/Enumerable docs 2013-12-12 23:07:56 +01:00
José Valim 37766232bf Merge pull request #1925 from jwarwick/changelog_typos
Fixed typo in CHANGELOG
2013-12-12 13:17:26 -08:00
José Valim 5e8717918c Merge pull request #1924 from bitwalker/float-funcs
Add floor and ceil functions to Float
2013-12-12 13:16:33 -08:00
Paul Schoenfelder d624ceb72b Add floor and ceil functions to Float 2013-12-12 15:05:00 -06:00
John Warwick fcb5076e42 Fixed typo in CHANGELOG 2013-12-12 22:01:46 +01:00
José Valim 067bdde570 Deprecate Enum.chunks* in favor of Enum.chunk* 2013-12-12 20:42:43 +01:00
José Valim fcad4a1343 Add bang to stream_to! entry in CHANGELOG 2013-12-12 18:04:27 +01:00
José Valim cf0dd31714 Add Stream.after/2, Stream.run/2 and File.stream_to!/3 for side effects
Closes #1831
2013-12-12 17:41:05 +01:00
José Valim e3e75e5720 Ensure Stream.zip/2 and Stream.flat_map/2 follow up suspended calls 2013-12-12 16:30:26 +01:00
José Valim 5369f2449c Ensure Stream.resource/3 is zippable 2013-12-12 15:35:54 +01:00
José Valim a7dae16877 Automatically enforce build path for in_dependency 2013-12-12 14:31:04 +01:00
José Valim eb46fe30a2 -S cannot be given more than once 2013-12-12 11:55:25 +01:00
José Valim aa1b678b54 Improve URI and Stream docs 2013-12-12 11:31:58 +01:00
José Valim e53f75f2b9 Merge pull request #1920 from jwarwick/uri_doctest
Added URI to doc_test list
2013-12-12 02:21:01 -08:00
José Valim 538b830c3e Update record and stream docs (tks @pragdave) 2013-12-12 11:20:39 +01:00
José Valim cb32338cae Merge pull request #1922 from elixir-lang/jv-iteratees
Migrate Enumerable to use an iteratees based solution
2013-12-12 02:19:55 -08:00
José Valim cf9284f057 Update CHANGELOG 2013-12-11 15:12:55 +01:00
José Valim 3ac57066ce Add Stream.scan/2 and Stream.scan/3 2013-12-11 15:01:28 +01:00
José Valim db19cdea42 Add Stream.uniq/2 2013-12-11 15:01:28 +01:00
José Valim 2d7965b7e6 Add Stream.chunks/2,3,4 2013-12-11 15:01:28 +01:00
José Valim 4a2d1d6555 Add Stream.chunks_by/2 2013-12-11 15:01:28 +01:00
José Valim 1b93904a27 Support Stream.drop/2 with negative count 2013-12-11 15:01:28 +01:00
José Valim 303789fc93 Support Stream.take/2 with negative count 2013-12-11 15:01:28 +01:00
José Valim 3c6417896e Add Stream.take_every/2 2013-12-11 15:01:28 +01:00
José Valim 23a4fc65fe Add Stream.each/2 2013-12-11 15:01:28 +01:00
José Valim 575e0b9494 Start sharing reducers in between Enum/Stream 2013-12-11 15:01:28 +01:00
José Valim e49998593e Change Enumerable.count/1 and Enumerable.member?/2 to return tagged tuples
The motivation behind this change is to not force every application
to implement count/1 and member?/2 manually. It also allows us in
the future to add functions like Enum.counted?/1 that will return
true if count is pre-calculated.
2013-12-11 15:01:27 +01:00
José Valim 33c6eb3e37 Ensure certain streams and enumerables are zippable 2013-12-11 15:01:27 +01:00
José Valim 405c14f7e1 Add Stream.zip/2 2013-12-11 15:01:27 +01:00
José Valim b26304fa59 Improve docs 2013-12-11 15:01:27 +01:00
José Valim 1c834e8938 Make the reducer function part of the suspended continuation 2013-12-11 15:01:27 +01:00
José Valim 8e51528dc1 Convert to new iteratee-based enumeration
Details are described in the Enumerable docs.
This new implementation allows us to implement interleaving
functions (like zip) while avoiding a huge penalty in regular
enumeration.
2013-12-11 15:01:27 +01:00
José Valim 898e01ccb5 Merge pull request #1921 from jwarwick/capture_io_docs
Cleaning up ExUnit.CaptureIO docs
2013-12-11 03:38:19 -08:00
John Warwick 24725090f5 Cleaning up ExUnit.CaptureIO docs 2013-12-11 12:20:04 +01:00
John Warwick cef74d0186 Added URI to doc_test list 2013-12-10 17:15:15 -05:00
José Valim 4e2a1230f3 Merge pull request #1919 from meh/master
Fix Version.match? with ~> and -dev versions
2013-12-10 09:01:54 -08:00
meh a8959f3987 Fix Version.match? with ~> and -dev versions 2013-12-10 17:51:39 +01:00
José Valim bce9116cff Ensure full alias is stored in context_modules 2013-12-10 15:15:28 +01:00
José Valim b78c3bc36b Add Stream.resource/3 2013-12-09 18:14:34 +01:00
José Valim 94be8a1b8a Merge pull request #1917 from alco/simpler-translate_assert
Simplify the logic of translate_assert
2013-12-09 07:04:51 -08:00
Alexei Sholik c19ea55ed7 Simplify the logic of translate_assert
There is no visible need to pass the fallback function through all the
cases of translate_assert. It's makes the code simpler if we keep
the conditional logic at the beginning.
2013-12-09 16:28:12 +02:00
José Valim 34c2c50f42 Merge pull request #1916 from augiedb/master
Add examples to URI
2013-12-08 00:32:56 -08:00
Augie De Blieck Jr. 6f219ad1da Clean up formatting error 2013-12-07 23:22:03 -05:00
Augie De Blieck Jr. 2487d741b6 Add examples to url.ex
Examples for encode_query, encode, decode, decode_query, query_decoder, and parse.
2013-12-07 23:20:18 -05:00
José Valim f62c26b9b6 Ensure record aliases in the same quote can be accessed, closes #1915
This commit also simplifies macro expansion for both imports and aliases
to always give higher preference to the macro environment rather than
the caller one, if such is available. The deal is to simplify both the
code and understanding of how macros work.
2013-12-07 20:01:11 +01:00
José Valim 1d7c56d423 Merge pull request #1914 from augiedb/patch-1
A series of minor Enum description text changes for clarity and consistency.
2013-12-06 02:26:21 -08:00
Augie De Blieck Jr. 827507603a A series of minor description text changes for clarity and consistency.
Lines 909 and 259 were missing words. Other changes are made for minor grammatical clarity and consistency.
2013-12-05 22:56:10 -05:00
José Valim 0977602647 Create consolidation directory before writing to it 2013-12-04 12:02:22 +00:00
José Valim ee86a5c1e0 Merge pull request #1911 from mururu/fix-pry
Use :elixir_env.ex_to_scope/1 insted of :elixir_scope.to_erl_env/1
2013-12-04 00:07:26 -08:00
Yuki Ito c4ffe1b8b3 Use :elixir_env.ex_to_scope/1 insted of :elixir_scope.to_erl_env/1 2013-12-04 11:19:17 +09:00
José Valim eaebcf77f3 Merge pull request #1912 from jwarwick/macro_docs
Cleaning up Macro docs
2013-12-03 16:34:58 -08:00
John Warwick fa56d91c08 Cleaning up Macro docs 2013-12-03 14:54:45 -05:00
vanstee 04623a6edf Always run tests without matching tags 2013-12-03 02:35:11 -05:00
vanstee a3afaeec8e Reverse filter match happy path to make more sense 2013-12-03 01:57:22 -05:00
vanstee 5d7a1cf7a0 Support exclusion of any tag key-value pair 2013-12-03 01:35:51 -05:00
vanstee 4e29f274c5 Move filter parsing logic to a more testable place 2013-12-02 20:28:25 -05:00
vanstee 05a321dc8c Ensure filter is always a list 2013-12-02 20:13:51 -05:00
vanstee 7818551e6a Don't hold on to the filter after printing it 2013-12-02 19:51:41 -05:00
José Valim 5dfa6ae618 Ensure macro expansion does not expand on variables 2013-12-02 13:00:41 +01:00
vanstee 9b6f104ffe Clean up mix test options wrangling 2013-12-02 02:30:39 -05:00
vanstee 80118f4561 Parse filters in a more sane way 2013-12-02 02:13:12 -05:00
José Valim 1fbb6871a0 Add more hygiene examples 2013-12-02 06:53:44 +01:00
vanstee 474b05e5c4 Override test helper with command line flags 2013-12-01 23:15:32 -05:00
vanstee 7d1e6b545d Accept and parse filter command line option 2013-12-01 23:15:31 -05:00
vanstee 01e31cf3c3 Filters apply to tests instead of cases 2013-12-01 23:15:31 -05:00
vanstee 640cc06d63 Fix warning about unused variable 2013-12-01 23:11:22 -05:00
vanstee b2f97f5406 Don't count tests that were skipped 2013-12-01 23:11:22 -05:00
vanstee d510356f20 Skip tests with a mismatched filter 2013-12-01 17:34:44 -05:00
vanstee cb6753742b Print formatted filters before running tests 2013-12-01 17:34:44 -05:00
vanstee 73253206f2 Add helper function to format filters 2013-12-01 17:34:44 -05:00
vanstee 90f9162e3f Write test exercising case filtering 2013-12-01 17:34:44 -05:00
José Valim c74f203c0d Merge pull request #1907 from mururu/travis
Add R16B02 and R16B01 to .travis.yml
2013-12-01 07:42:35 -08:00
Yuki Ito afa7eee4ad Add R16B02 and R16B01 to .travis.yml 2013-12-02 00:04:13 +09:00
José Valim 228b20eb7c Merge pull request #1906 from mprymek/uri-doc-fix
Fixed docstrings in URI
2013-12-01 01:35:09 -08:00
Miroslav Prymek 60c4ede916 Fixed docstrings in URI 2013-12-01 00:28:18 +01:00
José Valim 85e2845fd8 Merge pull request #1905 from jwarwick/exception_docs
Cleaning up Exception docs (and other Kernel doc typos)
2013-11-30 11:47:30 -08:00
John Warwick c1bedd5dce Cleaning up Exception docs (and other Kernel doc typos) 2013-11-30 14:31:42 -05:00
José Valim e424f02812 Ensure aliases defined in macros do not leak 2013-11-30 18:02:46 +01:00
José Valim 7dd9da12a7 Promote exception/1 as the best mechanism to customize exceptions
The previous mechanism of lazily customizing message/1 was very
dangerous, as you could have errors while calculating the message
and those would shop up just when the exception is printed, leading
to further breakage.

Closes #1747
2013-11-30 11:59:51 +01:00
José Valim 67109b291d Ensure path dependencies manifests point to the proper path
Closes #1888
2013-11-30 10:02:07 +01:00
José Valim bee0e28e5b Update CHANGELOG 2013-11-30 09:40:35 +01:00
José Valim 9ee79a16bb Deprecate Macro.extract_args/1 2013-11-29 23:24:44 +01:00
José Valim bbc0fe0a63 Slightly refactor and improve doctests code 2013-11-29 23:23:06 +01:00
José Valim 96077905fb Require :escript_main_module for generating escripts
escripts must be opt-in because developers should not assume
they can simply generate escripts for all projects. This change
is a small one but makes it clear the desire for the project to
be escriptized.

Closes #1845
2013-11-29 22:37:48 +01:00
José Valim 00431ebecb Warnings rely on stdio and should be sync 2013-11-29 22:37:48 +01:00
José Valim 203ddd0c37 Merge pull request #1837 from zambal/decompose_call
Add Macro.decompose_call/1
2013-11-29 13:37:40 -08:00
José Valim ab91165946 Merge pull request #1903 from krestenkrab/krab-regex-repeat
More precise regex exception match [for erjang]
2013-11-29 05:51:44 -08:00
Kresten Krab Thorup 6a7609d707 test cleanup as per comments 2013-11-29 14:30:10 +01:00
Kresten Krab Thorup ec6f95d970 More precise regex exception match
This is for Erjang, which generates different
exception messages
2013-11-29 13:41:39 +01:00
José Valim 0e54cad8ee Merge pull request #1901 from ericmj/defrecordp-dynamic
defrecordp support dynamic name and fields
2013-11-29 03:50:37 -08:00
José Valim c093e378d8 Merge pull request #1900 from krestenkrab/krab-unicode
In response to "The string type is broken"
2013-11-29 03:49:18 -08:00
José Valim a829da1775 Reduce the size of String.Graphemes module
Based on a suggestion @krestenkrab and an initial implementation
proposed in #1902, we can reduce the total size of the String.Graphemes
beam file by ensuring the function clauses for next_grapheme are
similar.

We have done this by implementing next_grapheme in a tail recursive
fashion and using binary_part to select the first bits. The end result
is a consistently 33% faster graphemes operation for ascii strings
and only 10% slower operation for heavy unicode strings (which may
be further optimazable).
2013-11-29 12:48:01 +01:00
Eric Meadows-Jonsson 7d83083525 defrecordp support dynamic name and fields 2013-11-29 12:25:16 +01:00
José Valim a1f104cd30 Merge pull request #1899 from jwarwick/exunit_docs
Cleaning up ex_unit docs
2013-11-28 23:59:53 -08:00
John Warwick 895852f1d2 Removed hard links to modules. Relying on ExDoc to autolink 2013-11-28 23:48:34 -05:00
Eric Meadows-Jonsson 9a0e8f38de Remove record_test from eunit suite 2013-11-29 01:42:37 +01:00
Eric Meadows-Jonsson ffb2258bb1 @type and friends support unquote fragments 2013-11-28 23:01:00 +01:00
Kresten Krab Thorup d95a7d1a58 Fix comment 2013-11-28 16:46:17 +01:00
Kresten Krab Thorup 3a8d0ccbc2 Some more unicode tests
Since this blog post [1] caused some stir, I added
tests corresponding to how most languages referred
to are broken. Naturally, that's not the case for
Elixir.

[1] http://mortoray.com/2013/11/27/the-string-type-is-broken/
2013-11-28 14:35:09 +01:00
José Valim 4ed4f52121 Fix regression when using in with module attributes 2013-11-28 13:56:37 +01:00
John Warwick d416b700e3 Cleaning up ex_unit docs 2013-11-27 22:53:14 -05:00
José Valim d3055a9d2a Store tags in context, remove records from context 2013-11-27 13:03:38 +01:00
José Valim cb66331e87 Fix failed IEx test that depended on ExUnit types 2013-11-27 10:41:35 +01:00
José Valim 5263610aac Move test outcome to an explicit state 2013-11-27 10:34:46 +01:00
José Valim 5d53abc5ff Support ExUnit tags for customizing tests in callbacks 2013-11-27 09:38:53 +01:00
Eric Meadows-Jonsson 89c4822fa0 Add typespecs to List 2013-11-26 22:55:57 +01:00
Eric Meadows-Jonsson 1f4037b93c Add List.update_at/3 2013-11-26 22:55:57 +01:00
José Valim 660f8607a6 Merge pull request #1897 from gvaughn/chunks_fix
fix for issue #1896
2013-11-26 11:37:45 -08:00
Greg Vaughn 52cd9b359c fix for issue #1896
If the enumerable is an even multiple of the counter (n) and an explicit
pad is given, you get an extra element.

Enum.chunks([1,2,3], 3)
iex> [[1,2,3]]
as expected, but

Enum.chunks([1,2,3], 3, 3, [""])
iex> [[1,2,3],[""]]
as not expected
2013-11-26 13:33:48 -06:00
José Valim 127a634ba2 Ensure we have a project before popping the stack 2013-11-26 20:02:36 +01:00
José Valim 09869ec135 Improve typespecs when formatted as strings 2013-11-26 09:15:39 +01:00
José Valim 4e52765755 Merge pull request #1895 from jwarwick/change_typos
Fixing CHANGELOG typos
2013-11-25 07:32:10 -08:00
John Warwick 2b3ae52fa5 Fixing CHANGELOG typos 2013-11-25 10:27:40 -05:00
José Valim 63fdbfb8cf Implement Module.eval_quoted in Elixir 2013-11-25 12:59:14 +01:00
José Valim 5c00882bff Update CHANGELOG 2013-11-25 11:39:02 +01:00
José Valim 043edd4990 location: :keep only affect function definitions
This commit changes how metadata is stored once location: :keep
is used and also removes the __scope__ special form.

Closes #1877
2013-11-25 11:35:00 +01:00
José Valim fd3d8b3b98 Share quote annotations with unquote fragments 2013-11-25 09:06:01 +01:00
José Valim e0354005ea Simplify get_opt(internal) handling 2013-11-25 09:06:01 +01:00
José Valim da34ccd54e Simplify lookups in elixir_dispatch 2013-11-25 09:06:01 +01:00
José Valim 4460770981 Update kernel.ex 2013-11-24 14:19:51 +01:00
Yuki Ito 5782dae432 Fix error message of access
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-11-24 13:46:38 +01:00
Yuki Ito 494659ab1c Ensure that Macro.expand_once/2 works with HashDict
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-11-24 13:45:59 +01:00
José Valim eae9177b18 Merge pull request #1892 from vipulnsward/typos_1
Fix some typos
2013-11-24 04:45:27 -08:00
José Valim 08afaa7982 Merge pull request #1893 from elixir-lang/jv-ex-in-ex
This merge moves most of the macros implemented in erlang to elixir.

The exceptions are `case/2`, `try/2` and `receive/1` that cannot be written
in Elixir and therefore were converted to special forms.

On the plus side, `var!/1`, `var!/2` and `alias!/1` were converted to regular
macros (and demoted from special forms).

This also simplifies dispatching, as we no longer need to special case
some of the Kernel macros.
2013-11-24 04:44:42 -08:00
José Valim d7179b404b Port module_nesting handler to Elixir 2013-11-24 13:42:01 +01:00
José Valim be71239b7c Move more code to Elixir land, simplify serialization 2013-11-24 13:28:50 +01:00
Vipul A M d330d77c33 Fix some typos 2013-11-24 17:39:39 +05:30
José Valim a8bdd19a03 Implement defmodule in Elixir 2013-11-24 11:53:41 +01:00
José Valim 720d1e2a71 Refactor aliases handling before we move defmodule to Kernel 2013-11-24 09:33:38 +01:00
José Valim f685f5d2a5 Implement left in right in Elixir 2013-11-23 12:21:07 +01:00
José Valim 0a84b90373 Move super? to Elixir as well 2013-11-23 11:09:51 +01:00
José Valim f98cda71c0 Implement var! and alias! in Elixir 2013-11-23 10:54:40 +01:00
José Valim b28c95d31a Move case/try/receive to special forms 2013-11-23 10:35:10 +01:00
José Valim abec6af3ac Move function/macro definitions to Kernel 2013-11-23 10:17:57 +01:00
José Valim e802d2960b Pass context information for definitions in the first argument
As we are moving the information to Elixir, we can no longer
access it if it is in the definition node.
2013-11-22 23:28:53 +01:00
José Valim 7d26bfbfad Move remaining functions to elixir_env 2013-11-22 23:13:26 +01:00
José Valim 4e251b10b9 Move function definitions to new serialization schema 2013-11-22 22:56:08 +01:00
José Valim f8380e9dcd Get rid of unused _@MODULE var 2013-11-22 22:41:30 +01:00
José Valim c2243e2c28 Make module compilation work with elixir expressions 2013-11-22 21:41:59 +01:00
José Valim ffcde49975 Macro functions and macro macros to Macro.Env 2013-11-22 20:46:01 +01:00
José Valim f4144a63f8 Automatically handle File.stream and File.binstream based on modes 2013-11-22 16:51:06 +01:00
José Valim 2655a318c2 Move some macros back to __op__ to work around bug in erl_eval 2013-11-22 15:42:12 +01:00
José Valim e553b6780b Convert bitwise 2013-11-22 14:41:50 +01:00
José Valim 398e0fd3fb Convert remaining operators 2013-11-22 14:39:11 +01:00
José Valim de3c0f639a Move more operators to Kernel 2013-11-22 13:44:37 +01:00
José Valim 1096cd00b9 Move arithmetic ops to Kernel 2013-11-22 13:29:02 +01:00
José Valim 260be7c8ea Start porting elixir_macros to pure elixir 2013-11-22 12:55:51 +01:00
José Valim bed554cc50 Update release docs task 2013-11-21 17:24:03 +01:00
Eric Meadows-Jonsson f60fd6a5d4 Ensure test fixtures are kept clean 2013-11-21 16:46:51 +01:00
José Valim a6c19950a0 Merge pull request #1878 from pminten/intersperse
Enum.intersperse, change to join
2013-11-21 04:53:02 -08:00
Peter Minten e1ff60a57d Add Enum.intersperse 2013-11-21 13:45:41 +01:00
José Valim 5d324c0a6f Mention File.open/2 modes in File.write/3 2013-11-21 13:11:35 +01:00
José Valim 4b17932bc8 Properly .gitignore automatically generated rebar_dep/ebin 2013-11-21 12:42:46 +01:00
Eric Meadows-Jonsson fe3d1b1cfe Add Enum.reverse/2 2013-11-20 15:44:08 +01:00
José Valim 42c5b63ad7 Update typespecs in Inspect.Algebra 2013-11-19 10:34:35 +01:00
José Valim e65eee49c4 Make inspect in general 20% faster
There are two main optimizations:

1. We have optimized Module.concat/2 (it is roughly 100% faster)
2. We have optimized rendering of Inspect.Algebra by skipping
   the intermediate s_doc representation. This reduces memory
   allocation and skips one extra pass on rendering.
2013-11-19 10:21:31 +01:00
José Valim 03e1e8e203 Merge pull request #1886 from jwarwick/make_docs
Updated 'make docs' to latest ex_doc cli args
2013-11-18 22:58:03 -08:00
John Warwick a135e14348 Updated 'make docs' to latest ex_doc cli args 2013-11-18 21:50:44 -05:00
Julian Doherty (madlep) dbc8c7c607 remove .git suffix for :github referenced deps
the `:github` option fails if a partial path with `.git` appended is
provided.

Fixed this to provide an example that works (if the "barbat" repo actually existed)

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2013-11-18 15:29:10 +01:00
José Valim 404e8290b8 Improve EEx docs 2013-11-18 20:01:02 +08:00
José Valim eba273a72a Update CHANGELOG 2013-11-18 12:11:05 +01:00
José Valim 75a1f2edbf Tidy up list docs 2013-11-18 12:07:57 +01:00
José Valim 1bf4a95643 Merge pull request #1883 from alco/list-delete_at
Implement List.delete_at
2013-11-18 03:04:58 -08:00
Alexei Sholik 4358386906 Implement List.delete_at 2013-11-18 12:53:54 +02:00
José Valim f4bc50c20f Have a convenience function that does not change the lock in umbrella 2013-11-18 09:03:09 +01:00
Eric Meadows-Jonsson 985ce8e031 Switch Enum.drop/take behaviour for negative counts 2013-11-16 23:01:25 +01:00
José Valim 2b72bc08d7 Attempt to improve elixir version error message 2013-11-16 22:30:49 +01:00
José Valim fa348e5027 iterate -> enumerate 2013-11-16 22:24:59 +01:00
José Valim cfa2aad8b4 Merge pull request #1868 from ericmj/typespecs
Add is_var/1 to typespecs and move guards to end of spec
2013-11-16 00:27:12 -08:00
José Valim 8c145c42f3 Merge pull request #1882 from christhekeele/surround-many-separator
Make surround_many accept an optional seperator per-invocation, or fall ...
2013-11-16 00:26:07 -08:00
Chris Keele 2b6b48688e Make surround_many accept an optional seperator per-invocation, or fall back to module attribute default. Passes tests+doc_tests. 2013-11-15 16:40:09 -06:00
José Valim 9b0617f889 Also symlink include directory 2013-11-15 08:48:24 +01:00
José Valim fb5c963f17 Ensure symlink_or_copy can override previous symlinks 2013-11-15 08:42:07 +01:00
José Valim ff679125e6 Merge pull request #1880 from knewter/minor_doc_fix_on_dict
Minor consistency update to Dict.values doc
2013-11-14 23:30:31 -08:00
Josh Adams 91d7fa1f9c Minor consistency update to Dict.values doc
Adds a period to the end of the first line in the doc, for consistency
with the rest of the module
2013-11-14 17:20:41 -06:00
José Valim 8310d62499 Simplify how app files are generated and checked 2013-11-14 20:53:49 +01:00
José Valim c6a50d960e Improve coverage for streams with nested accs 2013-11-14 19:13:08 +01:00
José Valim bee44b382b Compose streams as we go and remove tuples nesting 2013-11-14 19:13:08 +01:00
José Valim ac28ed12b1 Make IEx task with @moduledoc false 2013-11-14 12:47:33 +01:00
José Valim c4d039da37 Start v0.11.3-dev 2013-11-14 12:46:07 +01:00
José Valim b70fc122f8 Release v0.11.2 2013-11-14 12:41:45 +01:00
José Valim 0d5bf3a904 Merge pull request #1876 from alco/before-compile-doc
Fix the example for @before_compile hook
2013-11-13 05:33:23 -08:00
Alexei Sholik 66019c36ca Fix the example for @before_compile hook 2013-11-13 15:10:18 +02:00
José Valim a73235a2de Merge pull request #1875 from pragdave/iex-c-error
Catch attempts to compile missing files in iex and give slightly tidier error messages
2013-11-12 23:54:26 -08:00
pragdave 718b111797 Catch attempts to compile missing files in iex and give slightly tidier error messages 2013-11-12 23:26:58 -06:00
José Valim fd57b5de4f Ensure build per environment in umbrella children do not clash 2013-11-13 00:11:30 +01:00
José Valim bb9569db0c Merge pull request #1874 from jwarwick/success2
Updated mix.new success wording
2013-11-12 15:10:29 -08:00
John Warwick 74c798585b Updated mix.new success wording 2013-11-12 17:54:55 -05:00
José Valim cfdcf685cb Do not access compile_path for umbrellas 2013-11-12 23:26:47 +01:00
José Valim 572726cf65 Remove unecessary apply optimization 2013-11-12 14:29:17 +01:00
José Valim 1885841df6 Update CHANGELOG 2013-11-12 14:07:56 +01:00
José Valim 80a466af35 Merge pull request #1871 from tonini/mix-iex-task
Add mix iex that simply fails and tell users to use iex -S mix
2013-11-12 05:02:26 -08:00
José Valim 83db533700 Merge pull request #1872 from elixir-lang/jv-_build
Compile projects to _build
2013-11-12 05:02:00 -08:00
José Valim 49fe9b05a0 Copy dev build if the environments match 2013-11-12 12:56:43 +01:00
José Valim c8425edb6d Do not clean whole build on clean, only libs 2013-11-12 11:25:41 +01:00
José Valim b9cb17ed39 deps.loadpaths no longer loads the dependencies, it relies simply on builds 2013-11-12 11:02:59 +01:00
José Valim 5d48abb9ab Ensure we can clean deps that no longer exists 2013-11-12 10:55:37 +01:00
José Valim 1403350be0 Prune tasks when build per environment is on 2013-11-12 10:36:37 +01:00
José Valim df997f836b Allow builds to be customized per environment 2013-11-12 10:14:25 +01:00
Samuel Tonini 4544ab59cf Add mix iex that simply fails and tell users to use iex -S mix 2013-11-12 10:09:44 +01:00
José Valim 7614b6c1d0 Rename _build to _build/shared 2013-11-12 09:57:01 +01:00
José Valim 1da1f8c203 Update new applications .gitignore 2013-11-12 09:51:15 +01:00
José Valim 0addc221e0 Improve coverage for builds and rebar integration 2013-11-12 09:50:30 +01:00
Eric Meadows-Jonsson f6a2046850 Keep debug info for unicode files
Dialyzer stops processing when it finds a .beam file without debug info.
Only increases file size with ~120kb in total.
2013-11-11 21:21:44 +01:00
José Valim c345e51772 Ensure rebar dependencies are stored in the proper directory 2013-11-11 20:15:31 +01:00
José Valim 75e22706fc Share _build path when running commands from umbrella 2013-11-11 18:47:58 +01:00
José Valim eac9929328 Move rebar and other deps to _build 2013-11-11 18:14:16 +01:00
José Valim e1aead5ba3 Ensure archives work with new _build setup 2013-11-11 15:29:18 +01:00
José Valim 6f5d4d04fd Sinplify mechanism for checking and handling locks
In particular, there is no need to pass :lockfile around deps.
We also remove the umbrella handling from the lock.
2013-11-11 13:54:54 +01:00
José Valim 13eab25e6d Rename fetched to loaded and add a manifest_path 2013-11-11 13:40:05 +01:00
José Valim 2854fec36a Move mix dependencies to _build 2013-11-11 13:19:10 +01:00
Eric Meadows-Jonsson f125d19854 Move typespec guards to end of spec
Moves precedence of `when` to be lower than `::`.
2013-11-11 10:08:24 +01:00
Eric Meadows-Jonsson 1ce2e24f5d Add is_var/1 for use in typespec guards
Allows you to specify a variable without any type contraints.
2013-11-11 10:08:24 +01:00
José Valim 48208066b7 Merge pull request #1867 from christhekeele/fix-macro-env-doc-typo
s/wether/whether
2013-11-11 00:18:33 -08:00
Chris Keele 4914c4982d s/wether/whether 2013-11-10 18:06:23 -06:00
José Valim 14a562f260 Compile not ok dependencies on deps.get and deps.update 2013-11-10 21:13:24 +01:00
José Valim dd91696184 Do not override builtin type 2013-11-10 20:15:44 +01:00
José Valim 5c55f0fe91 Move mix configs to app environment and split TasksServer 2013-11-10 20:03:13 +01:00
José Valim a6474aecd4 Extract Mix.ProjectStack from Mix.Server 2013-11-10 19:05:40 +01:00
José Valim 5844e29443 Ensure app key is required when getting the compile_path 2013-11-10 15:33:34 +01:00
José Valim 38f7d709d1 Merge pull request #1866 from parroty/fix
Add :++, :-- and :** to Macro#binary_op_props
2013-11-10 05:38:14 -08:00
parroty 4931629b51 Add :++, :-- and :** to Macro#binary_op_props 2013-11-10 22:25:42 +09:00
José Valim 343d9ef042 Remove :compile_path from config and move it to Mix.Project 2013-11-10 13:02:20 +01:00
José Valim 703c7dfd21 Ensure Mix properly recurs rebar subdirs, closes #1862 2013-11-10 11:38:34 +01:00
José Valim 082af1c417 Reduce the amount of fixture files in mix 2013-11-10 11:37:17 +01:00
José Valim dc9caf4260 Merge pull request #1863 from philss/remove-mix-test-warning
Remove unused variables from mix test file
2013-11-08 23:22:03 -08:00
Philip Sampaio f7fd253918 Remove unused variables from mix test file 2013-11-09 01:42:18 -02:00
José Valim e6be0628ce Start v0.11.2-dev 2013-11-07 17:18:23 +01:00
José Valim 7159da5780 Release v0.11.1 2013-11-07 16:54:19 +01:00
José Valim 5dab337685 Ensure Mix.Shell.IO do not puke on :eof 2013-11-06 21:24:28 +01:00
José Valim 12f113f6f8 Merge pull request #1860 from jwarwick/warnings
Fixed warning message.
2013-11-06 11:18:04 -08:00
John Warwick c1367548dd fix typo in changelog 2013-11-06 14:11:51 -05:00
John Warwick e40375a65f underscorize unused argument 2013-11-06 14:11:30 -05:00
José Valim a6bcb014ec Merge pull request #1859 from josephwilk/var_doc
Improving docs on var!
2013-11-06 09:29:34 -08:00
josephwilk d3a526ebf0 Improving docs on var!
Remove implementation details about variable name.
Try and make it clear it is just unquoting a var.
2013-11-06 18:25:04 +01:00
José Valim eb8fba694c Skip remote call on dict behaviour size 2013-11-06 16:21:49 +01:00
José Valim 0d3aa7fd66 Improve some Kernel error messages 2013-11-06 16:14:31 +01:00
José Valim ca7f1fee5a Remove optional parameter on __using__ 2013-11-06 15:36:21 +01:00
José Valim 55ecdba77c Make test that changes global state sync 2013-11-06 15:00:00 +01:00
José Valim 53f5473b62 Update CHANGELOG 2013-11-06 12:35:17 +01:00
José Valim 4eb9cd3f59 Improve docs and error messages 2013-11-06 12:31:55 +01:00
Eric Meadows-Jonsson bf9e190561 Optional deps should be kept for the project that specifies them 2013-11-05 22:11:02 +01:00
José Valim 0f6459e50d Merge pull request #1855 from alco/elixirappsrc
Mark the autogenerated lib/src/elixir.app.src as such
2013-11-05 12:34:13 -08:00
José Valim 30b24b7e2d Merge pull request #1856 from ToJans/patch-1
Allow compilation in R17A
2013-11-05 12:33:30 -08:00
José Valim 7aa5a89396 Add support for optional dependencies 2013-11-05 21:14:27 +01:00
José Valim 3ede5ff7f9 Store only app names in Mix.Dep.deps 2013-11-05 21:14:27 +01:00
José Valim 2f2f2e0a58 Dependencies convergence properly relies on requirements
Previously Mix required dependencies to have exactly the same
requirements but now we just check all requirements satisfy the
current version.
2013-11-05 21:14:27 +01:00
Eric Meadows-Jonsson d20190c99a Allow lower case escapes in URI
See RFC 2396 2.4.1
Closes #1857
2013-11-05 19:07:20 +01:00
Tom Janssens 57b5319ae8 Allow compilation in R17A
Allow elixir to be compiled using R17A as well
2013-11-05 15:15:51 +01:00
Alexei Sholik ecd95a8929 Mark the autogenerated lib/src/elixir.app.src as such 2013-11-05 15:12:56 +02:00
José Valim f65ef1dd67 Remove duplication in deps.compile 2013-11-05 10:32:10 +01:00
Vincent Siliakus 5cf7f22b70 Renamed Macro.split_call/1 back to Macro.decompose_call/1 2013-11-05 10:16:16 +01:00
José Valim 356b74db01 Do not allow duplicated projects to be pushed onto the stack
Closes #1846
2013-11-05 10:15:51 +01:00
José Valim 94579848cc Move unicode from priv/ to unicode/ 2013-11-05 08:17:33 +01:00
José Valim e0c312bdc7 Add a test case ensuring managers are not set eagerly 2013-11-04 20:33:53 +01:00
José Valim 523b7f1f4d Do not fetch the manager before the repo is available 2013-11-04 20:13:05 +01:00
José Valim d436c9728a No need to support @recursive :both anymore 2013-11-04 18:24:28 +01:00
José Valim a917c6c0f8 Add a workaround for Mix deps.get and deps.umbrella what was not loading path deps
This will be properly fixed in a couple days once we move
to _build directories.
2013-11-04 18:00:06 +01:00
José Valim 8c57450421 Check the elixir req for dependencies too
Also streamline the workflow for deps.get and deps.update.
2013-11-04 17:46:28 +01:00
José Valim e999642a8e Improve syntax error on invalid calls
Closes #1847
2013-11-04 12:32:17 +01:00
José Valim 773d4b0a2e Improve error message for overriden deps 2013-11-04 12:22:59 +01:00
José Valim a79bdd5cdd Revert "Fix status code in mix test"
This reverts commit 4fc3fc11de.

We want to run all mix umbrella projects together, because
all their apps are booted together and we should not pretend
otherwise.
2013-11-04 11:46:14 +01:00
José Valim e3a6316a6a Precalculate the manager for unfetched dependencies 2013-11-04 11:27:35 +01:00
José Valim 3b6eafe833 Just skip loadpaths on deps.update and deps.get 2013-11-04 10:53:02 +01:00
José Valim 1db68b4d71 Update CHANGELOG 2013-11-04 08:38:57 +01:00
José Valim 8ee87b9eda Move topsorting to Mix.Deps.Umbrella 2013-11-04 07:42:14 +01:00
Yurii Rashkovskii a6410935c7 Remove some unused variables 2013-11-03 13:35:12 -08:00
José Valim 1ea2fe28e0 Improve exception messages in case of missing app 2013-11-03 22:32:52 +01:00
José Valim c7781e2e58 Oops, rename children to unfetched also in umbrella 2013-11-03 21:58:40 +01:00
José Valim 69792f36e0 Topsort umbrella children 2013-11-03 21:54:31 +01:00
José Valim 62780dc54a Simplify protocol code 2013-11-03 21:37:46 +01:00
José Valim f6e3b424c4 Topsort mix dependencies 2013-11-03 19:59:44 +01:00
José Valim a3021f49e7 Ensure umbrella projects sets the proper children environment 2013-11-03 19:17:57 +01:00
José Valim 3dbf899000 Add a test case for overriden deps with cycles in umbrellas 2013-11-03 19:04:44 +01:00
José Valim 8bbc42bfda Insert overriden dependency before any of its dependencies 2013-11-03 18:27:55 +01:00
José Valim dcf6ca1003 Ensure LexicalTracker doesn't fail accross lexical contexts 2013-11-03 18:02:03 +01:00
José Valim 70f42e22bf Add more information to generated mix.exs 2013-11-03 18:02:03 +01:00
José Valim ac91c25e21 Merge pull request #1841 from tonini/compilation_status_parallel_compilation
use a separat compilation status per parallel compilation
2013-11-03 08:30:43 -08:00
Samuel Tonini 427e5ed712 use a separat compilation status per parallel compilation 2013-11-03 16:52:56 +01:00
José Valim b84399fda0 Wrap formatted results in quotes
This improves the readability and also allows us to transparently
handle regexes that may come from rebar.
2013-11-03 16:26:48 +01:00
José Valim 400f1522c7 Properly format test cases names in --trace headers 2013-11-03 13:50:58 +01:00
José Valim 3d81da1951 Update String docs 2013-11-03 13:35:57 +01:00
José Valim b03f069861 Set the manager before going into the dep 2013-11-03 13:18:01 +01:00
José Valim 19e853e895 Remove more deprecated code 2013-11-03 12:56:02 +01:00
José Valim 350b01de82 Remove deprecated code 2013-11-03 12:22:45 +01:00
José Valim 69c25b7628 Improve docs for Mix.Archive 2013-11-03 12:16:42 +01:00
José Valim c67fba27f2 Deprecate :load_paths in preparation to Mix' _build 2013-11-03 12:07:56 +01:00
José Valim c822b920e0 Add tests covering a bad mix.exs file is not loaded on deps.get/deps.update 2013-11-03 11:43:45 +01:00
José Valim bc55eeb054 Do not load the dependency on check lock but only when it is fetched 2013-11-03 11:43:45 +01:00
José Valim d776493967 Split Mix.Deps into fetched and unfetched
Previously, we were fetching/loading the dependencies
before calling deps.update, that caused issues because
the updated version tried to use the load mixfile cache,
often failing to compile.
2013-11-03 11:43:45 +01:00
José Valim 6b89a9c726 Merge pull request #1842 from mururu/typo
Fix typo in CHANGELOG
2013-11-02 23:55:10 -07:00
Yuki Ito f41aac7b32 Fix typo in CHANGELOG 2013-11-03 14:43:47 +09:00
José Valim c5cf8a3470 Start v0.11.1-dev 2013-11-02 10:33:27 +01:00
José Valim 83ceee54b3 Release v0.11.0 2013-11-02 10:19:17 +01:00
José Valim b7a33832a5 Merge pull request #1838 from jwarwick/doc_typos
Updated defexception docs
2013-11-01 21:36:32 -07:00
John Warwick ef76cadcb2 Updated defexception docs 2013-11-01 22:54:33 -04:00
José Valim ebb989d3ed Update CHANGELOG 2013-11-01 22:06:16 +01:00
José Valim 00eede696e Remove unused alias 2013-11-01 22:03:46 +01:00
José Valim 3a94bab1bb Get rid of useless fourth argument in defexception 2013-11-01 22:03:33 +01:00
José Valim 2564635548 Document side-effects on stream APIs 2013-11-01 20:55:31 +01:00
José Valim 657054116b Add typespecs and to_string conversion to some IO APIs 2013-11-01 20:53:14 +01:00
Vincent Siliakus 7af9f8a976 Updated documentation for Macro.split_call/1 2013-11-01 17:32:29 +01:00
Vincent Siliakus fe5bfa0fe0 renamed Macro.decompose_call/1 to Macro.split_call/1 2013-11-01 17:17:59 +01:00
José Valim b7da298d02 Do not write manifest down if there was a compilation error, closes #1833 2013-11-01 16:31:47 +01:00
José Valim a75e70b856 Avoid interpolation which would convert char lists twice 2013-11-01 16:03:13 +01:00
José Valim aba0d5ba3f Simplify current module macro handling 2013-11-01 16:03:13 +01:00
José Valim d334c1d6e4 Add tests for LexicalTracker 2013-11-01 16:03:13 +01:00
José Valim d856e667b9 Add warnings for unused aliases 2013-11-01 16:03:12 +01:00
José Valim 5bd12f9cb9 Move lexical tracker to scope 2013-11-01 16:03:12 +01:00
José Valim 3c151d5357 Add Kernel.LexicalTracker 2013-11-01 16:03:12 +01:00
José Valim d92175ae41 Merge pull request #1834 from tonini/fix-wrong-error-message
fix wrong error message for `nil.func()` call
2013-11-01 06:06:11 -07:00
Samuel Tonini 2e9f4196cd fix wrong error message for nil.func() call
The expected behavior for `nil.func()` should be:

    iex(1)> nil.test()
    ** (UndefinedFunctionError) undefined function: nil.test/0
       nil.test()

Also via cli `elixir -e`:

     $ elixir -e 'nil.test()'
     ** (UndefinedFunctionError) undefined function: nil.test/0
     nil.test()
     ....

Undefined function calls behave like:

    iex(1)> hello()
    ** (RuntimeError) undefined function: hello/0
2013-11-01 13:55:33 +01:00
Vincent Siliakus 97bf67d8db Add Macro.decompose_call/1 2013-11-01 12:45:59 +01:00
José Valim 3f8363c019 Merge pull request #1835 from jwarwick/code_docs
Updated Code docs
2013-11-01 02:04:54 -07:00
John Warwick 98d791acb4 Updated Code docs 2013-10-31 22:15:39 -04:00
Samuel Tonini 0134a8f394 remove whitespace 2013-10-31 21:55:39 +01:00
José Valim 3d6071d640 Update CHANGELOG 2013-10-31 11:36:05 +01:00
José Valim 821b7c6f76 Ensure Enum.member? uses match (===) 2013-10-31 11:36:05 +01:00
José Valim 9897b894be Deprecate x in y inside matches 2013-10-31 11:36:05 +01:00
José Valim 90af53c070 Merge pull request #1830 from jwarwick/behaviour_docs
Doc updates for Application.Behaviour and Behaviour
2013-10-30 22:35:59 -07:00
John Warwick 2566dd7e24 Doc updates for Application.Behaviour and Behaviour 2013-10-30 23:25:59 -04:00
José Valim a7e63ae1f6 Merge pull request #1829 from tonini/escape-underline-in-link-with-ansidoc
underlines within links have to be escaped
2013-10-30 13:41:32 -07:00
Samuel Tonini a91e1f2288 underlines within links have to be escaped
in links the underline will be escaped with the ansi escape
sequence code `4` (underline).
To avoid this behavior it have to be properly escaped.
2013-10-30 21:12:24 +01:00
José Valim 174f031efd Eval should take into account other contexts 2013-10-30 16:15:19 +01:00
José Valim f1e2fcf9f9 Same module variables must be explicitly shared
Prior to this commit, the following code would work:

    defmodule Hello do
      defmacro write do
        quote do
          a = 1
        end
      end

      defmacro read do
        quote do
          a
        end
      end
    end

    require Hello
    Hello.write
    Hello.read

Although this is convenient, it has many issues:

1. It is not clear at any point the intent of sharing the variables
   in between the macros

2. Whenever macros from the same module are nested, there is a chance
   a variable used before and after the nesting to be overriden

We address this issue by only allowing variables explicitly marked
with var! to be shared. Here is how the example above should be updated:

    defmodule Hello do
      defmacro write do
        quote do
          var!(a, Hello) = 1
        end
      end

      defmacro read do
        quote do
          var!(a, Hello)
        end
      end
    end

    require Hello
    Hello.write
    Hello.read
2013-10-30 16:15:19 +01:00
José Valim dd47d099c9 Swap variables to kernel context 2013-10-30 16:15:19 +01:00
José Valim 992ac67512 Improve coverage for location: :keep 2013-10-30 16:15:19 +01:00
José Valim 9ff328096b Merge pull request #1827 from pminten/unicode-default
Make unicode the default for regular expressions
2013-10-30 07:51:17 -07:00
Peter Minten 6fbc92b088 Make unicode the default for regular expressions 2013-10-30 15:39:41 +01:00
José Valim 2d1facb17b Merge pull request #1826 from tonini/fix-no-dot-completion
Completion of . (dot) terminates shell process
2013-10-30 03:04:18 -07:00
Samuel Tonini ad4c89714c prevent iex process termination when autocomplete on a single dot (.) 2013-10-30 10:56:27 +01:00
José Valim 7120a5e050 Update CHANGELOG 2013-10-30 08:47:07 +01:00
José Valim d0fd5ed7f0 Fix the build, we no longer fail when taking over itself 2013-10-30 08:32:05 +01:00
José Valim 0b624fff39 Allow IEx to take over itself 2013-10-30 08:24:51 +01:00
José Valim 5c4b8fb2cd Allow IEx take over while evaluating 2013-10-30 08:20:13 +01:00
José Valim 64296f114d Improve and/or operator docs 2013-10-30 08:20:13 +01:00
José Valim 8e7b86964e Merge pull request #1825 from jwarwick/clean
Added advice to clean when building into the README
2013-10-29 13:51:29 -07:00
John Warwick ef475b241b Added advice to clean when building into the README 2013-10-29 16:48:38 -04:00
José Valim 1ca7c7a16e Use :lists.member as it uses === and not == 2013-10-29 12:43:02 +01:00
José Valim 3f71c03547 Fix build bootstrap 2013-10-29 12:31:00 +01:00
José Valim 15d2d9f249 Impose === as the operator in the Set API 2013-10-29 12:16:08 +01:00
José Valim a31f180dd9 Impose === as the operator in the Dict API 2013-10-29 11:52:58 +01:00
José Valim e31a06d979 Merge pull request #1822 from jwarwick/record_docs
Cleaned up Record docs
2013-10-29 00:24:04 -07:00
John Warwick 49fe6134ee Cleaned up Record docs 2013-10-28 23:20:20 -04:00
José Valim 9a684f0c0f Clean up ebin on Makefile recompile, closes #1808 2013-10-28 08:46:39 +01:00
José Valim 7193753b82 Improve and/or docs, closes #1812 2013-10-28 08:43:59 +01:00
José Valim e9af1ea988 Merge pull request #1813 from jwarwick/version_docs
Cleaning up Version docs
2013-10-28 00:39:55 -07:00
José Valim 97d4c56f3a Merge pull request #1818 from jwarwick/io_ansi_links
Cleaned up ExUnit docs
2013-10-27 23:36:27 -07:00
John Warwick 759539ffe5 Cleaned up ExUnit docs 2013-10-27 22:19:21 -04:00
José Valim 3bc24dfcf0 Merge pull request #1817 from parroty/doc
Doc fix for Kernel.SpecialForms
2013-10-27 09:27:12 -07:00
parroty 4ecc939a86 Doc fix for Kernel.SpecialForms 2013-10-28 00:39:32 +09:00
José Valim 030816959c Merge pull request #1816 from jwarwick/ioansi_docs
Fixing docs typos in IO.ANSI
2013-10-26 23:05:20 -07:00
John Warwick feab232bca Fixing docs typos in IO.ANSI 2013-10-26 22:52:45 -04:00
John Warwick eee4351311 Cleaning up Version docs 2013-10-26 22:28:51 -04:00
José Valim 6c3664c989 Improve mix coverage for dependent files 2013-10-26 17:52:48 +02:00
José Valim 737b39850f Speed up mix compile by relying on the manifests information 2013-10-26 17:48:41 +02:00
José Valim 67ac77fdb3 Sigils can now handle balanced tokens as in %s(f(o)o)
Closes #1480
2013-10-26 14:08:04 +02:00
José Valim 0171e201fb Move parsing from elixir_interpolations to elixir_parser 2013-10-26 13:46:01 +02:00
José Valim 86b3f72c5d Fix IO.ANSI docs 2013-10-26 13:46:01 +02:00
José Valim ed2c41837a Clean up escape char duplication 2013-10-26 13:46:00 +02:00
Eric Meadows-Jonsson 5bdcc6643d Fix redefining module race condition in mix tests 2013-10-26 10:57:58 +02:00
José Valim 2464cea20f Merge pull request #1809 from joshrotenberg/defrecord-doc-fix
fix defrecord doc error
2013-10-25 10:43:32 -07:00
josh rotenberg 4343b8a1e2 fix defrecord doc error 2013-10-25 10:18:59 -07:00
José Valim 98a5748376 Remove warnings on protocol/consolidation.ex 2013-10-25 13:20:05 +02:00
José Valim 404f77421a Refactor string interpolation to delegate to the tokenizer
This refactoring helps us clean up the interpolation code and
guarantee it works as expected since it goes under the same
tokenization rules. It also helps provide better sigil handling
(going to be improved in upcoming commits).
2013-10-25 13:19:47 +02:00
José Valim 1e0daad1b1 Merge pull request #1803 from fishcakez/fix/try_else_support
Document try else clause
2013-10-24 13:40:53 -07:00
José Valim 0b3c91bf6d Do not wrap fn -> end in do keyword
The previous AST came from the fact `fn -> end` was a shortcut
for `function do ... end`. But now since the original no longer
exists, it seems saner to update `fn -> end` AST.
2013-10-24 22:38:25 +02:00
James Fish 446f5dc2c3 Document try else clause 2013-10-24 21:11:58 +01:00
José Valim d421af678a Merge pull request #1807 from elegua/interpolation_parens
Fixed interpolation per issue 1758
2013-10-24 11:43:27 -07:00
elegua e63e4d44a3 Fixed interpolation per issue 1758 2013-10-24 11:25:34 -07:00
José Valim 09414b0ade Document fn special form (tks @wycats) 2013-10-24 18:03:52 +02:00
José Valim 9e6f49a55e Merge pull request #1805 from christhekeele/macro-docs-typo
Add an 's' to an expresion.
2013-10-24 08:20:41 -07:00
Chris Keele 60455a9cdc Add an 's' to an expresion. 2013-10-24 09:52:59 -05:00
José Valim d054f4f852 Log paths relative to the current working directory 2013-10-24 16:09:45 +02:00
José Valim 39b07ae8f7 Use if_ok approach on protocol pipes 2013-10-24 13:18:16 +02:00
José Valim b89309b05a Update CHANGELOG 2013-10-24 12:55:58 +02:00
José Valim 2d8268cb8d Add Macro.pipe and Macro.unpipe 2013-10-24 12:55:58 +02:00
José Valim 80aac85caf Merge pull request #1804 from jwarwick/system_summary
Added summary friendly docs to System
2013-10-23 23:31:36 -07:00
John Warwick 874517efd1 Added summary friendly docs to System 2013-10-23 22:37:14 -04:00
José Valim 1963026833 Remove too early project declarations 2013-10-23 22:05:07 +02:00
José Valim edcde3e801 Ensure overrider is placed after its dependencies 2013-10-23 17:32:33 +02:00
José Valim 088cfe0a4c Merge pull request #1801 from MSch/master
Make mix work out of the box if it's escriptize-d.
2013-10-23 05:40:25 -07:00
Martin Schürrer fc42dc9bb7 Make mix work out of the box if it's escriptize-d. 2013-10-23 14:38:17 +02:00
José Valim 5f9e0c1212 No need recur on loadpaths because children apps are now deps 2013-10-23 13:46:52 +02:00
José Valim 7131696621 Merge pull request #1800 from jwarwick/typo
Doc typos
2013-10-22 23:01:41 -07:00
John Warwick a4234a4494 Updated docs for Float, Integer and Tuple. 2013-10-22 18:02:45 -04:00
John Warwick 925f08c612 fixed typos in ChangeLog 2013-10-22 17:14:52 -04:00
José Valim 16a457f2f2 Merge pull request #1799 from fishcakez/fix/record_extractor_retrieve_type
Fix type of name passed to :code.lib_dir
2013-10-22 13:45:40 -07:00
José Valim 1c3f8a059b Merge pull request #1798 from fishcakez/fix/iex_cli_tty_works_type
Fix type of executable passed to Port.open in IEx.CLI.tty_works/0
2013-10-22 13:45:18 -07:00
José Valim dca38e4377 Merge pull request #1797 from fishcakez/fix/protocol_consol_dead
Remove dead code from Protocol.Consolidation.ensure_protcol/1
2013-10-22 13:43:25 -07:00
James Fish db41ceaf38 Fix type of name passed to :code.lib_dir
:code.lib_dir/1 may only accept an atom in the future, and should only
be passed an atom according to the spec.
2013-10-22 20:34:41 +01:00
James Fish b32b6a5108 Fix type of executable passed to Port.open in IEx.CLI.tty_works/0
Change the executable to a charlist instead of an atom
2013-10-22 20:23:37 +01:00
James Fish 4391247d30 Remove dead code from Protocol.Consolidation.ensure_protcol/1 2013-10-22 19:46:52 +01:00
José Valim e46e1b2907 Clarify Integer.parse/1 code with comments 2013-10-22 20:04:58 +02:00
José Valim 2644060de4 More typespecs improvements 2013-10-22 20:01:54 +02:00
José Valim 295fe4605b Merge pull request #1791 from fishcakez/fix/regex_specs
Fix Regex specs
2013-10-22 10:53:10 -07:00
José Valim 8d2251f8f1 Merge pull request #1796 from fishcakez/fix/elixir_errors_spec
Fix elixir_errors.erl specs to include binary paths
2013-10-22 10:51:22 -07:00
José Valim f0771bb392 Merge pull request #1792 from fishcakez/fix/introspect_no_abstract
Fix IEx.Instrospection.beam_specs/1 when no abstract code in beam
2013-10-22 10:46:40 -07:00
José Valim d93258ec69 Merge pull request #1793 from fishcakez/fix/inspect_algebra_format
Fix Inspect.Algebra.format/3 to handle :infinity correctly
2013-10-22 10:41:04 -07:00
James Fish f751deea85 Fix elixir_errors.erl specs to include binary path 2013-10-22 18:23:35 +01:00
José Valim f8c894004b Merge pull request #1789 from fishcakez/fix/code_raise_error
Fix invalid raises in Code
2013-10-22 10:16:19 -07:00
José Valim 8b77b603b1 Merge pull request #1790 from fishcakez/fix/enum_chunks_spec
Fix spec for Enum.chunks/4
2013-10-22 10:15:36 -07:00
José Valim 38948f7ed7 Merge pull request #1788 from fishcakez/fix/version_parse_specs
Fix Version.parse/1 specs
2013-10-22 10:14:07 -07:00
James Fish bb5b0b87c2 Fix Inspect.Algebra.format/3 to handle :infinity correctly 2013-10-22 18:10:43 +01:00
James Fish 5b42cefb64 Fix IEx.Instrospection.beam_specs/1 when no abstract code in beam 2013-10-22 18:05:36 +01:00
José Valim 3ee311cd0b Merge pull request #1787 from fishcakez/fix/mix_shell_typo
Fix typo s/Mix.Shell.error/Mix.shell.error/
2013-10-22 10:02:50 -07:00
José Valim 18a33427f5 Merge pull request #1786 from fishcakez/optimise_raise
Optimise raise/1,2 at compile time
2013-10-22 09:58:49 -07:00
James Fish d68b9340f0 Fix Regex specs 2013-10-22 17:57:40 +01:00
James Fish 50a2f8a8ea Fix spec for Enum.chunks/4 2013-10-22 17:54:34 +01:00
James Fish bc080f5d0b Fix invalid raises in Code 2013-10-22 17:50:32 +01:00
James Fish 1a0e8378ea Fix Version.parse/1 specs 2013-10-22 17:43:36 +01:00
James Fish e2e975a28e Fix typo s/Mix.Shell.error/Mix.shell.error/ 2013-10-22 17:38:33 +01:00
James Fish 3b141bb474 Optimise raise/1,2 at compile time
Where possible the type of the exception (1st argument) is inferred at
compile time instead of run time.
2013-10-22 17:19:26 +01:00
José Valim 3b07eb3ceb Merge pull request #1785 from parroty/doc
Fix docs for the [test_coverage] option of the [mix test]
2013-10-22 09:12:25 -07:00
parroty a5f2a04ae3 Fix docs for the [test_coverage] option of the [mix test] 2013-10-23 01:00:39 +09:00
José Valim 046af30c1b Optimize binary handling in Integer.parse and Float.parse
We have used the following commands to compile those files
with the appropriate optimization info:

    ERL_COMPILER_OPTIONS="bin_opt_info" elixir lib/elixir/lib/integer.ex
    ERL_COMPILER_OPTIONS="bin_opt_info" elixir lib/elixir/lib/float.ex

More info at www.erlang.org/doc/efficiency_guide/binaryhandling.html
2013-10-22 10:08:29 +02:00
José Valim 71071b6a09 No need to store result in IEx.Config 2013-10-22 10:08:29 +02:00
José Valim 8785313413 Merge pull request #1783 from ericmj/consolidation-order
Ensure consolidator works when functions are reordered
2013-10-21 05:56:51 -07:00
Eric Meadows-Jonsson fe2f04837e Ensure consolidator works when functions are reordered 2013-10-21 14:51:43 +02:00
José Valim e3da91ad4f Improve error messages throughout Mix codebase, closes #1779 2013-10-21 13:13:36 +02:00
José Valim 51d9856638 Ensure changes in mixfile forces a complete scm update 2013-10-21 12:23:30 +02:00
José Valim f4fc764789 Update CHANGELOG 2013-10-21 10:16:44 +02:00
José Valim 79366ee7fc Drop .elixirrc 2013-10-21 10:15:27 +02:00
José Valim 5765db5919 Merge pull request #1774 from pminten/enum-slice-range
Add Enum.slice(coll, range)
2013-10-20 15:49:27 -07:00
José Valim ca8a877d69 Merge pull request #1777 from ToJans/fix_mix_tests
Fix mix tests
2013-10-20 15:34:05 -07:00
José Valim caa2eb1709 Ensure deps at the same level converge, removing duplicated results 2013-10-21 00:32:50 +02:00
José Valim 8be9ceac82 Automatically generate umbrella refs in mix new inside apps 2013-10-21 00:16:47 +02:00
José Valim 4fc3fc11de Fix status code in mix test 2013-10-20 23:56:19 +02:00
José Valim 7d2669634b Umbrella projects now treat apps as deps 2013-10-20 23:42:18 +02:00
Tom Janssens 70275a5c8c My skills are a bit rusty... 2013-10-20 18:49:19 +02:00
Tom Janssens 0b92acf309 Still not fixed! 2013-10-20 15:43:13 +02:00
Tom Janssens c228323228 Broke the build! 2013-10-20 15:37:09 +02:00
Tom Janssens b91ae66e04 Merge remote-tracking branch 'elixir/master' into fix_mix_tests 2013-10-20 15:20:47 +02:00
Tom Janssens 7981f9df2d Fixed newline mismatches on windows for test_mix 2013-10-20 15:20:11 +02:00
José Valim 641b3f0766 Docs and code clean up in Mix.Project and Mix.Dep 2013-10-20 11:50:39 +02:00
Peter Minten 9a9fc7318c Add Enum.slice(coll, range)
By analogy to the new String.slice function.
2013-10-20 11:48:45 +02:00
José Valim 95e3fec729 Ensure Integer.parse and Float.parse do not accept double +, - and . 2013-10-20 10:54:11 +02:00
José Valim e884f84f03 Merge pull request #1775 from cblage/fast-number-parsing
Float.parse and Integer.parse Elixir native and fast implementation
2013-10-20 01:39:54 -07:00
Carlos Brito Lage 3cc8e10525 Merge branch 'master' into fast-number-parsing 2013-10-19 15:16:26 -07:00
Carlos Brito Lage 60d11ed7cc Native float parsing 2013-10-19 15:15:47 -07:00
Carlos Brito Lage 64754ab7bf Native Integer parsing 2013-10-19 15:13:45 -07:00
Carlos Brito Lage 8099754898 Added extra test coverage for float parsing 2013-10-19 15:13:28 -07:00
José Valim 4c2301b3a3 Improve String.slice implementation 2013-10-19 16:44:26 +02:00
José Valim cd74bd5d54 Also deprecate atom<->binary conversions with encoding 2013-10-18 22:18:57 +02:00
José Valim a8debf06ad Address typos 2013-10-18 22:18:57 +02:00
José Valim 2ca8066c3a Merge pull request #1771 from devinus/bitwise-oddeven
Use bitwise and to check if an integer is odd/even
2013-10-18 12:21:44 -07:00
José Valim f0b36dcc4e Merge pull request #1772 from devinus/genfsm-weird-atom
:ok should the default gen_fsm reply
2013-10-18 11:48:21 -07:00
Devin Torres be47b2aaf8 :ok should the default gen_fsm reply 2013-10-18 13:29:18 -05:00
Devin Torres bf7662f9ec Use bitwise and to check if an integer is odd/even 2013-10-18 13:25:53 -05:00
José Valim ea9b0e07da Split Number into Integer and Float in protocols 2013-10-18 19:50:19 +02:00
José Valim b343b6c459 Move tuple functionality to Tuple module 2013-10-18 19:39:11 +02:00
José Valim 429548ea75 Remove pid conversions from Kernel, clean up String conversions 2013-10-18 19:16:17 +02:00
José Valim 997a0857c3 Avoid flattening in access[...], closes #1763 2013-10-18 14:54:49 +02:00
José Valim d7ab47cddb Lists are already a literal, they don't require a :[] container 2013-10-18 10:51:04 +02:00
José Valim 4e0b1b5150 Fix wrong :timer.tc call 2013-10-18 10:50:42 +02:00
José Valim 811b60c115 Merge pull request #1769 from drueck/string-slice-range
Add version of String.slice that accepts a Range
2013-10-17 23:09:17 -07:00
José Valim 203e37d698 Merge pull request #1770 from ckrailo/minor_documentation_fix
Fix documentation wording on String.split/1
2013-10-17 23:04:52 -07:00
Christopher Krailo 0ab77cf550 Fix documentation on String.split/1
Make the beginning match String.split/3. The "on substrings" bit seemed odd and I liked how String.split/3 phrased it instead.
2013-10-17 20:08:04 -05:00
David Rueck a4e69dea6b Use if instead of case 2013-10-17 16:18:56 -07:00
David Rueck fc762befcc Fix @spec line 2013-10-17 16:18:09 -07:00
David Rueck 2952512330 Indent code examples correctly 2013-10-17 16:17:45 -07:00
David Rueck e93bd1e21b Add version of String.slice that accepts a Range 2013-10-17 15:38:29 -07:00
José Valim d50cd8a681 Fix a bug with Process.group_leader so IEx.pry properly affects the server leader 2013-10-17 18:03:37 +02:00
José Valim 356d445f2e Ensure Stream.flat_map does not intercept wrapped lazy enumeration 2013-10-17 11:35:42 +02:00
José Valim bf5982c239 Do not block the client if take is refused in IEx.pry 2013-10-17 10:24:48 +02:00
José Valim 056069071c Set ExUnit color based on IO.ANSI.terminal?, closes #1756 2013-10-16 10:05:26 +02:00
José Valim a4deb46d9e CHANGELOG and doc typos (thanks @ericmj) 2013-10-16 10:03:17 +02:00
José Valim 7e8b9b6e52 Merge pull request #1761 from elixir-lang/jv-pry
Provide IEx.pry functionality
2013-10-16 01:00:26 -07:00
José Valim 1bd5003f9b Store key-values in @protocol and @impl 2013-10-14 11:30:10 +02:00
José Valim cdd11e7bf1 Improve IEx.Server coverage 2013-10-13 22:28:47 +02:00
José Valim 10270da4cf IEx.Server.boot -> IEx.Server.start 2013-10-13 22:28:20 +02:00
José Valim 9fbe5cc57c Move booting process to IEx.Server and allow take over 2013-10-13 14:29:04 +02:00
José Valim cbec463554 Make ExUnit autorun configurable 2013-10-13 14:15:19 +02:00
José Valim 378f6ee0e2 Append archives/paths before executing any task
Closes #1760
2013-10-13 12:19:09 +02:00
José Valim 2bb10d43da Extract IEx.Evaluator to its own process 2013-10-13 12:09:01 +02:00
José Valim 3eb89fb31c Add IEx.Helpers.respawn 2013-10-13 09:34:53 +02:00
José Valim 54353f39dc Keep IEx helpers around for pry 2013-10-12 23:10:25 +02:00
José Valim 45b7debd35 Test IEx.Server start options and configurations 2013-10-12 21:59:54 +02:00
José Valim 79d2ddc46a Make the taking over process the evaluator 2013-10-12 21:50:15 +02:00
José Valim 0df9285db1 Initial pry handling 2013-10-12 20:52:29 +02:00
José Valim 9cc440bfd7 Do not store the whole config in IEx.History 2013-10-12 20:52:11 +02:00
José Valim 404faaf484 Merge pull request #1759 from alco/iex-history-reset
Iex history reset
2013-10-12 05:54:41 -07:00
Alexei Sholik e2f81270a6 Add a test for IEx.History.reset 2013-10-12 15:18:58 +03:00
Alexei Sholik b262884a19 Clarify the docstring for IEx.History.nth 2013-10-12 15:15:27 +03:00
Alexei Sholik 6126261dea Implement IEx.History.reset 2013-10-12 15:13:08 +03:00
José Valim 76b3a2af69 Move all IEx config handling to IEx.Server 2013-10-12 13:41:55 +02:00
José Valim 39c1fa91b4 Improve IEx docs, mention remsh, unify options handling 2013-10-12 11:48:47 +02:00
José Valim bc10b6d848 Do not start and configure IEx all the time 2013-10-11 21:00:50 +02:00
José Valim 0b51a53fe9 Handle markdown escapes in ANSIDocs, closes #1757 2013-10-11 10:31:30 +02:00
José Valim af2ce9cfc4 Merge IEx.ANSIDocs 2013-10-11 00:23:31 +02:00
José Valim 53c9dc33e0 Remove duplicated code 2013-10-11 00:22:49 +02:00
José Valim 0870003f1f Do not split words for handling inline code 2013-10-11 00:22:49 +02:00
José Valim bce28ab58a Simplify list handling by delegating to the same text handling function 2013-10-11 00:22:49 +02:00
José Valim 90de6c9a54 Improve coverage of backticks, underscsores, etc in ANSIDocs 2013-10-11 00:22:49 +02:00
pragdave 2eee8f77c7 fix problem with lone stars 2013-10-11 00:22:49 +02:00
pragdave 6923e7bfc6 fix bug with nested lists 2013-10-11 00:22:49 +02:00
pragdave 3f8d5b59ec add a few extra tests 2013-10-11 00:22:49 +02:00
José Valim 1317164d11 mend 2013-10-11 00:22:49 +02:00
José Valim 63b2180553 Split IEx colors into categories 2013-10-11 00:22:49 +02:00
pragdave e7c3bed4dd respond to review. make internal methods private. Add tests for external methods. 2013-10-11 00:22:48 +02:00
pragdave 8683026d05 Allow H3s to be nested inside lists 2013-10-11 00:22:48 +02:00
pragdave 949c45d610 Fix up handling of enabled: color option 2013-10-11 00:22:48 +02:00
pragdave 3dbdc77550 First pass at formatting iex help output 2013-10-11 00:18:59 +02:00
José Valim 099879ef6d Assert messages are received instead of flushing.
Since at the moment we call flush there is no guarantee
we received all desired messages.
2013-10-10 17:32:52 +02:00
José Valim e217f7ec5b Get rid of @only in List.Chars, improve deprecation message 2013-10-10 17:27:22 +02:00
José Valim 34b9cb67bd Update code comment 2013-10-10 16:08:36 +02:00
José Valim a3800b0ae5 Ensure we raise a meaningful error when there is no SCM
Closes #1742
2013-10-10 16:08:01 +02:00
José Valim b1b7c82f48 Ensure cover compiling results yield the app name
Closes #1743
2013-10-10 15:36:22 +02:00
José Valim f2919e873d Ensure defexception fields can be set dynamically
Closes #1751
2013-10-10 15:19:42 +02:00
José Valim b90d2608b5 Update CHANGELOG 2013-10-10 15:15:53 +02:00
José Valim 04077ec7c2 Deprecate Macro.expand_all
Eventually we would like to introduce expand_all with
proper compiler expansion semantics.
2013-10-10 12:45:42 +02:00
José Valim bae4c1c4c8 Merge pull request #1750 from elixir-lang/jv-protocols
Implement protocol consolidation
2013-10-10 03:45:30 -07:00
José Valim fad38e1bab Remove @prioritize from Protocols
The consolidation algorithm is now fast enough that prioritize
does not actually make any difference in performance. We achived
that by compiling the dispatch in two set of functions: one that
checks the types (list, tuple, function, etc) and another that
checks the leading atom name for records (HashDict, HashSet, etc).

By breaking in two different sets, the Erlang compiler can better
optimize pattern matching on those items to the point prioritizing
does not yield any benefit.

Thanks to @ericmj for the initial suggestion.
2013-10-10 11:55:28 +02:00
José Valim e17df44ef3 Merge pull request #1754 from pminten/exunit-case-doc-fix
Fix indentation on doc example
2013-10-09 07:28:15 -07:00
Peter Minten 5aa2340c7d Fix indentation on doc example 2013-10-09 16:14:37 +02:00
José Valim 65b0bde5e8 Fix typos (tks @ericmj) 2013-10-09 11:41:28 +02:00
José Valim 260b7387df Use attributes so we don't force protocols to be loaded 2013-10-09 10:13:34 +02:00
José Valim d13fe163e3 Merge pull request #1753 from mururu/rm-warning
Remove unused warning
2013-10-08 23:48:03 -07:00
Yuki Ito 3a834fcd2c Remove unused warning 2013-10-09 08:55:18 +09:00
José Valim f0af35d585 Provide a wrapper in CLI for easy stacktrace pruning
As suggested by @pragdave
2013-10-08 21:12:42 +02:00
José Valim f510d4c3df Normalize all stacktrace under the same name and improve IEx errors
Kudos to @pragdave for suggesting some those improvements and
providing an initial implementation in pull request #1752.
2013-10-08 20:47:21 +02:00
José Valim 4eee44fbae Read fallback_to_any from @only and @except for backwards compat 2013-10-08 16:21:00 +02:00
José Valim 0c883e1aa2 Ensure custom types can be prioritized 2013-10-08 16:09:41 +02:00
José Valim 2297f34179 Falling back to any in protocols needs to be explicit 2013-10-08 15:12:42 +02:00
José Valim a2c1f362c2 Implement protocol consolidation 2013-10-08 13:29:53 +02:00
José Valim 8575319c0e Improve iex typespec helpers output on bad data 2013-10-08 12:47:36 +02:00
José Valim 4c67f3daa0 Improve mix compile.elixir code comments 2013-10-08 10:29:32 +02:00
José Valim 3cf9774b92 Improve warn on invalid expressions 2013-10-07 20:16:54 +02:00
José Valim e35ddb6781 Update CHANGELOG 2013-10-07 19:01:57 +02:00
José Valim a0b8067571 Upgrade clustering from legacy to extended 2013-10-07 18:28:24 +02:00
José Valim 5f6fa9be11 Update Unicode database to 6.3.0 2013-10-07 18:13:41 +02:00
José Valim 81c6ac2548 Use GraphemeBreakProperty as in Unicode Annex #29 2013-10-07 17:59:25 +02:00
José Valim 47ab0c7a3a Deprecate @only and @except 2013-10-07 14:14:43 +02:00
José Valim 436cdde499 Fix bug where nested defmodule was shadowing alias hygiene 2013-10-07 13:57:07 +02:00
José Valim 7c7c3eb3c2 Clean up protocol implementation setting the ground for consolidation 2013-10-07 12:23:13 +02:00
José Valim 5cb06e9640 Allow a module to force its debug_info to be compiled 2013-10-07 10:16:02 +02:00
José Valim a7142e2f6d Remove unused types from formatter 2013-10-07 08:02:56 +02:00
José Valim 36331f0f0b No need to filter raise from stacktraces 2013-10-07 08:02:40 +02:00
José Valim 4997bc1e10 Remove deprecated and unused code branches 2013-10-04 21:39:55 +02:00
José Valim 0f14e5001d Improve Enum docs and optimize ops with negative indexes 2013-10-04 13:43:15 +02:00
José Valim d0f410ccd0 Improve record docs 2013-10-04 13:42:59 +02:00
José Valim cb023c4934 Bump to 0.10.4-dev 2013-10-02 20:58:53 +02:00
310 changed files with 21005 additions and 12785 deletions
+3 -5
View File
@@ -1,5 +1,7 @@
/.eunit/*
/.eunit
/.release
/docs
/ebin
/lib/*/ebin/*
/lib/*/tmp
/lib/*/test/tmp
@@ -7,11 +9,7 @@
/lib/elixir/src/*_lexer.erl
/lib/elixir/src/*_parser.erl
/lib/elixir/test/ebin
/deps/*
/ebin
/rel/elixir
erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
.*.swp
docs
+2
View File
@@ -6,4 +6,6 @@ notifications:
- jose.valim@plataformatec.com.br
- yrashk@gmail.com
otp_release:
- R16B02
- R16B01
- R16B
+137 -2
View File
@@ -1,3 +1,138 @@
# v0.12.1 (2014-01-04)
* Enhancements
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
* [ExUnit] Allow doctests to match against `#MyModule<>`
* Bug fixes
* [CLI] Abort when a pattern given to elixirc does not match any file
* [Float] Fix `Float.parse/1` to handle numbers of the form "-0.x"
* [IEx] Improve error message for `IEx.Helpers.r` when module does not exist
* [Mix] Ensure `deps.get` updates origin if lock origin and dep origin do not match
* [Mix] Use relative symlinks in _build
* [Typespec] Fix conversion of unary ops from typespec format to ast
* [Typespec] Fix handling of `tuple()` and `{}`
* Deprecations
* [Kernel] Do not leak clause heads. Previously, a variable defined in a case/receive head clauses would leak to the outer scope. This behaviour is deprecated and will be removed in the next release.
* [Kernel] Deprecate `__FILE__` in favor of `__DIR__` or `__ENV__.file`
* Backwards incompatible changes
* [GenFSM] GenServer now stops on unknown event/sync_event requests
* [GenServer] GenServer now stops on unknown call/cast requests
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
# v0.12.0 (2013-12-15)
* Enhancements
* [Exception] Allow `exception/1` to be overriden and promote it as the main mechanism to customize exceptions
* [File] Add `File.stream_to!/3`
* [Float] Add `Float.floor/1`, `Float.ceil/1` and `Float.round/3`
* [Kernel] Add `List.delete_at/2` and `List.updated_at/3`
* [Kernel] Add `Enum.reverse/2`
* [Kernel] Implement `defmodule/2`, `@/1`, `def/2` and friends in Elixir itself. `case/2`, `try/2` and `receive/1` have been made special forms. `var!/1`, `var!/2` and `alias!/1` have also been implemented in Elixir and demoted from special forms
* [Record] Support dynamic fields in `defrecordp`
* [Stream] Add `Stream.resource/3`
* [Stream] Add `Stream.zip/2`, `Stream.filter_map/3`, `Stream.each/2`, `Stream.take_every/2`, `Stream.chunk/2`, `Stream.chunk/3`, `Stream.chunk/4`, `Stream.chunk_by/2`, `Stream.scan/2`, `Stream.scan/3`, `Stream.uniq/2`, `Stream.after/2` and `Stream.run/1`
* [Stream] Support `Stream.take/2` and `Stream.drop/2` with negative counts
* Bug fixes
* [HashDict] Ensure a `HashDict` stored in an attribute can be accessed via the attribute
* [Enum] Fix bug in `Enum.chunk/4` where you'd get an extra element when the enumerable was a multiple of the counter and a pad was given
* [IEx] Ensure `c/2` helper works with full paths
* [Kernel] `quote location: :keep` now only affects definitions in order to keep the proper trace in definition exceptions
* [Mix] Also symlink `include` directories in _build dependencies
* [Version] Fix `Version.match?/2` with `~>` and versions with alphanumeric build info (like `-dev`)
* Deprecations
* [Enum] `Enumerable.count/1` and `Enumerable.member?/2` should now return tagged tuples. Please see `Enumerable` docs for more info
* [Enum] Deprecate `Enum.chunks/2`, `Enum.chunks/4` and `Enum.chunks_by/2` in favor of `Enum.chunk/2`, `Enum.chunk/4` and `Enum.chunk_by/2`
* [File] `File.binstream!/3` is deprecated. Simply use `File.stream!/3` which is able to figure out if `stream` or `binstream` operations should be used
* [Macro] `Macro.extract_args/1` is deprecated in favor of `Macro.decompose_call/1`
* Backwards incompatible changes
* [Enum] Behaviour of `Enum.drop/2` and `Enum.take/2` has been switched when given negative counts
* [Enum] Behaviour of `Enum.zip/2` has been changed to stop as soon as the first enumerable finishes
* [Enum] `Enumerable.reduce/3` protocol has changed to support suspension. Please see `Enumerable` docs for more info
* [Mix] Require `:escript_main_module` to be set before generating escripts
* [Range] `Range.Iterator` protocol has changed in order to work with the new `Enumerable.reduce/3`. Please see `Range.Iterator` docs for more info
* [Stream] The `Stream.Lazy` structure has changed to accumulate functions and accumulators as we go (its inspected representation has also changed)
* [Typespec] `when` clauses were moved to the outer part of the spec and should be in the keywords format. So `add(a, b) when is_subtype(a, integer) and is_subtype(b, integer) :: integer` should now be written as `add(a, b) :: integer when a: integer, b: integer`
# v0.11.2 (2013-11-14)
* Enhancements
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
* [Mix] Support `build_per_environment: true` in project configuration that manages a separete build per environment, useful when you have per-environment behaviour/compilation
* Backwards incompatible changes
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
# v0.11.1 (2013-11-07)
* Enhancements
* [Mix] Improve dependency convergence by explicitly checking each requirement instead of expecting all requirements to be equal
* [Mix] Support optional dependencies with `optional: true`. Optional dependencies are downloaded for the current project but they are automatically skipped when such project is used as a dependency
* Bug fixes
* [Kernel] Set compilation status per ParallelCompiler and not globally
* [Mix] Ensure Mix does not load previous dependencies versions before `deps.get`/`deps.update`
* [Mix] Ensure umbrella apps are sorted before running recursive commands
* [Mix] Ensure umbrella apps run in the same environment as the parent project
* [Mix] Ensure dependency tree is topsorted before compiling
* [Mix] Raise error when duplicated projects are pushed into the stack
* [URI] Allow lowercase escapes in URI
* Backwards incompatible changes
* [Mix] Setting `:load_paths` in your project configuration is deprecated
# v0.11.0 (2013-11-02)
* Enhancements
* [Code] Eval now returns variables from other contexts
* [Dict] Document and enforce all dicts use the match operator (`===`) when checking for keys
* [Enum] Add `Enum.slice/2` with a range
* [Enum] Document and enforce `Enum.member?/2` to use the match operator (`===`)
* [IEx] Split `IEx.Evaluator` from `IEx.Server` to allow custom evaluators
* [IEx] Add support for `IEx.pry` which halts a given process for inspection
* [IO] Add specs and allow some IO APIs to receive any data that implements `String.Chars`
* [Kernel] Improve stacktraces on command line interfaces
* [Kernel] Sigils can now handle balanced tokens as in `%s(f(o)o)`
* [Kernel] Emit warnings when an alias is not used
* [Macro] Add `Macro.pipe/3` and `Macro.unpipe/1` for building pipelines
* [Mix] Allow umbrella children to share dependencies between them
* [Mix] Allow mix to be escriptize'd
* [Mix] Speed mix projects compilation by relying on more manifests information
* [Protocol] Protocols now provide `impl_for/1` and `impl_for!/1` functions which receive a structure and returns its respective implementation, otherwise returns nil or an error
* [Set] Document and enforce all sets use the match operator (`===`) when checking for keys
* [String] Update to Unicode 6.3.0
* [String] Add `String.slice/2` with a range
* Bug fixes
* [Exception] Ensure `defexception` fields can be set dynamically
* [Kernel] Guarantee aliases hygiene is respected when the current module name is not known upfront
* [Kernel] `Kernel.access/2` no longer flattens lists
* [Mix] Ensure cyclic dependencies are properly handled
* [String] Implement the extended grapheme cluster algorithm for `String` operations
* Deprecations
* [Kernel] `pid_to_list/1`, `list_to_pid/1`, `binary_to_atom/2`, `binary_to_existing_atom/2` and `atom_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
* [Kernel] `insert_elem/3` and `delete_elem/2` are deprecated in favor of `Tuple.insert_at/3` and `Tuple.delete_at/2`
* [Kernel] Use of `in` inside matches (as in `x in [1,2,3] -> x`) is deprecated in favor of the guard syntax (`x when x in [1,2,3]`)
* [Macro] `Macro.expand_all/2` is deprecated
* [Protocol] `@only` and `@except` in protocols are now deprecated
* [Protocol] Protocols no longer fallback to `Any` out of the box (this functionality needs to be explicitly enabled by setting `@fallback_to_any` to true)
* [String] `String.to_integer/1` and `String.to_float/1` are deprecated in favor of `Integer.parse/1` and `Float.parse/1`
* Backwards incompatible changes
* [CLI] Reading `.elixirrc` has been dropped in favor of setting env vars
* [Kernel] `Kernel.access/2` now expects the second argument to be a compile time list
* [Kernel] `fn -> end` quoted expression is no longer wrapped in a `do` keyword
* [Kernel] Quoted variables from the same module must be explicitly shared. Previously, if a function returned `quote do: a = 1`, another function from the same module could access it as `quote do: a`. This has been fixed and the variables must be explicitly shared with `var!(a, __MODULE__)`
* [Mix] Umbrella apps now treat children apps as dependencies. This means all dependencies will be checked out in the umbrela `deps` directory. On upgrade, child apps need to point to the umbrella project by setting `deps_path: "../../deps_path", lockfile: "../../mix.lock"` in their project config
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
# v0.10.3 (2013-10-02)
* Enhancements
@@ -38,7 +173,7 @@
* Enhancements
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunk/2`, `Enum.chunk/4`, `Enum.chunk_by/2`, `Enum.concat/1` and `Enum.concat/2`
* [Enum] Support negative indices in `Enum.at/fetch/fetch!`
* [ExUnit] Show failures on CLIFormatter as soon as they pop up
* [IEx] Allow for strings in `h` helper
@@ -224,7 +359,7 @@
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil character
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
* [Mix] No longer support beam files on `mix local`
+16 -9
View File
@@ -19,11 +19,14 @@ INSTALL_PROGRAM = $(INSTALL) -m755
define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
$(Q) rm -rf lib/$(1)/_build
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@ rm -rf lib/$(1)/ebin
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
test_$(1): $(1)
@@ -42,7 +45,9 @@ compile: lib/elixir/src/elixir.app.src erlang elixir
lib/elixir/src/elixir.app.src: src/elixir.app.src
$(Q) rm -rf lib/elixir/src/elixir.app.src
$(Q) cp src/elixir.app.src lib/elixir/src/elixir.app.src
$(Q) echo "%% This file is automatically generated from <project_root>/src/elixir.app.src" \
>lib/elixir/src/elixir.app.src
$(Q) cat src/elixir.app.src >>lib/elixir/src/elixir.app.src
erlang:
$(Q) cd lib/elixir && $(REBAR) compile
@@ -59,16 +64,17 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@ echo "==> kernel (compile)";
$(Q) $(ELIXIRC) "lib/elixir/lib/kernel.ex" -o lib/elixir/ebin;
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
$(Q) $(MAKE) unicode
$(Q) rm -rf lib/elixir/ebin/elixir.app
$(Q) cd lib/elixir && $(REBAR) compile
unicode: $(UNICODE)
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
$(UNICODE): lib/elixir/unicode/unicode.ex lib/elixir/unicode/UnicodeData.txt lib/elixir/unicode/GraphemeBreakProperty.txt
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
@@ -92,6 +98,7 @@ clean:
cd lib/elixir && $(REBAR) clean
rm -rf ebin
rm -rf lib/*/ebin
rm -rf lib/elixir/test/ebin
rm -rf lib/*/tmp
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/mix/test/fixtures/deps_on_git_repo
@@ -109,7 +116,7 @@ docs: compile ../ex_doc/bin/ex_doc
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" "./ebin" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
rm -rf ebin
../ex_doc/bin/ex_doc:
@@ -121,9 +128,9 @@ release_zip: compile
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
release_docs: docs
cd ../elixir-lang.github.com
rm -rf ../elixir-lang.github.com/docs/master
mv docs ../elixir-lang.github.com/docs/master
cd ../docs
rm -rf ../docs/master
mv docs ../docs/master
release_erl: compile
$(Q) rm -rf rel/elixir
+4 -2
View File
@@ -10,11 +10,13 @@ If you want to contribute to Elixir or run it from source, clone this repository
$ git clone https://github.com/elixir-lang/elixir.git
$ cd elixir
$ make test
$ make clean test
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
+3 -3
View File
@@ -10,17 +10,17 @@ This document simply outlines the release process:
4) Commit changes above with title "Release vVERSION"
5) Push master and create tag from master branch
5) Push master and create tag vVERSION from master branch
6) Release new docs with `make release_docs`, move docs to `docs/stable`
7) Release new zip with `make release_zip`, push new zip to Github Releases
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
8) Push package to expm with `expm publish package.exs`
9) Merge master into stable branch and push it
10) After release, bump versions and add `-dev` back
10) After release, bump versions, add `-dev` back and commit
11) `make release_docs` once again and push `elixir-lang.github.com`
+1 -1
View File
@@ -1 +1 @@
0.10.3
0.12.1
+1 -3
View File
@@ -5,7 +5,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; 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 (*)
-S \"script\"   Finds and executes the given script
-pr \"file\" Requires the given files/patterns in parallel (*)
-pa \"path\" Prepends the given path to Erlang code path (*)
-pz \"path\" Appends the given path to Erlang code path (*)
@@ -73,8 +73,6 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
+1 -1
View File
@@ -12,7 +12,7 @@ echo.
echo -v Prints version and exit
echo -e command Evaluates the given command (*)
echo -r file Requires the given files/patterns (*)
echo -S script Finds and executes the given script (*)
echo -S script Finds and executes the given script
echo -pr file Requires the given files/patterns in parallel (*)
echo -pa path Prepends the given path to Erlang code path (*)
echo -pz path Appends the given path to Erlang code path (*)
+1 -1
View File
@@ -5,7 +5,7 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
-v Prints version
-e \"command\" Evaluates the given command (*)
-r \"file\" Requires the given files/patterns (*)
-S \"script\"   Finds and executes the given script (*)
-S \"script\"   Finds and executes the given script
-pr \"file\" Requires the given files/patterns in parallel (*)
-pa \"path\" Prepends the given path to Erlang code path (*)
-pz \"path\" Appends the given path to Erlang code path (*)
+1 -1
View File
@@ -1,3 +1,3 @@
#!/usr/bin/env elixir
Mix.start
Mix.CLI.run
Mix.CLI.main
+13
View File
@@ -27,6 +27,19 @@ defmodule EEx do
above and is available to you if you want to provide your own
ways of handling the compiled template.
## Options
All functions in this module accepts EEx-related options.
They are:
* `:line` - the line to be used as the template start.
Defaults to 1;
* `:file` - the file to be used in the template.
Defaults to the given file the template is read from
or to "nofile" when compiling from a string;
* `:engine` - the EEx engine to be used for compilation.
Defaults to `EEx.Engine`;
## Engine
EEx has the concept of engines which allows you to modify or
+8 -14
View File
@@ -1,4 +1,4 @@
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: 'nofile', line: 1, start_line: 1
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: "nofile", line: 1, start_line: 1
defmodule EEx.Compiler do
@moduledoc false
@@ -23,7 +23,7 @@ defmodule EEx.Compiler do
end
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
expr = maybe_block :elixir_translator.forms!(chars, line, state.file, [])
expr = Code.string_to_quoted!(chars, [line: line, file: 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 :elixir_translator.forms!(wrapped, state.start_line, state.file, [])
tuples = Code.string_to_quoted!(wrapped, [line: state.start_line, file: state.file])
buffer = insert_quotes(tuples, state.dict)
{ buffer, t }
end
@@ -63,7 +63,7 @@ defmodule EEx.Compiler do
defp wrap_expr(current, line, buffer, chars, state) do
new_lines = List.duplicate(?\n, line - state.line)
if state.dict == [] and is_empty?(buffer) do
if state.dict == [] and empty?(buffer) do
{ current ++ new_lines ++ chars, state }
else
key = length(state.dict)
@@ -74,24 +74,18 @@ defmodule EEx.Compiler do
# Check if the syntax node represents an empty string
defp is_empty?(bin) when is_binary(bin) do
defp empty?(bin) when is_binary(bin) do
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
end
defp is_empty?({ :<>, _, [left, right] }) do
is_empty?(left) and is_empty?(right)
defp empty?({ :<>, _, [left, right] }) do
empty?(left) and empty?(right)
end
defp is_empty?(_) do
defp empty?(_) do
false
end
# Block wrapping
defp maybe_block([]), do: nil
defp maybe_block([h]), do: h
defp maybe_block(other), do: { :__block__, [], other }
# Changes placeholder to real expression
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
+1 -1
View File
@@ -103,7 +103,7 @@ defmodule EEx.Tokenizer do
# 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 } ->
{ :ok, _line, tokens } ->
tokens = Enum.reverse(tokens)
fn_index = fn_index(tokens)
+1 -1
View File
@@ -16,7 +16,7 @@ defmodule EEx.SmartEngineTest do
end
defp assert_eval(expected, actual, binding // []) do
result = EEx.eval_string(actual, binding, file: __FILE__)
result = EEx.eval_string(actual, binding, file: __ENV__.file)
assert result == expected
end
end
+3 -3
View File
@@ -321,7 +321,7 @@ foo
{ EExText.Compiled,
:before_compile,
0,
[file: to_char_list(__FILE__), line: 7]
[file: to_char_list(__ENV__.file), line: 7]
}
}
@@ -330,7 +330,7 @@ foo
{ EExText.Compiled,
:after_compile,
0,
[file: to_char_list(__FILE__), line: 18]
[file: to_char_list(__ENV__.file), line: 18]
}
}
@@ -345,7 +345,7 @@ foo
end
defp assert_eval(expected, actual) do
result = EEx.eval_string(actual, [], file: __FILE__, engine: EEx.Engine)
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
assert result == expected
end
end
+31 -16
View File
@@ -9,33 +9,44 @@
-define(line(Opts), elixir_utils:get_line(Opts)).
-record(elixir_scope, {
context=nil, %% can be assign, guards or nil
extra=nil, %% extra information about the context, like fn_match for fns
context=nil, %% can be match, guards or nil
extra=nil, %% extra information about the context, like fn_match and do_match
noname=false, %% when true, don't add new names (used by try)
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
unsafe_vars=[], %% a set keeping all unsafe vars
temp_vars=nil, %% a set keeping all temporary unsafe vars
backup_vars=nil, %% a copy of vars to be used on ^var
temp_vars=nil, %% a set of all variables defined in a particular assign
match_vars=nil, %% a set of all variables defined in a particular match
clause_vars=nil, %% a dict of all variables defined in a particular clause
extra_guards=nil, %% extra guards from args expansion
counter=[], %% a counter for the variables defined
local=nil, %% the scope to evaluate local functions against
context_modules=[], %% modules defined in the current context
counter=[], %% a dict counting the variables defined
file=(<<"nofile">>) %% the current scope filename
}).
-record(elixir_env, {
module=nil, %% the current module
file=(<<"nofile">>), %% the current filename
line=1, %% the current line
function=nil, %% the current function
context=nil, %% can be match_vars, guards or nil
requires=[], %% a set with modules required
aliases=[], %% an orddict with aliases by new -> old names
functions=[], %% a list with functions imported from module
macros=[], %% a list with macros imported from module
macro_aliases=[], %% keep aliases defined inside a macro
aliases, %% an orddict with aliases by new -> old names
file, %% the current scope filename
requires, %% a set with modules required
macro_macros=[], %% a list with macros imported from module inside a macro
macros, %% a list with macros imported from module
macro_functions=[], %% a list with functions imported from module inside a macro
functions %% a list with functions imported from module
context_modules=[], %% modules defined in the current context
vars=[], %% a set of defined variables
lexical_tracker=nil, %% holds the lexical tracker pid
local=nil %% the module to delegate local functions to
}).
-record(elixir_quote, {
line=0,
line=false,
keep=false,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
@@ -45,8 +56,12 @@
escape=false
}).
%% Introspection
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-record(elixir_tokenizer, {
file,
terminators=[],
check_terminators=true,
existing_atoms_only=false
}).
%% Used in tokenization and interpolation
+4 -9
View File
@@ -11,8 +11,6 @@ defprotocol Access do
following built-in types: keywords, records and functions.
"""
@only [List, Record, Atom]
@doc """
Receives the element being accessed and the access item.
"""
@@ -22,6 +20,7 @@ end
defimpl Access, for: List do
@doc """
Access the given key in a tuple list.
The key is found via the `===` operator.
## Examples
@@ -34,14 +33,10 @@ defimpl Access, for: List do
"★☆"
"""
def access(dict, key)
def access([{ key, value }|_], key), do: value
def access([{ _, _ }|t], key), do: access(t, key)
def access([], _key), do: nil
def access(list, key) do
case :lists.keyfind(key, 1, list) do
{ ^key, value } -> value
false -> nil
end
end
end
defimpl Access, for: Atom do
+20 -20
View File
@@ -1,40 +1,40 @@
defmodule Application.Behaviour do
@moduledoc """
This module is a convenience to define application module callbacks.
Default callbacks for applications.
In Erlang/OTP, an application is a component that can be started
and stopped as a unit, and which can be re-used in other systems
as well.
and stopped as a unit, and which can be reused in other systems.
The first step to achieve this is to define an application specification.
The first step in creating an application is to define an application specification.
For example, if your application is named `:my_app`, an app specification
should exist at `ebin/my_app.app`. This file is usually defined by
build tools like Mix.
Then, with the app specification in hands, we must also define an
application module callback that controls how to start and stop
such applications. This module is about defining such callbacks.
With the app specification in hand, we must define
application module callbacks that control how to start and stop
instances of the application. This module is about defining such callbacks.
There are two callbacks required to be implemented:
There are two callbacks which must be implemented:
1. `start(type, args)` - It must return `{ :ok, pid }` or
1. `start(type, args)` - must return `{ :ok, pid }` or
`{ :ok, pid, state }`, where `pid` is the process identifier
of the supervisor tree root;
of the supervisor tree root and `state` is application defined
state information;
2. `stop(state)` receives the state returned by `start` and should
do any necessary cleaning up. Notice that shutting down the supervisor
2. `stop(state)` - receives the `state` returned by `start` and should
do any necessary clean up. Notice that shutting down the supervisor
is automatically handled by the VM;
When using this module, it simply tags the module behaviour as
`:application` and defines a default `stop/1` callback. The `start/2`
still needs to be defined by the user.
When using this module, it tags the module behaviour as
`:application` and provides a default `stop/1` callback. The `start/2` callback
still needs to be implemented by the user.
You can learn more about the `:application` module, the application
specification and the application module callbacks below:
specification and the application module callbacks from these sources:
http://www.erlang.org/doc/man/application.html
http://www.erlang.org/doc/design_principles/applications.html
http://learnyousomeerlang.com/building-otp-applications
* http://www.erlang.org/doc/man/application.html
* http://www.erlang.org/doc/design_principles/applications.html
* http://learnyousomeerlang.com/building-otp-applications
## Example
@@ -102,4 +102,4 @@ defmodule Application.Behaviour do
defoverridable [stop: 1]
end
end
end
end
+16 -15
View File
@@ -1,12 +1,12 @@
defmodule Behaviour do
@moduledoc """
A convenience module for defining behaviours.
Utilities for defining behaviour intefaces.
Behaviours can be referenced by other modules
in order to ensure they implement the proper
callbacks.
to ensure they implement required callbacks.
For example, you can specify the `URI.Parser`
behaviour as follow:
behaviour as follows:
defmodule URI.Parser do
use Behaviour
@@ -18,7 +18,7 @@ defmodule Behaviour do
defcallback default_port() :: integer
end
And then a specific module may use it as:
And then a module may use it as:
defmodule URI.HTTP do
@behaviour URI.Parser
@@ -26,24 +26,25 @@ defmodule Behaviour do
def parse(info), do: info
end
In case the behaviour changes or URI.HTTP does
If the behaviour changes or `URI.HTTP` does
not implement one of the callbacks, a warning
will be raised.
## Implementation
Behaviours since Erlang R15 must be defined via
Since Erlang R15, behaviours must be defined via
`@callback` attributes. `defcallback` is a simple
mechanism that defines the `@callback` attribute
according to the type specification and also allows
docs and defines a custom function signature.
according to the given type specification. `defcallback` allows
documentation to be created for the callback and defines
a custom function signature.
The callbacks and their documentation can be retrieved
via the `__behaviour__` callback function.
"""
@doc """
Defines a callback according to the given type specification.
Define a function callback according to the given type specification.
"""
defmacro defcallback({ :::, _, [fun, return] }) do
do_defcallback(fun, return, __CALLER__)
@@ -54,7 +55,7 @@ defmodule Behaviour do
end
@doc """
Defines a macro callback according to the given type specification.
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return] }) do
do_defmacrocallback(fun, return, __CALLER__)
@@ -65,19 +66,19 @@ defmodule Behaviour do
end
defp do_defcallback(fun, spec, caller) do
case Macro.extract_args(fun) do
case Macro.decompose_call(fun) do
{ name, args } ->
do_callback(:def, name, args, name, length(args), spec, caller)
:error ->
_ ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
end
end
defp do_defmacrocallback(fun, spec, caller) do
case Macro.extract_args(fun) do
case Macro.decompose_call(fun) do
{ name, args } ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args], name, length(args), spec, caller)
:error ->
_ ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
end
end
+12 -12
View File
@@ -46,83 +46,83 @@ defmodule Bitwise do
Bitwise not.
"""
defmacro bnot(expr) do
quote do: __op__(:bnot, unquote(expr))
quote do: :erlang.bnot(unquote(expr))
end
@doc """
Bitwise not as operator.
"""
defmacro ~~~expr do
quote do: __op__(:bnot, unquote(expr))
quote do: :erlang.bnot(unquote(expr))
end
@doc """
Bitwise and.
"""
defmacro band(left, right) do
quote do: __op__(:band, unquote(left), unquote(right))
quote do: :erlang.band(unquote(left), unquote(right))
end
@doc """
Bitwise and as operator.
"""
defmacro left &&& right do
quote do: __op__(:band, unquote(left), unquote(right))
quote do: :erlang.band(unquote(left), unquote(right))
end
@doc """
Bitwise or.
"""
defmacro bor(left, right) do
quote do: __op__(:bor, unquote(left), unquote(right))
quote do: :erlang.bor(unquote(left), unquote(right))
end
@doc """
Bitwise or as operator.
"""
defmacro left ||| right do
quote do: __op__(:bor, unquote(left), unquote(right))
quote do: :erlang.bor(unquote(left), unquote(right))
end
@doc """
Bitwise xor.
"""
defmacro bxor(left, right) do
quote do: __op__(:bxor, unquote(left), unquote(right))
quote do: :erlang.bxor(unquote(left), unquote(right))
end
@doc """
Bitwise xor as operator.
"""
defmacro left ^^^ right do
quote do: __op__(:bxor, unquote(left), unquote(right))
quote do: :erlang.bxor(unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift left.
"""
defmacro bsl(left, right) do
quote do: __op__(:bsl, unquote(left), unquote(right))
quote do: :erlang.bsl(unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift left as operator.
"""
defmacro left <<< right do
quote do: __op__(:bsl, unquote(left), unquote(right))
quote do: :erlang.bsl(unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift right.
"""
defmacro bsr(left, right) do
quote do: __op__(:bsr, unquote(left), unquote(right))
quote do: :erlang.bsr(unquote(left), unquote(right))
end
@doc """
Arithmetic bitshift right as operator.
"""
defmacro left >>> right do
quote do: __op__(:bsr, unquote(left), unquote(right))
quote do: :erlang.bsr(unquote(left), unquote(right))
end
end
+85 -59
View File
@@ -1,29 +1,29 @@
defmodule Code do
defexception LoadError, file: nil do
def message(exception) do
"could not load #{exception.file}"
defexception LoadError, [:file, :message] do
def exception(opts) do
file = opts[:file]
LoadError[message: "could not load #{file}", file: file]
end
end
@moduledoc """
The Code module is responsible for managing code compilation,
code evaluation and code loading.
Utilities for managing code compilation, code evaluation and code loading.
It complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
to add behavior which is specific to Elixir.
"""
@doc """
Returns all loaded files.
List all loaded files.
"""
def loaded_files do
:elixir_code_server.call :loaded
end
@doc """
Removes the given files from the loaded files list.
The modules defined in the file are not removed,
Remove files from the loaded files list.
The modules defined in the file are not removed;
calling this function only removes them from the list,
allowing them to be required again.
"""
@@ -32,7 +32,8 @@ defmodule Code do
end
@doc """
Appends a path to the Erlang VM code path.
Append a path to the Erlang VM code path.
The path is expanded with `Path.expand/1` before being appended.
"""
def append_path(path) do
@@ -40,7 +41,8 @@ defmodule Code do
end
@doc """
Prepends a path to the Erlang VM code path.
Prepend a path to the Erlang VM code path.
The path is expanded with `Path.expand/1` before being prepended.
"""
def prepend_path(path) do
@@ -48,7 +50,8 @@ defmodule Code do
end
@doc """
Deletes a path from the Erlang VM code path.
Delete a path from the Erlang VM code path.
The path is expanded with `Path.expand/1` before being deleted.
"""
def delete_path(path) do
@@ -56,9 +59,12 @@ defmodule Code do
end
@doc """
Evaluates the contents given by `string`. The second argument is
a keyword list of variable bindings, followed by a keyword list of
environment options. Those options can be:
Evaluate the contents given by `string`.
The `binding` argument is a keyword list of variable bindings.
The `opts` argument is a keyword list of environment options.
Those options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line on which the script starts
@@ -83,9 +89,12 @@ defmodule Code do
etc.
Returns a tuple of the form `{ value, binding }`,
where `value` is the the value returned from evaluating `string`; `binding`
is a keyword list with the value of all variable bindings after evaluating
`string`. If an error occurs while evaluating `string` an exception will be raised.
where `value` is the the value returned from evaluating `string`.
If an error occurs while evaluating `string` an exception will be raised.
`binding` is a keyword list with the value of all variable bindings
after evaluating `string`. The binding key is usually an atom, but it
may be a tuple for variables defined in a different context.
## Examples
@@ -95,7 +104,7 @@ defmodule Code do
iex> Code.eval_string("c = a + b", [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2, c: 3]}
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
{3, [a: 3, b: 2]}
For convenience, you can pass `__ENV__` as the `opts` argument and
@@ -118,12 +127,12 @@ defmodule Code do
end
defp do_eval_string(string, binding, opts) when is_list(binding) do
{ value, binding, _scope } = :elixir.eval to_char_list(string), binding, opts
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
{ value, binding }
end
@doc """
Evaluates the quoted contents.
Evaluate the quoted contents.
See `eval_string/3` for a description of arguments and return values.
@@ -153,8 +162,7 @@ defmodule Code do
end
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
{ value, binding, _scope } =
:elixir.eval_quoted [quoted], binding, opts
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
{ value, binding }
end
@@ -169,7 +177,7 @@ defmodule Code do
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
end
end
@@ -179,7 +187,7 @@ defmodule Code do
end)
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, module }]"
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, module }]"
end
end
@@ -191,12 +199,14 @@ defmodule Code do
end)
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
end
end
@doc """
Converts the given string to its quoted form. Returns `{ :ok, quoted_form }`
Convert the given string to its quoted form.
Returns `{ :ok, quoted_form }`
if it succeeds, `{ :error, { line, error, token } }` otherwise.
## Options
@@ -209,25 +219,27 @@ defmodule Code do
* `:existing_atoms_only` - When `true`, raises an error
when non-existing atoms are found by the tokenizer.
## Macro.to_string/1
## Macro.to_string/2
The opposite of converting a string to its quoted form is
`Macro.to_string/1`, which converts a quoted form to a string/binary
`Macro.to_string/2`, which converts a quoted form to a string/binary
representation.
"""
def string_to_quoted(string, opts // []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms(to_char_list(string), line, file, opts)
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
case res do
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
{ :ok, forms } -> { :ok, forms }
_ -> res
end
end
@doc """
Converts the given string to its quoted form. It returns the ast if it succeeds,
Convert the given string to its quoted form.
It returns the ast if it succeeds,
raises an exception otherwise. The exception is a `TokenMissingError`
in case a token is missing (usually because the expression is incomplete),
`SyntaxError` otherwise.
@@ -237,16 +249,13 @@ defmodule Code do
def string_to_quoted!(string, opts // []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms!(to_char_list(string), line, file, opts)
unpack_quote(line, res)
:elixir.string_to_quoted!(to_char_list(string), line, file, opts)
end
defp unpack_quote(_line, []), do: nil
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
@doc """
Loads the given `file`. Accepts `relative_to` as an argument to tell where
Load 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 a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
module defined in the file.
@@ -266,9 +275,11 @@ defmodule Code do
end
@doc """
Requires the given `file`. Accepts `relative_to` as an argument to tell where
Require the given `file`.
Accepts `relative_to` as an argument to tell where
the file is located. The return value is the same as that of `load_file/2`. If
the file was already required/loaded, doesn't do anything and returns nil.
the file was already required/loaded, doesn't do anything and returns `nil`.
Notice that if `require_file` is invoked by different processes concurrently,
the first process to invoke `require_file` acquires a lock and the remaining
@@ -294,7 +305,8 @@ defmodule Code do
end
@doc """
Loads the compilation options from the code server.
Load the compilation options from the code server.
Check `compiler_options/1` for more information.
"""
def compiler_options do
@@ -302,8 +314,9 @@ defmodule Code do
end
@doc """
Sets compilation options. These options are global
since they are stored by Elixir's Code Server.
Set compilation options.
These options are global since they are stored by Elixir's Code Server.
Available options are:
@@ -312,7 +325,7 @@ defmodule Code do
* `: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;
code, `false` by default;
* `:ignore_module_conflict` - when `true`, override modules that were already defined
without raising errors, `false` by default;
@@ -325,28 +338,34 @@ defmodule Code do
end
@doc """
Compiles the given string and returns a list of tuples where
Compile the given string.
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.files/2`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
:elixir_compiler.string String.to_char_list!(string), file
:elixir_compiler.string to_char_list(string), file
end
@doc """
Compiles the quoted expression and returns a list of tuples where
Compile the quoted expression.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
"""
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
:elixir_compiler.quoted [quoted], file
:elixir_compiler.quoted quoted, 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 yet loaded,
Ensure the given module is loaded.
If the module is already
loaded, this works as no-op. If the module was not yet loaded,
it tries to load it.
If it succeeds loading the module, it returns
@@ -362,7 +381,7 @@ defmodule Code do
modules need to be loaded upfront or explicitly.
Therefore, this function is used to check if a module is loaded
before using it and allows one to react accordingly. For example, the `URI`
before using it and allows one to react accordingly. For example, the `URI`
module uses this function to check if a specific parser exists for a given
URI scheme.
@@ -387,17 +406,21 @@ defmodule Code do
end
@doc """
Similar to `ensure_loaded/1`, but returns a boolean in case
it could be ensured or not.
Ensure the given module is loaded.
Similar to `ensure_loaded/1`, but returns `true` if the module
is already loaded or was successfully loaded. Returns `false` otherwise.
"""
def ensure_loaded?(module) do
match?({ :module, ^module }, ensure_loaded(module))
end
@doc """
Ensures the given module is compiled and loaded. If the module
Ensure 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
loaded yet, it checks if it needs to be compiled first and
then tries to load it.
If it succeeds loading the module, it returns
@@ -425,8 +448,11 @@ defmodule Code do
end
@doc """
Similar to `ensure_compiled/1`, but returns a boolean in case
it could be ensured or not.
Ensure the given module is compiled and loaded.
Similar to `ensure_compiled/1`, but returns `true` if the module
is already loaded or was successfully loaded and compiled.
Returns `false` otherwise.
"""
def ensure_compiled?(module) do
match?({ :module, ^module }, ensure_compiled(module))
+9 -5
View File
@@ -31,6 +31,11 @@ defmodule Dict do
IO.puts "#{k}: #{v}"
end)
## Match
Dictionaries are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
"""
use Behaviour
@@ -65,7 +70,6 @@ defmodule Dict do
defcallback update(t, key, value, (value -> value)) :: t
defcallback update!(t, key, (value -> value)) :: t | no_return
defcallback values(t) :: list(value)
defcallback reduce(t, any, ({key, value}, any -> any)) :: any
defmacrop target(dict) do
quote do
@@ -97,7 +101,7 @@ defmodule Dict do
end
@doc """
Returns a list of all values in `dict`
Returns a list of all values in `dict`.
The values are not guaranteed to be in any order.
## Examples
@@ -420,7 +424,7 @@ defmodule Dict do
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included.
included.
Any non-member keys are ignored.
## Examples
@@ -458,8 +462,8 @@ defmodule Dict do
end
@doc """
Check if two dicts are equal. If the dicts are of different types, they are
first converted to lists.
Check if two dicts are equal using `===`. If the dicts are
of different types, they are first converted to lists.
## Examples
+45 -41
View File
@@ -2,7 +2,7 @@ defmodule Dict.Behaviour do
@moduledoc """
This module makes it easier to create your own `Dict` compliant
module, by providing default implementations for some required functions.
Usage:
defmodule MyDict do
@@ -13,16 +13,16 @@ defmodule Dict.Behaviour do
# override default implementations if needed
end
The client module must contain following functions: `size/1`, `fetch/2`,
The client module must contain following functions: `size/1`, `fetch/2`,
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
the Dict behaviour, so no extra functions are actually required.
Based on these functions, `Dict.Behaviour` generates default implementations
Based on these functions, `Dict.Behaviour` generates default implementations
for other functions such as `drop`, `take`, etc. All of the functions are
defined as overridable, so you can provide your own implementation if
needed.
If you implement `new/0` and `new/1` functions, you can also test your custom
If you implement `new/0` and `new/1` functions, you can also test your custom
module via `Dict` doctests:
defmodule MyDict do
@@ -38,49 +38,42 @@ defmodule Dict.Behaviour do
end
"""
defmacro __using__(_) do
quote do
@behaviour Dict
def get(dict, key, default // nil) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> default
end
end
defoverridable get: 2, get: 3
def fetch!(dict, key) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
:error -> raise KeyError, key: key
end
end
defoverridable fetch!: 2
def has_key?(dict, key) do
match? { :ok, _ }, fetch(dict, key)
end
defoverridable has_key?: 2
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
end
defoverridable put_new: 3
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
end
defoverridable drop: 2
def take(dict, keys) do
take(dict, keys, new)
end
defoverridable take: 2
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
@@ -88,38 +81,49 @@ defmodule Dict.Behaviour do
:error -> take(dict, keys, acc)
end
end
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
defoverridable to_list: 1
def keys(dict), do: reduce(dict, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
defoverridable keys: 1
def values(dict), do: reduce(dict, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
defoverridable values: 1
def to_list(dict) do
reduce(dict, { :cont, [] }, fn
kv, acc -> { :cont, [kv|acc] }
end) |> elem(1) |> :lists.reverse
end
def keys(dict) do
reduce(dict, { :cont, [] }, fn
{k, _}, acc -> { :cont, [k|acc] }
end) |> elem(1) |> :lists.reverse
end
def values(dict) do
reduce(dict, { :cont, [] }, fn
{_, v}, acc -> { :cont, [v|acc] }
end) |> elem(1) |> :lists.reverse
end
def equal?(dict1, dict2) do
case Dict.size(dict1) == Dict.size(dict2) do
import Kernel, except: [size: 1]
case size(dict1) == size(dict2) do
false -> false
true ->
try do
reduce(dict1, nil, fn({ k, v }, _acc) ->
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
end)
true
catch
:error -> false
end
true ->
reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
unless fetch(dict2, k) == { :ok, v } do
{ :halt, false }
else
{ :cont, true }
end
end) |> elem(1)
end
end
defoverridable equal?: 2
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
end)
end
defoverridable merge: 2, merge: 3
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
has_key?: 2, put_new: 3
end
end
end
+986 -805
View File
File diff suppressed because it is too large Load Diff
+68 -13
View File
@@ -1,3 +1,7 @@
# Some exceptions implement `message/1` instead of `exception/1` mostly
# for bootstrap reasons. It is recommended for applications to implement
# `exception/1` instead of `message/1` as described in `defexception/3` docs.
defexception RuntimeError, message: "runtime error"
defexception ArgumentError, message: "argument error"
defexception ArithmeticError, message: "bad argument in arithmetic expression"
@@ -39,9 +43,16 @@ defexception CaseClauseError, [actual: nil] do
end
end
defexception TryClauseError, [actual: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.actual)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
args = Enum.map_join(exception.args, ", ", &inspect/1)
"bad arity error: #{inspect(exception.function)} called with (#{args})"
end
end
@@ -94,25 +105,56 @@ defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.EmptyError, message: "empty error"
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
"could not #{action exception} #{path exception}: #{formatted}"
end
end
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
location = if on = on(exception), do: ". #{on}", else: ""
"could not #{action exception} from #{source exception} to " <>
"#{destination exception}#{location}: #{formatted}"
end
end
defmodule Exception do
@moduledoc """
Several convenience functions to work with and pretty print
Convenience functions to work with and pretty print
exceptions and stacktraces.
Notice that stacktraces in Elixir are updated on errors.
For example, at any given moement, `System.stacktrace`
will return the stacktrace for the last error that ocurred
in the current process.
That said, many of the functions in this module will
automatically calculate the stacktrace based on the caller,
when invoked without arguments, changing the value of
`System.stacktrace`. If instead you want to format the
stacktrace of the latest error, you should instead explicitly
pass the `System.stacktrace` as argument.
"""
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions. It takes the kind spilled by
`catch` as an argument as a convenience for converting only
`:errors`, ignorning the others.
to Elixir exceptions.
It takes the `kind` spilled by `catch` as an argument and
normalizes only `:error`, returning the untouched payload
for others.
"""
def normalize(:error, exception), do: normalize(exception)
def normalize(_kind, other), do: other
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions. Useful when interfacing Erlang
code with Elixir code.
to Elixir exceptions.
Useful when interfacing Erlang code with Elixir code.
"""
def normalize(exception) when is_exception(exception) do
exception
@@ -146,6 +188,10 @@ defmodule Exception do
CaseClauseError[actual: actual]
end
def normalize({ :try_clause, actual }) do
TryClauseError[actual: actual]
end
def normalize(:undef) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
@@ -178,12 +224,12 @@ defmodule Exception do
end
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
def format_stacktrace_entry({ _module, :__MODULE__, 1, file_line }, cwd) do
"#{format_location(file_line, cwd)}(module)"
end
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
def format_stacktrace_entry({ _module, :__FILE__, 1, file_line }, cwd) do
"#{format_location(file_line, cwd)}(file)"
end
@@ -199,8 +245,8 @@ defmodule Exception do
Formats the stacktrace.
A stacktrace must be given as an argument. If not, this function
calculates the current stacktrace and formats it. As a consequence,
the value of `System.stacktrace` is changed.
calculates a new stacktrace based on the caller and formats it. As
a consequence, the value of `System.stacktrace` is changed.
"""
def format_stacktrace(trace // nil) do
trace = trace || try do
@@ -219,6 +265,11 @@ defmodule Exception do
@doc """
Formats the caller, i.e. the first entry in the stacktrace.
A stacktrace must be given as an argument. If not, this function
calculates a new stacktrace based on the caller and formats it. As
a consequence, the value of `System.stacktrace` is changed.
Notice that due to tail call optimization, the stacktrace
may not report the direct caller of the function.
"""
@@ -266,6 +317,8 @@ defmodule Exception do
"Foo.bar/1"
iex> Exception.format_mfa Foo, :bar, []
"Foo.bar()"
iex> Exception.format_mfa nil, :bar, []
"nil.bar()"
"""
def format_mfa(module, fun, arity) do
@@ -277,12 +330,14 @@ defmodule Exception do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect module}.#{fun}/#{arity}"
"#{format_module module}#{fun}/#{arity}"
end
end
defp format_module(mod), do: "#{inspect mod}."
@doc """
Formats the given file and line as shown in stacktraces.
If any of the values are nil, they are omitted.
+67 -48
View File
@@ -31,22 +31,6 @@ defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.h
"""
end
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
"could not #{action exception} #{path exception}: #{formatted}"
end
end
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
location = if on = on(exception), do: ". #{on}", else: ""
"could not #{action exception} from #{source exception} to " <>
"#{destination exception}#{location}: #{formatted}"
end
end
defmodule File do
@moduledoc """
This module contains functions to manipulate files.
@@ -63,9 +47,9 @@ defmodule File do
in the `IO` module. By default, a file is opened in binary mode
which requires the functions `IO.binread/2` and `IO.binwrite/2`
to interact with the file. A developer may pass `:utf8` as an
option when opening the file and then all other functions
from `IO` are available, since they work directly with Unicode
data.
option when opening the file, then the slower `IO.read/2` and
`IO.write/2` functions must be used as they are responsible for
doing the proper conversions and data guarantees.
Most of the functions in this module return `:ok` or
`{ :ok, result }` in case of success, `{ :error, reason }`
@@ -101,7 +85,7 @@ defmodule File do
## Examples
File.regular? __FILE__ #=> true
File.regular? __ENV__.file #=> true
"""
def regular?(path) do
@@ -481,7 +465,7 @@ defmodule File do
case F.list_dir(src) do
{ :ok, files } ->
case mkdir(dest) do
success in [:ok, { :error, :eexist }] ->
success when success in [:ok, { :error, :eexist }] ->
Enum.reduce(files, [dest|acc], fn(x, acc) ->
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
end)
@@ -560,6 +544,8 @@ defmodule File do
* :enospc - There is a no space left on the device.
* :eacces - Missing permission for writing the file or searching one of the parent directories.
* :eisdir - The named file is a directory.
Check `File.open/2` for existing modes.
"""
def write(path, content, modes // []) do
F.write_file(path, content, modes)
@@ -968,39 +954,72 @@ defmodule File do
end
@doc """
Opens the given `file` with the given `mode` and returns
a stream for each `:line` (default) or for a given number
Opens the file at the given `path` with the given `modes` and
returns a stream for each `:line` (default) or for a given number
of bytes given by `line_or_bytes`.
The returned stream will fail for the same reasons as `File.open!`.
Note that the file is opened only when streaming begins.
The returned stream will fail for the same reasons as
`File.open!/2`. Note that the file is opened only and every time
streaming begins.
Note that stream by default uses `IO.binread/2` unless
the file is opened with an encoding, then the slower `IO.read/2`
is used to do the proper data conversion and guarantees.
"""
def stream!(file, mode // [], line_or_bytes // :line) do
fn(acc, fun) ->
device = open!(file, mode)
try do
IO.stream(device, line_or_bytes, acc, fun)
after
F.close(device)
def stream!(path, modes // [], line_or_bytes // :line) do
modes = open_defaults(modes, true)
bin = nil? List.keyfind(modes, :encoding, 0)
start_fun =
fn ->
case F.open(path, modes) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "stream", path: to_string(path)
end
end
end
next_fun =
case bin do
true -> &IO.do_binstream(&1, line_or_bytes)
false -> &IO.do_stream(&1, line_or_bytes)
end
Stream.resource(start_fun, next_fun, &F.close/1)
end
@doc """
Opens the given `file` with the given `mode` and returns
a binstream for each `:line` (default) or for a given number
of bytes given by `line_or_bytes`.
Receives a stream and returns a new stream that will open the file
at the given `path` for write with the extra `modes` and write
each value to the file.
The returned stream will fail for the same reasons as `File.open!`.
Note that the file is opened only when streaming begins.
The returned stream will fail for the same reasons as
`File.open!/2`. Note that the file is opened only and every time
streaming begins.
Note that stream by default uses `IO.binwrite/2` unless
the file is opened with an encoding, then the slower `IO.write/2`
is used to do the proper data conversion and guarantees.
"""
def binstream!(file, mode // [], line_or_bytes // :line) do
fn(fun, acc) ->
device = open!(file, mode)
try do
IO.binstream(device, line_or_bytes, fun, acc)
after
F.close(device)
def stream_to!(stream, path, modes // []) do
modes = open_defaults([:write|List.delete(modes, :write)], true)
bin = nil? List.keyfind(modes, :encoding, 0)
fn acc, f ->
case F.open(path, modes) do
{ :ok, device } ->
each =
case bin do
true -> &IO.binwrite(device, &1)
false -> &IO.write(device, &1)
end
stream
|> Stream.each(each)
|> Stream.after(fn -> F.close(device) end)
|> Enumerable.Stream.Lazy.reduce(acc, f)
{ :error, reason } ->
raise File.Error, reason: reason, action: "stream_to", path: to_string(path)
end
end
end
@@ -1046,7 +1065,7 @@ defmodule File do
end
@doc """
Changes the owner given by the user id `gid`
Changes the owner given by the user id `uid`
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
"""
@@ -1057,8 +1076,8 @@ defmodule File do
@doc """
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def chown!(file, gid) do
case chown(file, gid) do
def chown!(file, uid) do
case chown(file, uid) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change owner for", path: to_string(file)
+167
View File
@@ -0,0 +1,167 @@
defmodule Float do
@moduledoc """
Functions for working with floating point numbers.
"""
@doc """
Parses a binary into a float.
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
Otherwise `:error`.
## Examples
iex> Float.parse("34")
{34.0,""}
iex> Float.parse("34.25")
{34.25,""}
iex> Float.parse("56.5xyz")
{56.5,"xyz"}
iex> Float.parse("pi")
:error
"""
@spec parse(binary) :: { float, binary } | :error
def parse("-" <> binary) do
case parse_unsign(binary) do
:error -> :error
{ number, remainder } -> { -number, remainder }
end
end
def parse(binary) do
parse_unsign(binary)
end
defp parse_unsign("-" <> _), do: :error
defp parse_unsign(binary) when is_binary(binary) do
case Integer.parse binary do
:error -> :error
{ integer_part, after_integer } -> parse_unsign after_integer, integer_part
end
end
# Dot followed by digit is required afterwards or we are done
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
parse_unsign(rest, char - ?0, 1, int)
end
defp parse_unsign(rest, int) do
{ :erlang.float(int), rest }
end
# Handle decimal points
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
end
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
case Integer.parse after_e do
:error ->
# Note we rebuild the binary here instead of breaking it apart at
# the function clause because the current approach copies a binary
# just on this branch. If we broke it apart in the function clause,
# the copy would happen when calling Integer.parse/1.
{ floatify(int, float, decimal), << ?e, after_e :: binary >> }
{ exponential, after_exponential } ->
{ floatify(int, float, decimal, exponential), after_exponential }
end
end
defp parse_unsign(bitstring, float, decimal, int) do
{ floatify(int, float, decimal), bitstring }
end
defp floatify(int, float, decimal, exponential // 0) do
multiplier = if int < 0, do: -1.0, else: 1.0
# Try to ensure the minimum amount of rounding errors
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
# Try avoiding stuff like this:
# iex(1)> 0.0001 * 75
# 0.007500000000000001
# Due to IEEE 754 floating point standard
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
final_decimal_places = decimal - exponential
if final_decimal_places > 0 do
decimal_power_round = :math.pow(10, final_decimal_places)
trunc(result * decimal_power_round) / decimal_power_round
else
result
end
end
@doc """
Round a float to the largest integer less than or equal to `num`
## Examples
iex> Float.floor(34)
34
iex> Float.floor(34.25)
34
iex> Float.floor(-56.5)
-57
"""
@spec floor(float | integer) :: integer
def floor(num) when is_integer(num), do: num
def floor(num) when is_float(num) do
truncated = :erlang.trunc(num)
case :erlang.abs(num - truncated) do
x when x > 0 and num < 0 -> truncated - 1
_ -> truncated
end
end
@doc """
Round a float to the largest integer greater than or equal to `num`
## Examples
iex> Float.ceil(34)
34
iex> Float.ceil(34.25)
35
iex> Float.ceil(-56.5)
-56
"""
@spec ceil(float | integer) :: integer
def ceil(num) when is_integer(num), do: num
def ceil(num) when is_float(num) do
truncated = :erlang.trunc(num)
case :erlang.abs(num - truncated) do
x when x > 0 and num > 0 -> truncated + 1
_ -> truncated
end
end
@doc """
Rounds a floating point value to an arbitrary number of fractional digits
(between 0 and 15) with an optional midpoint rounding mode (:up or :down,
defaults to :up).
## Examples
iex> Float.round(5.5675, 3)
5.568
iex> Float.round(5.5675, 3, :down)
5.567
iex> Float.round(-5.5675, 3)
-5.567
iex> Float.round(-5.5675, 3, :down)
-5.568
"""
@spec round(float, integer, atom | nil) :: float
def round(number, precision, midpoint_rounding // :up) when is_float(number) and is_integer(precision) and precision in 0..15 do
# Default to :up if anything but :down is provided for midpoint rounding mode
case midpoint_rounding do
:down -> Kernel.round(Float.floor(number * :math.pow(10, precision))) / :math.pow(10, precision)
_ -> Kernel.round(Float.ceil(number * :math.pow(10, precision))) / :math.pow(10, precision)
end
end
end
-1
View File
@@ -25,7 +25,6 @@ defmodule GenEvent.Behaviour do
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
{ :ok, pid } = :gen_event.start_link
+65 -76
View File
@@ -21,78 +21,67 @@ defmodule GenFSM.Behaviour do
works - we hereby declare a full disclaimer for any lawsuits that the
behaviour of the CVM in its original state might encur.
defmodule MyFsm do
use GenFSM.Behaviour
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
#
# API functions
#
# API functions
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
#
# Callbacks
#
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p])
when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{:next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
# Callbacks
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
end
{ :ok, _pid } = MyFsm.start_link()
@@ -138,15 +127,15 @@ defmodule GenFSM.Behaviour do
for you. The list of callbacks are:
* `init(args)` - invoked when the FSM is started;
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
handle `sync_send_all_state_event` messages;
* `handle_event(event, state_name, state_data)` - invoked to handle
* `handle_event(event, state_name, state_data)` - invoked to handle
`send_all_state_event` messages;
* `handle_info(msg, state_name, state_data)` - handle all other
* `handle_info(msg, state_name, state_data)` - handle all other
messages which are normally received by processes;
* `terminate(reason, state_name, state_data)` - called when the FSM
* `terminate(reason, state_name, state_data)` - called when the FSM
is about to terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
@@ -154,9 +143,9 @@ defmodule GenFSM.Behaviour do
For each state you need to define either or both of these:
* `state_name(event, state_data)` - invoked to handle
* `state_name(event, state_data)` - invoked to handle
`send_event` messages;
* `state_name(event, from, state_data)`- invoked to handle
* `state_name(event, from, state_data)`- invoked to handle
`sync_send_event` messages;
If you send asynchronous events you only need to implement the
@@ -180,13 +169,13 @@ defmodule GenFSM.Behaviour do
@behavior :gen_fsm
@doc false
def handle_event(_event, state_name, state_data) do
{ :next_state, state_name, state_data }
def handle_event(event, state_name, state_data) do
{ :stop, {:bad_event, state_name, event}, state_data }
end
@doc false
def handle_sync_event(_event, from, state_name, state_data) do
{ :reply, :default_implementation, state_name, state_data }
def handle_sync_event(event, from, state_name, state_data) do
{ :stop, {:bad_sync_event, state_name, event}, state_data }
end
@doc false
+4 -4
View File
@@ -94,8 +94,8 @@ defmodule GenServer.Behaviour do
end
@doc false
def handle_call(_request, _from, state) do
{ :noreply, state }
def handle_call(request, _from, state) do
{ :stop, { :bad_call, request }, state }
end
@doc false
@@ -104,8 +104,8 @@ defmodule GenServer.Behaviour do
end
@doc false
def handle_cast(_msg, state) do
{ :noreply, state }
def handle_cast(msg, state) do
{ :stop, { :bad_cast, msg }, state }
end
@doc false
+28 -18
View File
@@ -188,7 +188,8 @@ defmodule HashDict do
end
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size)
node_reduce(root, depth, acc, fun, @node_size,
fn { :cont, acc }, _f -> { :done, acc } end)
end
## Dict-wide functions
@@ -328,13 +329,10 @@ defmodule HashDict do
end
# Reduces the bucket
defp bucket_reduce([[k|v]|t], acc, fun) do
bucket_reduce(t, fun.({ k, v }, acc), fun)
end
defp bucket_reduce([], acc, _fun) do
acc
end
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
defp bucket_reduce([[k|v]|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.({ k, v }, acc), fun)
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
@@ -399,17 +397,29 @@ defmodule HashDict do
end
# Reduces a node recursively
defp node_reduce(bucket, -1, acc, fun, _) do
bucket_reduce(bucket, acc, fun)
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
{ :halted, acc }
end
defp node_reduce(node, depth, acc, fun, count) when count >= 1 do
acc = node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_reduce(node, depth, acc, fun, count - 1)
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
end
defp node_reduce(_node, _, acc, _fun, 0) do
acc
defp node_reduce([[k|v]|t], -1, { :cont, acc }, fun, _count, next) do
node_reduce(t, -1, fun.({ k, v }, acc), fun, _count, next)
end
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
next.(acc, fun)
end
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
&node_reduce(node, depth, &1, &2, count - 1, next))
end
defp node_reduce(_node, _, acc, fun, 0, next) do
next.(acc, fun)
end
# Folds a node recursively
@@ -468,9 +478,9 @@ end
defimpl Enumerable, for: HashDict do
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
def member?(_dict, _), do: false
def count(dict), do: HashDict.size(dict)
def member?(dict, { k, v }), do: { :ok, match?({ :ok, ^v }, HashDict.fetch(dict, k)) }
def member?(_dict, _), do: { :ok, false }
def count(dict), do: { :ok, HashDict.size(dict) }
end
defimpl Access, for: HashDict do
+50 -32
View File
@@ -136,11 +136,12 @@ defmodule HashSet do
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
:lists.foldl(fun, acc, bucket)
bucket_reduce(bucket, acc, fun)
end
def reduce(trie() = set, acc, fun) do
set_fold(set, acc, fun)
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size,
fn { :cont, acc }, _f -> { :done, acc } end)
end
## HashSet-wide functions
@@ -225,29 +226,21 @@ defmodule HashSet do
end
defp set_equal?(set1, set2) do
try do
reduce(set1, true, fn member, acc ->
case member?(set2, member) do
true -> acc
_ -> throw(:error)
end
end)
catch
:error -> false
end
reduce(set1, { :cont, true }, fn member, acc ->
case member?(set2, member) do
true -> { :cont, acc }
_ -> { :halt, false }
end
end) |> elem(1)
end
defp set_disjoint?(set1, set2) do
try do
reduce(set1, true, fn member, acc ->
case member?(set2, member) do
false -> acc
_ -> throw(:error)
end
end)
catch
:error -> false
end
reduce(set1, { :cont, true }, fn member, acc ->
case member?(set2, member) do
false -> { :cont, acc }
_ -> { :halt, false }
end
end) |> elem(1)
end
defp set_fold(ordered(bucket: bucket), acc, fun) do
@@ -311,6 +304,11 @@ defmodule HashSet do
{ [], 0 }
end
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
defp bucket_reduce([h|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.(h, acc), fun)
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
@@ -396,6 +394,33 @@ defmodule HashSet do
end
end
# Reduces a node recursively
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
{ :halted, acc }
end
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
end
defp node_reduce([h|t], -1, { :cont, acc }, fun, _count, next) do
node_reduce(t, -1, fun.(h, acc), fun, _count, next)
end
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
next.(acc, fun)
end
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
&node_reduce(node, depth, &1, &2, count - 1, next))
end
defp node_reduce(_node, _, acc, fun, 0, next) do
next.(acc, fun)
end
# Folds a node recursively
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
@@ -476,17 +501,10 @@ defmodule HashSet do
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
@doc false
def inspect_depth(trie(depth: d)), do: d
@doc false
def inspect_contract(trie(contract_on: c)), do: c
@doc false
def inspect_expand(trie(expand_on: e)), do: e
end
defimpl Enumerable, for: HashSet do
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
def member?(set, v), do: HashSet.member?(set, v)
def count(set), do: HashSet.size(set)
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
def count(set), do: { :ok, HashSet.size(set) }
end
+45 -22
View File
@@ -19,6 +19,8 @@ defprotocol Inspect do
receives an `Inspect.Opts` record as the second argument, it returns
the underlying algebra document instead of the formatted string.
## Examples
Many times, inspecting a structure can be implemented in function
of existing entities. For example, here is `HashSet`'s `inspect`
implementation:
@@ -41,8 +43,18 @@ defprotocol Inspect do
Since regular strings are valid entities in an algebra document,
an implementation of inspect may simply return a string,
although that will devoid it of any pretty-printing.
"""
## Error handling
In case there is an error while your structure is being inspected,
Elixir will automatically default to the raw inspecting. You can
however access the underlying error by invoking the Inspect
implementation directly. For example, to test Inspect.HashSet above,
you just need to do:
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
"""
def inspect(thing, opts)
end
@@ -326,11 +338,7 @@ defimpl Inspect, for: Tuple do
[name|tail] = tuple_to_list(record)
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if Enum.first(tail) == :__exception__ do
surround_record(name, tl(fields), tl(tail), opts)
else
surround_record(name, fields, tail, opts)
end
surround_record(name, fields, tail, opts)
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
end
@@ -347,26 +355,45 @@ defimpl Inspect, for: Tuple do
end
defp surround_record(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, &keyword(&1, opts))
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
)
end
defp zip_fields([{ key, _ }|tk], [value|tv]) do
case atom_to_binary(key) do
"_" <> _ -> zip_fields(tk, tv)
key -> [{ key, value }|zip_fields(tk, tv)]
end
end
defp zip_fields([], []) do
[]
end
defp keyword({ k, v }, opts) do
concat(
atom_to_binary(k, :utf8) <> ": ",
Kernel.inspect(v, opts)
)
concat(k <> ": ", Kernel.inspect(v, opts))
end
end
defimpl Inspect, for: Number do
defimpl Inspect, for: Integer do
@doc """
Represents the number as a string.
Represents the integer as a string.
## Examples
iex> inspect(1)
"1"
"""
def inspect(thing, _opts) do
integer_to_binary(thing)
end
end
defimpl Inspect, for: Float do
@doc """
Floats are represented using the shortened, correctly rounded string
that converts to float when read back with `binary_to_float/1`. This
is done via the Erlang implementation of _Printing Floating-Point
@@ -375,14 +402,10 @@ defimpl Inspect, for: Number do
## Examples
iex> inspect(1)
"1"
iex> inspect(1.0)
"1.0"
"""
def inspect(thing, _opts) when is_integer(thing) do
integer_to_binary(thing)
end
def inspect(thing, _opts) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
@@ -446,7 +469,7 @@ end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> iolist_to_binary(pid_to_list(pid))
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
end
end
+32 -43
View File
@@ -71,9 +71,6 @@ defmodule Inspect.Algebra do
@nesting 1
@break " "
defp repeat(_, 0), do: ""
defp repeat(s, i), do: :lists.duplicate(i, s)
# Functional interface to `doc` records
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
@@ -96,10 +93,9 @@ defmodule Inspect.Algebra do
defp do_is_doc(doc) do
quote do
unquote(doc) |> is_binary or
unquote(doc) |> is_integer or
is_binary(unquote(doc)) or
unquote(doc) == :doc_nil or
(unquote(doc) |> is_tuple and
(is_tuple(unquote(doc)) and
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
end
end
@@ -273,7 +269,7 @@ defmodule Inspect.Algebra do
"A!B"
"""
@spec folddoc([any], ((any, [t]) -> t)) :: t
@spec folddoc([t], ((t, t) -> t)) :: t
def folddoc([], _), do: empty
def folddoc([doc], _), do: doc
def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))
@@ -296,7 +292,7 @@ defmodule Inspect.Algebra do
"""
@spec surround(binary, t, binary) :: t
def surround(left, doc, right) do
group concat [left, nest(doc, @nesting), right]
group concat left, concat(nest(doc, @nesting), right)
end
@doc %S"""
@@ -314,32 +310,38 @@ defmodule Inspect.Algebra do
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
iex> Inspect.Algebra.pretty(doc, 20)
"[1! 2! 3! ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
def surround_many(left, [], right, _, _fun) do
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
def surround_many(left, docs, right, limit, fun, separator // @surround_separator)
def surround_many(left, [], right, _, _fun, _) do
concat(left, right)
end
def surround_many(left, docs, right, limit, fun) do
surround(left, surround_many(docs, limit, fun), right)
def surround_many(left, docs, right, limit, fun, sep) do
surround(left, surround_many(docs, limit, fun, sep), right)
end
defp surround_many(_, 0, _fun) do
defp surround_many(_, 0, _fun, _sep) do
"..."
end
defp surround_many([h], _limit, fun) do
defp surround_many([h], _limit, fun, _sep) do
fun.(h)
end
defp surround_many([h|t], limit, fun) when is_list(t) do
defp surround_many([h|t], limit, fun, sep) when is_list(t) do
glue(
concat(fun.(h), @surround_separator),
surround_many(t, decrement(limit), fun)
concat(fun.(h), sep),
surround_many(t, decrement(limit), fun, sep)
)
end
defp surround_many([h|t], _limit, fun) do
defp surround_many([h|t], _limit, fun, _sep) do
glue(
concat(fun.(h), @tail_separator),
fun.(t)
@@ -367,57 +369,44 @@ defmodule Inspect.Algebra do
# Rendering and internal helpers
# Records representing __simple__ documents, already on a fixed layout
# Those are generalized by `sdoc` type.
@type sdoc :: :s_nil | s_text_t | s_line_t
defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc
# Record representing the document mode to be rendered: flat or broken
@typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
def fits?(:infinity, _), do: true # no pretty printing
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
@doc false
@spec format(integer, integer, [{ integer, mode, t }]) :: atom | tuple
def format(_, _, []), do: :s_nil
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: [binary]
def format(_, _, []), do: []
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: [s | format(w, (k + byte_size s), t)]
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
k = k + byte_size(s)
if fits?(w - k, t) do
s_text(str: s, sdoc: format(w, k, t))
if w == :infinity or fits?(w - k, t) do
[s | format(w, k, t)]
else
s_line(indent: i, sdoc: format(w, i, t))
prefix = :binary.copy(" ", i)
[@newline, prefix | format(w, i, t)]
end
end
@doc false
@spec render(sdoc) :: binary
@spec render([binary]) :: binary
def render(sdoc) do
iolist_to_binary do_render sdoc
end
@spec do_render(sdoc) :: [binary]
defp do_render(:s_nil), do: [""]
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
defp do_render(s_line(indent: i, sdoc: d)) do
prefix = repeat " ", i
[@newline | [prefix | do_render d]]
iolist_to_binary sdoc
end
end
+55 -4
View File
@@ -3,17 +3,68 @@ defmodule Integer do
Functions for working with integers.
"""
import Bitwise
@doc """
Returns true if `n` is an odd number, otherwise false.
Determines if an integer is odd.
Returns `true` if `n` is an odd number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
"""
defmacro odd?(n) do
quote do: rem(unquote(n), 2) != 0
quote do: (unquote(n) &&& 1) == 1
end
@doc """
Returns true if `n` is an even number, otherwise false.
Determines if an integer is even.
Returns `true` if `n` is an even number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
"""
defmacro even?(n) do
quote do: rem(unquote(n), 2) == 0
quote do: (unquote(n) &&& 1) == 0
end
@doc """
Converts a binary to an integer.
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
Otherwise `:error`.
## Examples
iex> Integer.parse("34")
{34,""}
iex> Integer.parse("34.5")
{34,".5"}
iex> Integer.parse("three")
:error
"""
@spec parse(binary) :: { integer, binary } | :error
def parse(<< ?-, bin :: binary >>) do
case do_parse(bin) do
:error -> :error
{ number, remainder } -> { -number, remainder }
end
end
def parse(<< ?+, bin :: binary >>) do
do_parse(bin)
end
def parse(bin) when is_binary(bin) do
do_parse(bin)
end
defp do_parse(<< char, bin :: binary >>) when char in ?0..?9, do: do_parse(bin, char - ?0)
defp do_parse(_), do: :error
defp do_parse(<< char, rest :: binary >>, acc) when char in ?0..?9 do
do_parse rest, 10 * acc + (char - ?0)
end
defp do_parse(bitstring, acc) do
{ acc, bitstring }
end
end
+56 -37
View File
@@ -1,35 +1,33 @@
defexception IO.StreamError, reason: nil do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
"error during streaming: #{formatted}"
defexception IO.StreamError, [:reason, :message] do
def exception(opts) do
reason = opts[:reason]
formatted = iolist_to_binary(:file.format_error(reason))
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
end
end
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.
Use the bin* functions if the data is binary, useful when
working with raw bytes or when no unicode conversion should
be performed.
Functions handling IO.
Many functions in this module expects an IO device as argument.
An IO device must be a pid or an atom representing a process.
For convenience, Elixir provides `:stdio` and `:stderr` as
shortcuts to Erlang's `:standard_io` and `:standard_error`.
An io data can be:
The majority of the functions expect data encoded in UTF-8
and will do a conversion to string, via the `String.Chars`
protocol (as shown in typespecs).
* 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);
The functions starting with `bin*` expects iodata as arguments,
i.e. iolists or binaries with no particular encoding.
"""
@type device :: atom | pid
@type chardata :: char_list | String.Chars.t
@type nodata :: { :error, term } | :eof
import :erlang, only: [group_leader: 0]
defmacrop is_iolist(data) do
@@ -50,6 +48,7 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
def read(device // group_leader, chars_or_line)
def read(device, :line) do
@@ -72,6 +71,7 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
def binread(device // group_leader, chars_or_line)
def binread(device, :line) do
@@ -105,8 +105,9 @@ defmodule IO do
#=> "error"
"""
def write(device // group_leader(), item) when is_iolist(item) do
:io.put_chars map_dev(device), item
@spec write(device, chardata) :: :ok
def write(device // group_leader(), item) do
:io.put_chars map_dev(device), to_chardata(item)
end
@doc """
@@ -115,6 +116,7 @@ defmodule IO do
Check `write/2` for more information.
"""
@spec binwrite(device, iodata) :: :ok | { :error, term }
def binwrite(device // group_leader(), item) when is_iolist(item) do
:file.write map_dev(device), item
end
@@ -124,9 +126,10 @@ defmodule IO do
but adds a newline at the end. The argument is expected
to be a chardata.
"""
def puts(device // group_leader(), item) when is_iolist(item) do
@spec puts(device, chardata) :: :ok
def puts(device // group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [item, ?\n]
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@doc """
@@ -142,6 +145,7 @@ defmodule IO do
IO.inspect Process.list
"""
@spec inspect(term, Keyword.t) :: term
def inspect(item, opts // []) do
inspect group_leader(), item, opts
end
@@ -149,6 +153,7 @@ defmodule IO do
@doc """
Inspects the item with options using the given device.
"""
@spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
@@ -178,6 +183,8 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec getn(chardata, pos_integer) :: chardata | nodata
@spec getn(device, chardata) :: chardata | nodata
def getn(prompt, count // 1)
def getn(prompt, count) when is_integer(count) do
@@ -194,8 +201,9 @@ defmodule IO do
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
"""
@spec getn(device, chardata, pos_integer) :: chardata | nodata
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), prompt, count)
:io.get_chars(map_dev(device), to_chardata(prompt), count)
end
@doc """
@@ -210,18 +218,22 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec gets(device, chardata) :: chardata | nodata
def gets(device // group_leader(), prompt) do
:io.get_line(map_dev(device), prompt)
:io.get_line(map_dev(device), to_chardata(prompt))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line if :line is given or by a given
iterated line by line if `:line` is given or by a given
number of codepoints.
This reads the io as utf-8. Check out
This reads the IO as utf-8. Check out
`IO.binstream/2` to handle the IO as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
## Examples
Here is an example on how we mimic an echo server
@@ -230,41 +242,45 @@ defmodule IO do
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
"""
def stream(device, line_or_bytes) do
fn(acc, f) -> stream(map_dev(device), line_or_bytes, acc, f) end
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line.
iterated line by line or by a number of bytes. This
reads the IO as a raw binary.
This reads the io as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
end
@doc false
def stream(device, what, acc, fun) do
def do_stream(device, what) do
case read(device, what) do
:eof ->
acc
nil
{ :error, reason } ->
raise IO.StreamError, reason: reason
data ->
stream(device, what, fun.(data, acc), fun)
{ data, device }
end
end
@doc false
def binstream(device, what, acc, fun) do
def do_binstream(device, what) do
case binread(device, what) do
:eof ->
acc
nil
{ :error, reason } ->
raise IO.StreamError, reason: reason
data ->
binstream(device, what, fun.(data, acc), fun)
{ data, device }
end
end
@@ -272,4 +288,7 @@ defmodule IO do
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other), do: other
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
end
+18 -17
View File
@@ -16,9 +16,9 @@ end
defmodule IO.ANSI do
@moduledoc """
This module provides functionality to render ANSI escape sequences
Functionality to render ANSI escape sequences
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
in the text used to control formatting, color, and other output options
in text used to control formatting, color, and other output options
on video text terminals.
"""
@@ -28,7 +28,7 @@ defmodule IO.ANSI do
Checks whether the default I/O device is a terminal or a file.
Used to identify whether printing ANSI escape sequences will likely
be printed as intended.
be displayed as intended.
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
@@ -144,18 +144,19 @@ defmodule IO.ANSI do
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
multiple sequences).
It will also append a %{reset} to the string. If you don't want this
behaviour, use `escape_fragment/1` and `escape_fragment/2`.
It will also append a `%{reset}` to the string. If you don't want this
behaviour, use `escape_fragment/2`.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When false, no ANSI codes will emitted.
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/0` function)
of handling these sequences (using `terminal?/1` function)
## Example
## Examples
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes\e[0m"
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes\e[0m"
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit // terminal?) do
@@ -174,16 +175,16 @@ defmodule IO.ANSI do
multiple sequences).
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When false, no ANSI codes will emitted.
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/0` function)
of handling these sequences (using `terminal?/1` function)
## Example
## Examples
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
+377
View File
@@ -0,0 +1,377 @@
defmodule IO.ANSI.Docs do
@moduledoc false
@bullets [?*, ?-, ?+]
@default_colors [ enabled: true,
doc_code: "cyan,bright",
doc_inline_code: "cyan",
doc_headings: "yellow,bright",
doc_title: "reverse,yellow,bright",
doc_bold: "bright",
doc_underline: "underline" ]
@doc """
Prints the head of the documentation (i.e. the function signature).
See `print/3` for docs on the supported options.
"""
def print_heading(heading, colors // @default_colors) do
IO.puts IO.ANSI.reset
width = column_width()
padding = div(width + String.length(heading), 2)
heading = heading |> String.rjust(padding) |> String.ljust(width)
write(:doc_title, heading, colors)
end
@doc """
Prints the documentation body.
In addition to the priting string, takes a truth value for whether to use ANSI
escape codes, and a keyword list for the printing color settings. Supported
keys for the color settings are:
* `:enabled` - toggles coloring on and off (true)
* `:doc_code` - code blocks (cyan, bright)
* `:doc_inline_code` - inline code (cyan)
* `:doc_headings` - h1 and h2 headings (yellow, bright)
* `:doc_title` - top level heading (reverse, yellow, bright)
* `:doc_bold` - bold text (bright)
* `:doc_underline` - underlined text (underline)
Values for the color settings are strings with comma-separated attributes.
Supported attributes are:
* Colors: `black red green yellow blue magenta cyan white`
* Intensity: `normal bright`
* Decoration: `underline reverse`
"""
def print(doc, colors // @default_colors) do
doc
|> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1)
|> process("", colors)
end
defp process([], _indent, _colors), do: nil
defp process(["# " <> heading | rest], _indent, colors) do
write_h1(String.strip(heading), colors)
process(rest, "", colors)
end
defp process(["## " <> heading | rest], _indent, colors) do
write_h2(String.strip(heading), colors)
process(rest, "", colors)
end
defp process(["### " <> heading | rest], indent, colors) do
write_h3(String.strip(heading), indent, colors)
process(rest, indent, colors)
end
defp process(["" | rest], indent, colors) do
process(rest, indent, colors)
end
defp process([" " <> line | rest], indent, colors) do
process_code(rest, [line], indent, colors)
end
defp process([line | rest], indent, colors) do
{ stripped, count } = strip_spaces(line, 0)
case stripped do
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
process_list(item, rest, count, indent, colors)
_ ->
process_text(rest, [line], indent, false, colors)
end
end
defp strip_spaces(" " <> line, acc) do
strip_spaces(line, acc + 1)
end
defp strip_spaces(rest, acc) do
{ rest, acc }
end
## Headings
defp write_h1(heading, colors) do
write_h2(String.upcase(heading), colors)
end
defp write_h2(heading, colors) do
write(:doc_headings, heading, colors)
end
defp write_h3(heading, indent, colors) do
IO.write(indent)
write(:doc_headings, heading, colors)
end
## Lists
defp process_list(line, rest, count, indent, colors) do
IO.write indent <> "• "
{ contents, rest, done } = process_list_next(rest, count, false, [])
process_text(contents, [line], indent <> " ", true, colors)
if done, do: IO.puts(IO.ANSI.reset)
process(rest, indent, colors)
end
# Process the thing after a list item entry. It can be either:
#
# * Continuation of the list
# * A nested list
# * The end of the list
#
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
case list_next(line, count) do
:done -> { Enum.reverse(acc), [line|rest], false }
chopped -> process_list_next(rest, count, false, [chopped|acc])
end
end
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
{ Enum.reverse(acc), rest, false }
end
defp process_list_next(["" | rest], count, _done, acc) do
process_list_next(rest, count, true, [""|acc])
end
defp process_list_next(rest, _count, done, acc) do
{ Enum.reverse(acc), rest, done }
end
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
defp list_next(line, 0), do: chop(line, 2)
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
defp list_next(line, _acc), do: line
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
defp chop(line, _acc), do: line
## Text (paragraphs / lists)
defp process_text(doc=["" | _], para, indent, from_list, colors) do
write_text(Enum.reverse(para), indent, from_list, colors)
process(doc, indent, colors)
end
defp process_text([], para, indent, from_list, colors) do
write_text(Enum.reverse(para), indent, from_list, colors)
end
defp process_text([line | rest], para, indent, true, colors) do
{ stripped, count } = strip_spaces(line, 0)
case stripped do
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(Enum.reverse(para), indent, true, colors)
process_list(item, rest, count, indent, colors)
_ ->
process_text(rest, [line | para], indent, true, colors)
end
end
defp process_text([line | rest], para, indent, from_list, colors) do
process_text(rest, [line | para], indent, from_list, colors)
end
defp write_text(lines, indent, from_list, colors) do
lines
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [], colors)
|> String.split(%r{\s})
|> write_with_wrap(column_width() - size(indent), indent, from_list)
unless from_list, do: IO.puts(IO.ANSI.reset)
end
## Code blocks
defp process_code([], code, indent, colors) do
write_code(code, indent, colors)
end
# Blank line between code blocks
defp process_code([ "", " " <> line | rest ], code, indent, colors) do
process_code(rest, [line, "" | code], indent, colors)
end
defp process_code([ " " <> line | rest ], code, indent, colors) do
process_code(rest, [line|code], indent, colors)
end
defp process_code(rest, code, indent, colors) do
write_code(code, indent, colors)
process(rest, indent, colors)
end
defp write_code(code, indent, colors) do
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", colors)
end
## Helpers
defp write(style, string, colors) do
IO.puts color(style, colors) <> string <> IO.ANSI.reset
IO.puts IO.ANSI.reset
end
defp write_with_wrap([], _available, _indent, _first) do
:ok
end
defp write_with_wrap(words, available, indent, first) do
{ words, rest } = take_words(words, available, [])
IO.puts (if first, do: "", else: indent) <> Enum.join(words, " ")
write_with_wrap(rest, available, indent, false)
end
defp take_words([word|words], available, acc) do
available = available - length_without_escape(word, 0)
cond do
# It fits, take one for space and continue decreasing
available > 0 ->
take_words(words, available - 1, [word|acc])
# No space but we got no words
acc == [] ->
{ [word], words }
# Otherwise
true ->
{ Enum.reverse(acc), [word|words] }
end
end
defp take_words([], _available, acc) do
{ Enum.reverse(acc), [] }
end
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
length_without_escape(rest, count)
end
defp length_without_escape(<< ?\e, ?[, _, ?m, rest :: binary >>, count) do
length_without_escape(rest, count)
end
defp length_without_escape(rest, count) do
case String.next_grapheme(rest) do
:no_grapheme -> count
{ _, rest } -> length_without_escape(rest, count + 1)
end
end
defp handle_links(text) do
text
|> remove_square_brackets_in_link
|> escape_underlines_in_link
end
defp escape_underlines_in_link(text) do
case Regex.match?(%r{.*(https?\S*)}, text) do
true ->
Regex.replace(%r{_}, text, "\\\\_")
_ ->
text
end
end
defp remove_square_brackets_in_link(text) do
Regex.replace(%r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
end
# Single inline quotes.
@single [?`, ?_, ?*]
# ` does not require space in between
@spaced [?_, ?*]
# Clauses for handling spaces
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, colors) do
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, colors)
end
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, colors) when mark in @spaced do
handle_inline(rest, nil, [?\s, mark|buffer], acc, colors)
end
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, colors)
end
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, colors) when mark in @spaced do
handle_inline(rest, limit, [mark, ?\s|buffer], acc, colors)
end
# Clauses for handling escape
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, colors) do
handle_inline(rest, limit, [?\\|buffer], acc, colors)
end
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
handle_inline(rest, limit, [?*, ?*|buffer], acc, colors)
end
# A escape is not valid inside `
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, colors)
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
handle_inline(rest, limit, [mark|buffer], acc, colors)
end
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, colors) when rest != "" do
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], colors)
end
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, colors) when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], colors)
end
# Inline end
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, colors) do
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, colors) when mark in @single do
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
end
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, colors) do
handle_inline(rest, mark, [char|buffer], acc, colors)
end
defp handle_inline(<<>>, _mark, buffer, acc, _colors) do
iolist_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
end
defp inline_buffer(buffer, colors) do
[h|t] = Enum.reverse([IO.ANSI.reset|buffer])
[color_for(h, colors)|t]
end
defp color_for("`", colors), do: color(:doc_inline_code, colors)
defp color_for("_", colors), do: color(:doc_underline, colors)
defp color_for("*", colors), do: color(:doc_bold, colors)
defp color_for("**", colors), do: color(:doc_bold, colors)
defp color(style, colors) do
color = colors[style]
enabled = colors[:enabled]
IO.ANSI.escape_fragment("%{#{color}}", enabled)
end
defp column_width() do
case :io.columns do
{ :ok, width } -> min(width, 80)
_ -> 80
end
end
end
+2258 -2065
View File
File diff suppressed because it is too large Load Diff
+52 -24
View File
@@ -104,6 +104,10 @@ defmodule Kernel.CLI do
prune_stacktrace(t)
end
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
[]
end
defp prune_stacktrace([h|t]) do
[h|prune_stacktrace(t)]
end
@@ -217,7 +221,7 @@ defmodule Kernel.CLI do
defp process_compiler(["--warnings-as-errors"|t], config) do
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
end
defp process_compiler(["--verbose"|t], config) do
process_compiler t, config.verbose_compile(true)
end
@@ -273,16 +277,14 @@ defmodule Kernel.CLI do
defp process_command({:cookie, h}, _config) do
if Node.alive? do
Node.set_cookie(binary_to_atom(h))
:ok
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
else
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
end
end
defp process_command({:eval, expr}, _config) when is_binary(expr) do
Code.eval_string(expr, [])
:ok
wrapper fn -> Code.eval_string(expr, []) end
end
defp process_command({:app, app}, _config) when is_binary(app) do
@@ -296,8 +298,7 @@ defmodule Kernel.CLI do
defp process_command({:script, file}, _config) when is_binary(file) do
if exec = find_elixir_executable(file) do
Code.require_file(exec)
:ok
wrapper fn -> Code.require_file(exec) end
else
{ :error, "-S : Could not find executable #{file}" }
end
@@ -305,8 +306,7 @@ defmodule Kernel.CLI do
defp process_command({:file, file}, _config) when is_binary(file) do
if :filelib.is_regular(file) do
Code.require_file(file)
:ok
wrapper fn -> Code.require_file(file) end
else
{ :error, "No file named #{file}" }
end
@@ -318,8 +318,7 @@ defmodule Kernel.CLI do
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
Enum.map files, &Code.require_file(&1)
:ok
wrapper fn -> Enum.map files, &Code.require_file(&1) end
else
{ :error, "-r : No files matched pattern #{pattern}" }
end
@@ -331,8 +330,7 @@ defmodule Kernel.CLI do
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
Kernel.ParallelRequire.files(files)
:ok
wrapper fn -> Kernel.ParallelRequire.files(files) end
else
{ :error, "-pr : No files matched pattern #{pattern}" }
end
@@ -341,20 +339,50 @@ defmodule Kernel.CLI do
defp process_command({:compile, patterns}, config) do
:filelib.ensure_dir(:filename.join(config.output, "."))
files = Enum.map patterns, &Path.wildcard(&1)
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
:ok
else
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
case match_regular_files(patterns) do
{ :ok, [] } ->
{ :error, "No files matched provided patterns" }
{ :ok, files } ->
wrapper fn ->
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
end
{ :missing, missing } ->
{ :error, "No files matched pattern(s) #{Enum.join(missing, ",")}" }
end
end
defp match_regular_files(patterns) do
matched_files = Enum.map patterns, fn(pattern) ->
case Path.wildcard(pattern) do
[] -> { :missing, pattern }
files -> { :ok, files }
end
end
files = Enum.filter_map matched_files,
fn(match) -> elem(match, 0) == :ok end,
&elem(&1, 1)
missing_patterns = Enum.filter_map matched_files,
fn(match) -> elem(match, 0) == :missing end,
&elem(&1, 1)
if missing_patterns == [] do
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, &:filelib.is_regular(&1)
{ :ok, files }
else
{ :missing, Enum.uniq(missing_patterns) }
end
end
defp wrapper(fun) do
fun.()
:ok
end
defp find_elixir_executable(file) do
if exec = System.find_executable(file) do
# If we are on Windows, the executable is going to be
+4 -2
View File
@@ -18,9 +18,11 @@ defmodule Kernel.ErrorHandler do
{ :module, _ } -> []
{ :error, _ } ->
parent = :erlang.get(:elixir_compiler_pid)
parent <- { :waiting, self(), module }
ref = :erlang.make_ref
parent <- { :waiting, self(), ref, module }
:erlang.garbage_collect(self)
receive do
{ :release, ^parent } -> :ok
{ ^ref, :release } -> :ok
end
end
end
+175
View File
@@ -0,0 +1,175 @@
# This is a module Elixir responsible for tracking
# the usage of aliases, imports and requires in the Elixir scope.
#
# The implementation simply stores dispatch information in an
# ETS table and then consults this table once compilation is done.
#
# Note that since this is required for bootstrap, we can't use
# any of the `GenServer.Behaviour` conveniences.
defmodule Kernel.LexicalTracker do
@timeout 30_000
@behaviour :gen_server
@import 2
@alias 3
@doc """
Returns all remotes linked to in this lexical scope.
"""
def remotes(arg) do
# If the module is compiled from a function, its lexical
# scope may be long gone, so it has no associated PID.
if pid = to_pid(arg) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
else
[]
end
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__lexical_tracker)
val
end
# Internal API
# Starts the tracker and returns its pid.
@doc false
def start_link do
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
pid
end
@doc false
def stop(pid) do
:gen_server.cast(pid, :stop)
end
@doc false
def add_import(pid, module, line, warn) do
:gen_server.cast(pid, { :add_import, module, line, warn })
end
@doc false
def add_alias(pid, module, line, warn) do
:gen_server.cast(pid, { :add_alias, module, line, warn })
end
@doc false
def remote_dispatch(pid, module) do
:gen_server.cast(pid, { :remote_dispatch, module })
end
@doc false
def import_dispatch(pid, module) do
:gen_server.cast(pid, { :import_dispatch, module })
end
@doc false
def alias_dispatch(pid, module) do
:gen_server.cast(pid, { :alias_dispatch, module })
end
@doc false
def collect_unused_imports(pid) do
unused(pid, @import)
end
@doc false
def collect_unused_aliases(pid) do
unused(pid, @alias)
end
defp unused(pid, pos) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.foldl(fn
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
[{ module, :erlang.element(pos, tuple) }|acc]
_, acc ->
acc
end, [], ets) |> Enum.sort
end
# Callbacks
def init([]) do
{ :ok, :ets.new(:lexical, [:protected]) }
end
def handle_call(:ets, _from, d) do
{ :reply, d, d }
end
def handle_call(request, _from, d) do
{ :stop, { :bad_call, request }, d }
end
def handle_cast({ :remote_dispatch, module }, d) do
add_module(d, module)
{ :noreply, d }
end
def handle_cast({ :import_dispatch, module }, d) do
add_dispatch(d, module, @import)
{ :noreply, d }
end
def handle_cast({ :alias_dispatch, module }, d) do
add_dispatch(d, module, @alias)
{ :noreply, d }
end
def handle_cast({ :add_import, module, line, warn }, d) do
add_directive(d, module, line, warn, @import)
{ :noreply, d }
end
def handle_cast({ :add_alias, module, line, warn }, d) do
add_directive(d, module, line, warn, @alias)
{ :noreply, d }
end
def handle_cast(:stop, d) do
{ :stop, :normal, d }
end
def handle_cast(msg, d) do
{ :stop, { :bad_cast, msg }, d }
end
def handle_info(_msg, d) do
{ :noreply, d }
end
def terminate(_reason, _d) do
:ok
end
def code_change(_old, d, _extra) do
{ :ok, d }
end
# Callbacks helpers
# In the table we keep imports and aliases.
# If the value is false, it was not imported/aliased
# If the value is true, it was imported/aliased
# If the value is a line, it was imported/aliased and has a pending warning
defp add_module(d, module) do
:ets.insert_new(d, { module, false, false })
end
defp add_dispatch(d, module, pos) do
:ets.update_element(d, module, { pos, true })
end
defp add_directive(d, module, line, warn, pos) do
add_module(d, module)
marker = if warn, do: line, else: true
:ets.update_element(d, module, { pos, marker })
end
end
+79 -68
View File
@@ -20,13 +20,8 @@ defmodule Kernel.ParallelCompiler do
* `:each_file` - for each file compiled, invokes the callback passing the file
* `:each_module` - for each module compiled, invokes the callback
passing the file, module and the module bytecode
* `:each_waiting` - every time a module waits for another module to be compiled,
this callback is invoked with the module we are waiting
The compiler doesn't care about the return values of the callbacks except
by `each_waiting`, which must return nil or a file as a hint where the source
could be found.
The compiler doesn't care about the return values of the callbacks.
Returns the modules generated by each compiled file.
"""
def files(files, callbacks // [])
@@ -47,14 +42,15 @@ defmodule Kernel.ParallelCompiler do
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
:elixir_code_server.cast(:reset_warnings)
compiler_pid = self()
:elixir_code_server.cast({ :reset_warnings, compiler_pid })
schedulers = max(:erlang.system_info(:schedulers_online), 2)
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with Kernel.CompilationError
case :elixir_code_server.call(:compilation_status) do
# compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call({ :compilation_status, compiler_pid }) do
:ok -> result
:error -> raise CompileError, [], []
end
@@ -66,29 +62,38 @@ defmodule Kernel.ParallelCompiler do
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
end
# Release waiting processes
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
{ ^h, ref, _ } = List.keyfind(waiting, h, 0)
h <- { ref, :release }
waiting = List.keydelete(waiting, h, 0)
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
end
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
parent = self()
child = spawn_link fn ->
:erlang.put(:elixir_compiler_pid, parent)
:erlang.put(:elixir_ensure_compiled, true)
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
{ pid, ref } =
:erlang.spawn_monitor fn ->
:erlang.put(:elixir_compiler_pid, parent)
:erlang.put(:elixir_ensure_compiled, true)
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
try do
if output do
:elixir_compiler.file_to_path(h, output)
else
:elixir_compiler.file(h)
end
parent <- { :compiled, self(), h }
catch
kind, reason ->
parent <- { :failure, self(), kind, reason, System.stacktrace }
exit(try do
if output do
:elixir_compiler.file_to_path(h, output)
else
:elixir_compiler.file(h)
end
{ :compiled, h }
catch
kind, reason ->
{ :failure, kind, reason, System.stacktrace }
end)
end
end
spawn_compilers(t, original, output, callbacks, waiting, [{child, h}|queued], schedulers, result)
spawn_compilers(t, original, output, callbacks, waiting, [{pid, ref, h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
@@ -96,7 +101,10 @@ defmodule Kernel.ParallelCompiler do
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
Enum.each queued, fn { child, _, _ } ->
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
child <- { ref, :release }
end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
@@ -108,47 +116,61 @@ defmodule Kernel.ParallelCompiler do
# Wait for messages from child processes
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
receive do
{ :compiled, child, file } ->
if callback = Keyword.get(callbacks, :each_file) do
callback.(file)
end
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, 0)
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :module_available, child, file, module, binary } ->
{ :module_available, child, ref, file, module, binary } ->
if callback = Keyword.get(callbacks, :each_module) do
callback.(file, module, binary)
end
# Release the module loader which is waiting for an ack
child <- { self, :ack }
child <- { ref, :ack }
new_waiting = release_waiting_processes(module, waiting)
new_result = [module|result]
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
{ :waiting, child, on } ->
# If one of the callbacks is each_waiting and we haven't seen
# the hinted file before, add it to the list to be processed.
if (callback = Keyword.get(callbacks, :each_waiting)) &&
(hint = callback.(on)) &&
not(hint in original) do
files = [hint|files]
original = [hint|original]
available = lc { pid, _, waiting_module } inlist waiting,
waiting_module == module,
do: pid
spawn_compilers(available ++ files, original, output, callbacks,
waiting, queued, schedulers, [module|result])
{ :waiting, child, ref, on } ->
# Oops, we already got this module. Do not put it on waiting.
if :lists.member(on, result) do
child <- { :release, ref }
else
waiting = [{ child, ref, on }|waiting]
end
spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
if callback = Keyword.get(callbacks, :each_file) do
callback.(file)
end
new_waiting = :orddict.store(child, on, waiting)
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
{ :failure, child, kind, reason, stacktrace } ->
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, down_pid, 0)
new_queued = List.keydelete(queued, down_pid, 0)
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
{ :DOWN, down_ref, :process, _down_pid, other } ->
handle_failure(down_ref, :exit, other, [], files, waiting, queued)
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
end
end
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
case List.keyfind(queued, ref, 1) do
{ child, ^ref, file } ->
if many_missing?(child, files, waiting, queued) do
IO.puts "== Compilation failed =="
IO.puts "Compilation failed on the following files:\n"
Enum.each Enum.reverse(queued), fn { pid, file } ->
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
case List.keyfind(waiting, pid, 0) do
{ _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
_ -> :ok
end
end
@@ -156,29 +178,18 @@ defmodule Kernel.ParallelCompiler do
IO.puts "\nThe first failure is shown below..."
end
{^child, file} = List.keyfind(queued, child, 0)
IO.puts "== Compilation error on file #{file} =="
:erlang.raise(kind, reason, stacktrace)
_ ->
:ok
end
end
defp many_missing?(child, files, waiting, queued) do
waiting_length = length(waiting)
match?({ ^child, _ }, List.keyfind(waiting, child, 0)) and
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
waiting_length > 1 and files == [] and
waiting_length == length(queued)
end
# Release waiting processes that are waiting for the given module
defp release_waiting_processes(module, waiting) do
Enum.filter waiting, fn { child, waiting_module } ->
if waiting_module == module do
child <- { :release, self() }
false
else
true
end
end
end
end
+22 -12
View File
@@ -35,7 +35,7 @@ defmodule Kernel.ParallelRequire do
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
child = spawn_link fn ->
{ pid, ref } = :erlang.spawn_monitor fn ->
if compiler_pid != :undefined do
:erlang.put(:elixir_compiler_pid, compiler_pid)
end
@@ -46,25 +46,35 @@ defmodule Kernel.ParallelRequire do
:erlang.process_flag(:error_handler, handler)
try do
exit(try do
new = Code.require_file(h) || []
parent <- { :required, self, Enum.map(new, &elem(&1, 0)), h }
{ :required, Enum.map(new, &elem(&1, 0)), h }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
end
{ :failure, kind, reason, System.stacktrace }
end)
end
spawn_requires(t, [child|waiting], callback, schedulers, result)
spawn_requires(t, [{ pid, ref }|waiting], callback, schedulers, result)
end
defp wait_for_messages(files, waiting, callback, schedulers, result) do
receive do
{ :required, child, mods, file } ->
callback.(file)
spawn_requires(files, List.delete(waiting, child), callback, schedulers, mods ++ result)
{ :failure, _child, kind, reason, stacktrace } ->
:erlang.raise(kind, reason, stacktrace)
{ :DOWN, ref, :process, pid, status } ->
tuple = { pid, ref }
if tuple in waiting do
case status do
{ :required, mods, file } ->
callback.(file)
result = mods ++ result
waiting = List.delete(waiting, tuple)
{ :failure, kind, reason, stacktrace } ->
:erlang.raise(kind, reason, stacktrace)
other ->
:erlang.raise(:exit, other, [])
end
end
spawn_requires(files, waiting, callback, schedulers, result)
end
end
end
end
+1 -1
View File
@@ -111,7 +111,7 @@ defmodule Kernel.RecordRewriter do
defp optimize_record_other_call(line, record, res, function, left, args) do
call = { :call, line,
{ :remote, line, { :atom, line, record }, { :atom, 0, function } },
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
args ++ [left]
}
{ call, res }
+563 -174
View File
@@ -4,42 +4,37 @@ defmodule Kernel.SpecialForms do
cannot be overriden by the developer and are the basic
building blocks of Elixir code.
Some of those forms are lexical (like `alias`, `import`, etc).
The macros `{}`, `[]` and `<<>>` are also special forms used
to define data structures, respectively tuples, lists and binaries.
Some of those forms are lexical (like `alias`, `case`, etc).
The macros `{}` and `<<>>` are also special forms used to define
tuple and binary data structures respectively.
This module also documents Elixir's pseudo variables (`__MODULE__`,
`__FILE__`, `__ENV__` and `__CALLER__`). Pseudo variables return
This module also documents Elixir's pseudo variables (`__ENV__`,
`__MODULE__`, `__DIR__` 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 constructs.
Finally, it also documents 2 special forms, `__block__` 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 constructs.
"""
@doc """
Defines a new tuple.
Only two item tuples are considered literals in Elixir.
Therefore all other tuples are represented in the AST
as a call to the special form `:{}`.
## Examples
iex> { 1, 2, 3 }
{ 1, 2, 3 }
iex> quote do: { 1, 2, 3 }
{ :{}, [], [1,2,3] }
"""
defmacro :{}.(args)
@doc """
Defines a new list.
## Examples
iex> [ 1, 2, 3 ]
[ 1, 2, 3 ]
"""
defmacro :[].(args)
defmacro unquote(:{})(args)
@doc """
Defines a new bitstring.
@@ -176,7 +171,7 @@ defmodule Kernel.SpecialForms do
For floats, unit * size must result in 32 or 64, corresponding
to binary32 and binary64, respectively.
"""
defmacro :<<>>.(args)
defmacro unquote(:<<>>)(args)
@doc """
Defines a remote call or an alias.
@@ -232,7 +227,7 @@ defmodule Kernel.SpecialForms do
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
at runtime via `apply/3` and `Module.concat/2`:
iex> apply(Kernel, :+, [1,2])
iex> apply(:erlang, :+, [1,2])
3
iex> Module.concat(Kernel, Sample)
Kernel.Sample
@@ -289,7 +284,7 @@ defmodule Kernel.SpecialForms do
[{:__aliases__, [alias: false], [:String]}, Sample]}
"""
defmacro (:.).(left, right)
defmacro unquote(:.)(left, right)
@doc """
`alias` is used to setup aliases, often useful with modules names.
@@ -326,6 +321,18 @@ defmodule Kernel.SpecialForms do
`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.
## Warnings
If you alias a module and you don't use the alias, Elixir is
going to issue a warning implying the alias is not being used.
In case the alias is generated automatically by a macro,
Elixir won't emit any warnings though, since the alias
was not explicitly defined.
Both warning behaviors could be changed by explicitily
setting the `:warn` option to true or false.
"""
defmacro alias(module, opts)
@@ -464,21 +471,40 @@ defmodule Kernel.SpecialForms do
"""
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 """
Returns the current directory as a binary.
Although the directory can be accessed as `Path.dirname(__ENV__.file)`,
this macro is a convenient shortcut.
"""
defmacro __DIR__
@doc """
Allows you to get the representation of any expression.
Access an already bound variable in match clauses.
## Examples
Elixir allows variables to be rebound via static single assignment:
iex> x = 1
iex> x = 2
iex> x
2
However, in some situations, it is useful to match against an existing
value, instead of rebinding. This can be done with the `^` special form:
iex> x = 1
iex> ^x = 1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
Note the `^` special form is only useful in matches.
"""
defmacro ^(var)
@doc %S"""
Gets the representation of any expression.
## Examples
@@ -508,14 +534,14 @@ defmodule Kernel.SpecialForms do
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control what quote is
able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
* `:location` - When set to `:keep`, keeps the current line and file from quote.
Read the Stacktrace information section below for more information;
* `:hygiene` - Allows a developer to disable hygiene selectively;
* `:context` - Sets the resolution context;
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
`unquote` is automatically disabled;
## Macro literals
## Quote literals
Besides the tuple described above, Elixir has a few literals that
when quoted return themselves. They are:
@@ -524,42 +550,114 @@ defmodule Kernel.SpecialForms do
1 #=> Integers
2.0 #=> Floats
[1, 2] #=> Lists
"binaries" #=> Binaries
{key, value} #=> Tuple with two elements
"strings" #=> Strings
{key, value} #=> Tuples with two elements
## Hygiene and context
## Quote and macros
Elixir macros are hygienic via means of deferred resolution.
This means variables, aliases and imports defined inside the
quoted refer to the context that defined the macro and not
the context where the macro is expanded.
`quote` is commonly used with macros for code generation. As an exercise,
let's define a macro that multiplies a number by itself (squared). Note
there is no reason to define such as a macro (and it would actually be
seen as a bad practice), but it is simple enough that it allows us to focus
on the important aspects of quotes and macros:
For this mechanism to work, every quoted code is attached
to a context. Consider the following example:
defmodule ContextSample do
def hello do
quote do: world
defmodule Math do
defmacro squared(x) do
quote do
unquote(x) * unquote(x)
end
end
end
ContextSample.hello
#=> {:world,[],ContextSample}
We can invoke it as:
Notice how the third element of the returned tuple is the
module name. This means that the variable is associated to the
`ContextSample` module and only code generated by this module
will be able to access that particular `world` variable.
While this means macros from the same module could have
conflicting variables, it also allows different quotes from
the same module to access them.
import Math
IO.puts "Got #{squared(5)}"
The context can be disabled or changed by explicitly setting
the `context` option. All hygiene mechanisms are based on such
context and we are going to explore each of them in the following
subsections.
At first, there is nothing in this example that actually reveals it is a
macro. But what is happening is that, at compilation time, `squared(5)`
becomes `5 * 5`. The argument `5` is duplicated in the produced code, we
can see this behaviour in practice though because our macro actually has
a bug:
### Hygiene in variables
import Math
my_number = fn ->
IO.puts "Returning 5"
5
end
IO.puts "Got #{squared(my_number.())}"
The example above will print:
Returning 5
Returning 5
25
Notice how "Returning 5" was printed twice, instead of just once. This is
because a macro receives an expression and not a value (which is what we
would expect in a regular function). This means that:
squared(my_number.())
Actually expands to:
my_number.() * my_number.()
Which invokes the function twice, explaining why we get the printed value
twice! In the majority of the cases, this is actually unexpected behaviour,
and that's why one of the first things you need to keep in mind when it
comes to macros is to **not unquote the same value more than once**.
Let's fix our macro:
defmodule Math do
defmacro squared(x) do
quote do
x = unquote(x)
x * x
end
end
end
Now invoking `square(my_number.())` as before will print the value just
once.
In fact, this pattern is so common that most of the times you will want
to use the `bind_quoted` option with `quote`:
defmodule Math do
defmacro squared(x) do
quote bind_quoted: [x: x] do
x * x
end
end
end
`:bind_quoted` will translate to the same code as the example above.
`:bind_quoted` can be used in many cases and is seen as good practice,
not only because it helps us from running into common mistakes but also
because it allows us to leverage other tools exposed by macros, such as
unquote fragments discussed in some sections below.
Before we finish this brief introduction, you will notice that, even though
we defined a variable `x` inside our quote:
quote do
x = unquote(x)
x * x
end
When we call:
import Math
squared(5)
x #=> ** (RuntimeError) undefined function or variable: x
We can see that `x` did not leak to the user context. This happens
because Elixir macros are hygienic, a topic we will discuss at length
in the next sections as well.
## Hygiene in variables
Consider the following example:
@@ -577,8 +675,8 @@ defmodule Kernel.SpecialForms do
In the example above, `a` returns 10 even if the macro
is apparently setting it to 1 because variables defined
in the macro does not affect the context the macro is executed.
If you want to set or get a variable in the user context, you
in the macro does not affect the context the macro is executed in.
If you want to set or get a variable in the caller's context, you
can do it with the help of the `var!` macro:
defmodule NoHygiene do
@@ -593,37 +691,53 @@ defmodule Kernel.SpecialForms do
NoHygiene.interference
a #=> 1
It is important to understand that quoted variables are scoped
to the module they are defined. That said, even if two modules
define the same quoted variable `a`, their values are going
to be independent:
Note that you cannot even access variables defined in the same
module unless you explicitly give it a context:
defmodule Hygiene1 do
defmacro var1 do
quote do: a = 1
defmodule Hygiene do
defmacro write do
quote do
a = 1
end
end
defmacro read do
quote do
a
end
end
end
defmodule Hygiene2 do
defmacro var2 do
quote do: a = 2
Hygiene.write
Hygiene.read
#=> ** (RuntimeError) undefined function or variable: a
For such, you can explicitly pass the current module scope as
argument:
defmodule ContextHygiene do
defmacro write do
quote do
var!(a, ContextHygiene) = 1
end
end
defmacro read do
quote do
var!(a, ContextHygiene)
end
end
end
Calling macros `var1` and `var2` are not going to change their
each other values for `a`. This is useful because quoted
variables from different modules cannot conflict. If you desire
to explicitly access a variable from another module, we can once
again use `var!` macro, but explicitly passing a second argument:
# Access the variable a from Hygiene1
quote do: var!(a, Hygiene1) = 2
ContextHygiene.write
ContextHygiene.read
#=> 1
Hygiene for variables can be disabled overall as:
quote hygiene: [vars: false], do: x
### Hygiene in aliases
## Hygiene in aliases
Aliases inside quote are hygienic by default.
Consider the following example:
@@ -643,10 +757,23 @@ defmodule Kernel.SpecialForms do
in the context the macro is expanded, the code above works
because `D` still expands to `HashDict`.
In some particular cases you may want to access an alias
or a module defined in the caller. In such scenarios, you
can access it by disabling hygiene with `hygiene: [aliases: false]`
or by using the `alias!` macro inside the quote:
Similarly, even if we defined an alias with the same name
before invoking a macro, it won't affect the macro's result:
defmodule Hygiene do
alias HashDict, as: D
defmacro no_interference do
quote do: D.new
end
end
require Hygiene
alias SomethingElse, as: D
Hygiene.no_interference #=> #HashDict<[]>
In some cases, you want to access an alias or a module defined
in the caller. For such, you can use the `alias!` macro:
defmodule Hygiene do
# This will expand to Elixir.Nested.hello
@@ -674,7 +801,6 @@ defmodule Kernel.SpecialForms do
#=> "world"
end
## Hygiene in imports
Similar to aliases, imports in Elixir are hygienic. Consider the
@@ -696,7 +822,16 @@ defmodule Kernel.SpecialForms do
Hygiene.return_size #=> 5
Notice how `return_size` returns 5 even though the `size/1`
function is not imported.
function is not imported. In fact, even if `return_size` imported
a function from another module, it wouldn't affect the function
result:
def return_size do
import Dict, only: [size: 1]
get_size
end
Calling this new `return_size` will still return 5 as result.
Elixir is smart enough to delay the resolution to the latest
moment possible. So, if you call `size("hello")` inside quote,
@@ -726,35 +861,31 @@ defmodule Kernel.SpecialForms do
## Stacktrace information
One of Elixir's goals is to provide a proper stacktrace whenever there is an
exception. In order to work properly with macros, the default behavior
in quote is to not set a line. When a macro is invoked and the quoted
expressions is expanded, the call site line is inserted.
When defining functions via macros, developers have the option of
choosing if runtime errors will be reported from the caller or from
inside the quote. Let's see an example:
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 the original
line and file lines instead of setting them to 0:
quote location: :keep do
def handle_call(request, _from, state) do
{ :reply, :undef, state }
# adder.ex
defmodule Adder do
@doc "Defines a function that adds two numbers"
defmacro defadd do
quote location: :keep do
def add(a, b), do: a + b
end
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__` and exceptions happening inside
the quote will always point to `GenServer.Behaviour` file.
# sample.ex
defmodule Sample do
import Adder
defadd
end
When using `location: :keep` and invalid arguments are given to
`Sample.add/2`, the stacktrace information will point to the file
and line inside the quote. Without `location: :keep`, the error is
reported to where `defadd` was invoked. Note `location: :keep` affects
only definitions inside the quote.
## Binding and unquote fragments
@@ -767,12 +898,12 @@ defmodule Kernel.SpecialForms do
def unquote(k)(), do: unquote(v)
end
In the example above, we have generated the function `foo/0` and
In the example above, we have generated the functions `foo/0` and
`bar/0` dynamically. Now, imagine that, we want to convert this
functionality into a macro:
defmacro defkv(kv) do
Enum.each kv, fn { k, v } ->
Enum.map kv, fn { k, v } ->
quote do
def unquote(k)(), do: unquote(v)
end
@@ -789,13 +920,13 @@ defmodule Kernel.SpecialForms do
defkv kv
This is because the macro is expecting its arguments to be a
key-value at **compilation** time. Since in the example above
keyword list at **compilation** time. Since in the example above
we are passing the representation of the variable `kv`, our
code fails.
This is actually a common pitfall when developing macros. In
practice, we want to avoid doing work at compilation time as
much as we can. That said, let's attempt to improve our macro:
much as possible. That said, let's attempt to improve our macro:
defmacro defkv(kv) do
quote do
@@ -807,15 +938,15 @@ defmodule Kernel.SpecialForms do
If you try to run our new macro, you will notice it won't
even compile, complaining that the variables `k` and `v`
do not exist. This is because of the ambiguity: `unquote(k)`
does not exist. This is because of the ambiguity: `unquote(k)`
can either be an unquote fragment, as previously, or a regular
unquote as in `unquote(kv)`.
One solution for this problem is to disable unquoting in the
macro, however, doing that would make it impossible to inject
One solution to this problem is to disable unquoting in the
macro, however, doing that would make it impossible to inject the
`kv` representation into the tree. That's when the `:bind_quoted`
option comes to the rescue. By using `:bind_quoted`, we can
automatically disable unquoting while still injecting the
option comes to the rescue (again!). By using `:bind_quoted`, we
can automatically disable unquoting while still injecting the
desired variables into the tree:
defmacro defkv(kv) do
@@ -831,32 +962,6 @@ defmodule Kernel.SpecialForms do
"""
defmacro quote(opts, block)
@doc """
When used inside quoting, marks that the variable should
not be hygienized. The argument can be either a variable
node (i.e. a tuple with three elements where the last
one is an atom) or an atom representing the variable name.
Check `quote/2` for more information.
"""
defmacro var!(var)
@doc """
Defines a variable in the given context.
If the context is `false`, it is not stored in any particular
context and the variable is not shared in between clauses.
Check `quote/2` for more information.
"""
defmacro var!(var, context)
@doc """
When used inside quoting, marks that the alias should not
be hygienezed. This means the alias will be expanded when
the macro is expanded.
"""
defmacro alias!(alias)
@doc """
Unquotes the given expression from inside a macro.
@@ -880,8 +985,7 @@ defmodule Kernel.SpecialForms do
#=> { :sum, [], [1, 13, 3] }
"""
name = :unquote
defmacro unquote(name)(expr)
defmacro unquote(:unquote)(expr)
@doc """
Unquotes the given list expanding its arguments. Similar
@@ -894,8 +998,7 @@ defmodule Kernel.SpecialForms do
#=> { :sum, [], [1, 2, 3, 4, 5] }
"""
name = :unquote_splicing
defmacro unquote(name)(expr)
defmacro unquote(:unquote_splicing)(expr)
@doc """
List comprehensions allow you to quickly build a list from another list:
@@ -969,6 +1072,18 @@ defmodule Kernel.SpecialForms do
"""
defmacro bc(args)
@doc """
Defines an anonymous function.
## Examples
iex> add = fn a, b -> a + b end
iex> add.(1, 2)
3
"""
defmacro unquote(:fn)(clauses)
@doc """
Internal special form for block expressions.
@@ -1002,17 +1117,17 @@ defmodule Kernel.SpecialForms do
A capture also allows the captured functions to be partially
applied, for example:
iex> fun = &atom_to_binary(&1, :utf8)
iex> fun.(:hello)
"hello"
iex> fun = &is_record(&1, Range)
iex> fun.(1..3)
true
In the example above, we use &1 as a placeholder, generating
a function with one argument. We could also use `&2` and `&3`
to delimit more arguments:
iex> fun = &atom_to_binary(&1, &2)
iex> fun.(:hello, :utf8)
"hello"
iex> fun = &is_record(&1, &2)
iex> fun.(1..3, Range)
true
Since operators are calls, they are also supported, although
they require explicit parentheses around:
@@ -1057,21 +1172,7 @@ defmodule Kernel.SpecialForms do
&(foo(&1, &2); &3 + &4)
"""
name = :&
defmacro unquote(name)(expr)
@doc """
Internal special form for modifying scope information.
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__, [line: 1], [[file: "iex"],[do: 1]] }
Check `quote/2` for more information.
"""
defmacro __scope__(opts, args)
defmacro unquote(:&)(expr)
@doc """
Internal special form to hold aliases information.
@@ -1117,4 +1218,292 @@ defmodule Kernel.SpecialForms do
See `Kernel.defoverridable` for more information and documentation.
"""
defmacro super(args)
@doc """
Matches the given expression against the given clauses.
## Examples
case thing do
{ :selector, i, value } when is_integer(i) ->
value
value ->
value
end
In the example above, we match `thing` against each clause "head"
and execute the clause "body" corresponding to the first clause
that matches. If no clause matches, an error is raised.
## Variables handling
Notice that variables bound in a clause "head" do not leak to the
outer context:
case data do
{ :ok, value } -> value
:error -> nil
end
value #=> unbound variable value
However, variables explicitly bound in the clause "body" are
accessible from the outer context:
value = 7
case lucky? do
false -> value = 13
true -> true
end
value #=> 7 or 13
In the example above, value is going to be `7` or `13` depending on
the value of `lucky?`. In case `value` has no previous value before
case, clauses that do not explicitly bind a value have the variable
bound to nil.
"""
defmacro case(condition, blocks)
@doc """
Evaluate the given expressions and handle any error, exit
or throw that may have happened.
## Examples
try do
do_something_that_may_fail(some_arg)
rescue
ArgumentError ->
IO.puts "Invalid argument given"
catch
value ->
IO.puts "caught \#{value}"
else
value ->
IO.puts "Success! The result was \#{value}"
after
IO.puts "This is printed regardless if it failed or succeed"
end
The rescue clause is used to handle exceptions, while the catch
clause can be used to catch thrown values. The else clause can
be used to control flow based on the result of the expression.
Catch, rescue and else clauses work based on pattern matching.
Note that calls inside `try` are not tail recursive since the VM
needs to keep the stacktrace in case an exception happens.
## Rescue clauses
Besides relying on pattern matching, rescue clauses provides some
conveniences around exceptions that allows one to rescue an
exception by its name. All the following formats are valid rescue
expressions:
try do
UndefinedModule.undefined_function
rescue
UndefinedFunctionError -> nil
end
try do
UndefinedModule.undefined_function
rescue
[UndefinedFunctionError] -> nil
end
# rescue and bind to x
try do
UndefinedModule.undefined_function
rescue
x in [UndefinedFunctionError] -> nil
end
# rescue all and bind to x
try do
UndefinedModule.undefined_function
rescue
x -> nil
end
## Catching exits and Erlang errors
The catch clause works exactly the same as in erlang. Therefore,
one can also handle exits/errors coming from Erlang as below:
try do
exit(1)
catch
:exit, 1 -> IO.puts "Exited with 1"
end
try do
error(:sample)
catch
:error, :sample ->
IO.puts "sample error"
end
Although the second form should be avoided in favor of raise/rescue
control mechanisms.
## Else clauses
Else clauses allow the result of the expression to be pattern
matched on:
x = 2
try do
1 / x
rescue
ArithmeticError ->
:infinity
else
y when y < 1 and y > -1 ->
:small
_ ->
:large
end
If an else clause is not present the result of the expression will
be return, if no exceptions are raised:
x = 1
^x =
try do
1 / x
rescue
ArithmeticError ->
:infinity
end
However when an else clause is present but the result of the expression
does not match any of the patterns an exception will be raised. This
exception will not be caught by a catch or rescue in the same try:
x = 1
try do
try do
1 / x
rescue
# The TryClauseError can not be rescued here:
TryClauseError ->
:error_a
else
0 ->
:small
end
rescue
# The TryClauseError is rescued here:
TryClauseError ->
:error_b
end
Similarly an exception inside an else clause is not caught or rescued
inside the same try:
try do
try do
nil
catch
# The exit(1) call below can not be caught here:
:exit, _ ->
:exit_a
else
_ ->
exit(1)
end
catch
# The exit is caught here:
:exit, _ ->
:exit_b
end
This means the VM nolonger needs to keep the stacktrace once inside
an else clause and so tail recursion is possible when using a `try`
with a tail call as the final call inside an else clause. The same
is true for rescue and catch clauses.
## Variable handling
Since an expression inside `try` may not have been evaluated
due to an exception, any variable created inside `try` cannot
be accessed externaly. For instance:
try do
x = 1
do_something_that_may_fail(same_arg)
:ok
catch
_, _ -> :failed
end
x #=> unbound variable `x`
In the example above, `x` cannot be accessed since it was defined
inside the `try` clause. A common practice to address this issue
is to return the variables defined inside `try`:
x =
try do
x = 1
do_something_that_may_fail(same_arg)
x
catch
_, _ -> :failed
end
"""
defmacro try(args)
@doc """
Checks if there is a message matching the given clauses
in the current process mailbox.
In case there is no such message, the current process hangs
until a message arrives or waits until a given timeout value.
## Examples
receive do
{ :selector, i, value } when is_integer(i) ->
value
value when is_atom(value) ->
value
_ ->
IO.puts :stderr, "Unexpected message received"
end
An optional after clause can be given in case the message was not
received after the specified period of time:
receive do
{ :selector, i, value } when is_integer(i) ->
value
value when is_atom(value) ->
value
_ ->
IO.puts :stderr, "Unexpected message received"
after
5000 ->
IO.puts :stderr, "No message in 5 seconds"
end
The `after` clause can be specified even if there are no match clauses.
There are two special cases for the timout value given to after
* `:infinity` - The process should wait indefinitely for a matching
message, this is the same as not using a timeout.
* 0 - if there is no matching message in the mailbox, the timeout
will occur immediately.
## Variables handling
The `receive` special form handles variables exactly as the `case`
special macro. For more information, check the docs for `case/2`.
"""
defmacro receive(args)
end
+283 -119
View File
@@ -2,56 +2,95 @@ defmodule Kernel.Typespec do
@moduledoc """
Provides macros and functions for working with typespecs.
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
`@callback` available in modules are handled by the equivalent
macros defined by this module.
Elixir comes with a notation for declaring types and specifications. Elixir is
dynamically typed, as such typespecs are never used by the compiler to
optimize or modify code. Still, using typespecs is useful as documentation and
tools such as [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) can
analyze the code with typespecs to find bugs.
## Defining a type
The attributes `@type`, `@opaque`, `@typep`, `@spec` and `@callback` available
in modules are handled by the equivalent macros defined by this module. See
sub-sections "Defining a type" and "Defining a specification" below.
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
## Types and their syntax
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
below.
The type syntax provided by Elixir is fairly similar to the one in
[Erlang](http://www.erlang.org/doc/reference_manual/typespec.html).
## Defining a specification
Most of the built-in types provided in Erlang (for example, `pid()`) are
expressed the same way: `pid()` or simply `pid`. Parametrized types are also
supported (`list(integer`) and so are remote types (`Enum.t`).
@spec function_name(type, type) :: type
@callback function_name(type, type) :: type
For integers and atoms literals are allowed as types (ex. `1`, `:atom` or
`false`). All other types are built up of unions of predefined types. Certain
shorthands are allowed, such as `[...]`, `<<>>` and `{...}`).
For more details, see documentation for `defspec` and `defcallback` below.
### Predefined types
## Types
Type :: any # the top type, the set of all terms
| none # the bottom type, contains no terms
| pid
| port
| reference
| Atom
| Bitstring
| float
| Fun
| Integer
| List
| Tuple
| Union
| UserDefined # Described in section "Defining a type"
The type syntax provided by Elixir is fairly similar to the one
in Erlang.
Atom :: atom
| ElixirAtom # `:foo`, `:bar`, ...
Most of the built-in types provided in Erlang (for example, `pid()`)
are expressed the same way: `pid()` or simply `pid`. Parametrized types
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
Bitstring :: <<>>
| << _ :: M >> # M is a positive integer
| << _ :: _ * N >> # N is a positive integer
| << _ :: M, _ :: _ * N >>
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
well.
Fun :: (... -> any) # any function
| (... -> Type) # any arity, returning Type
| (() -> Type))
| (TList -> Type)
Main differences lie in how bit strings and functions are defined:
Integer :: integer
| ElixirInteger # ..., -1, 0, 1, ... 42 ...
| ElixirInteger..ElixirInteger # specifies an integer range
### Bit Strings
List :: list(Type) # proper list ([]-terminated)
| improper_list(Type1, Type2) # Type1=contents, Type2=termination
| maybe_improper_list(Type1, Type2) # Type1 and Type2 as above
| nonempty_list(Type) # proper non-empty list
| [] # empty list
| [Type] # shorthand for list(Type)
| [Type, ...] # sharthand for nonempty_list(Type)
Tuple :: tuple # stands for a tuple of any size
| {} # empty tuple
| { TList }
TList :: Type
| Type, TList
Union :: Type1 | Type2
### Bit strings
Bit string with a base size of 3:
<<_ :: 3>>
<< _ :: 3 >>
Bit string with a unit size of 8:
<<_ :: _ * 8>>
<< _ :: _ * 8 >>
### Anonymous functions
Any anonymous function:
((...) -> any)
or
(... -> any)
Anonymous function with arity of zero:
@@ -61,22 +100,92 @@ defmodule Kernel.Typespec do
Anonymous function with some arity:
((type, type) -> type)
or
(type, type -> type)
## Built-in types
Built-in type | Defined as
:-------------------- | :---------
`term` | `any`
`binary` | `<< _ :: _ * 8 >>`
`bitstring` | `<< _ :: _ * 1 >>`
`boolean` | `false` &#124; `true`
`byte` | `0..255`
`char` | `0..0xffff`
`number` | `integer` &#124; `float`
`list` | `[any]`
`maybe_improper_list` | `maybe_improper_list(any, any)`
`nonempty_list` | `nonempty_list(any)`
`iodata` | `iolist` &#124; `binary`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])`
`module` | `atom`
`mfa` | `{ atom, atom, arity }`
`arity` | `0..255`
`node` | `atom`
`timeout` | `:infinity` &#124; `non_neg_integer`
`no_return` | `none`
Some built-in types cannot be expressed with valid syntax according to the
language defined above.
Built-in type | Can be interpreted as
:---------------- | :--------------------
`non_neg_integer` | `0..`
`pos_integer` | `1..`
`neg_integer` | `..-1`
Types defined in other modules are referred to as "remote types", they are
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
## Defining a type
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
A type defined with `@typep` is private. An opaque type, defined with
`@opaque` is a type where the internal structure of the type will not be
visible, but the type is still public.
Types can be parametrised by defining variables as parameters, these variables
can then be used to define the type.
@type dict(key, value) :: [{ key, value }]
Types can also be defined for records, see `defrecord/3`.
## Defining a specification
@spec function_name(type1, type2) :: return_type
@callback function_name(type1, type2) :: return_type
Callbacks are used to define the callbacks functions of behaviours (see
`Behaviour`).
Guards can be used to restrict type variables given as arguments to the
function.
@spec function(arg) :: [arg] when arg: atom
Type variables with no restriction can also be defined.
@spec function(arg) :: [arg] when arg: var
Specifications can be overloaded just like ordinary functions.
@spec function(integer) :: atom
@spec function(atom) :: integer
## Notes
Elixir discourages the use of type `string()` as it might be confused
with binaries which are referred to as "strings" in Elixir (as opposed to
character lists). In order to use the type that is called `string()` in Erlang,
one has to use the `char_list()` type which is a synonym for `string()`. If you
use `string()`, you'll get a warning from the compiler.
Elixir discourages the use of type `string` as it might be confused with
binaries which are referred to as "strings" in Elixir (as opposed to character
lists). In order to use the type that is called `string` in Erlang, one has to
use the `char_list` type which is a synonym for `string`. If you use `string`,
you'll get a warning from the compiler.
If you want to refer to the "string" type (the one operated by functions in the
String module), use `String.t()` type instead.
See http://www.erlang.org/doc/reference_manual/typespec.html
for more information.
If you want to refer to the "string" type (the one operated by functions in
the String module), use `String.t` type instead.
"""
@doc """
@@ -90,7 +199,7 @@ defmodule Kernel.Typespec do
"""
defmacro deftype(type) do
quote do
Kernel.Typespec.deftype(:type, unquote(Macro.escape type), __ENV__)
Kernel.Typespec.deftype(:type, unquote(Macro.escape(type, unquote: true)), __ENV__)
end
end
@@ -105,7 +214,7 @@ defmodule Kernel.Typespec do
"""
defmacro defopaque(type) do
quote do
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape type), __ENV__)
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape(type, unquote: true)), __ENV__)
end
end
@@ -120,7 +229,7 @@ defmodule Kernel.Typespec do
"""
defmacro deftypep(type) do
quote do
Kernel.Typespec.deftype(:typep, unquote(Macro.escape type), __ENV__)
Kernel.Typespec.deftype(:typep, unquote(Macro.escape(type, unquote: true)), __ENV__)
end
end
@@ -135,7 +244,7 @@ defmodule Kernel.Typespec do
"""
defmacro defspec(spec) do
quote do
Kernel.Typespec.defspec(:spec, unquote(Macro.escape spec), __ENV__)
Kernel.Typespec.defspec(:spec, unquote(Macro.escape(spec, unquote: true)), __ENV__)
end
end
@@ -150,7 +259,7 @@ defmodule Kernel.Typespec do
"""
defmacro defcallback(spec) do
quote do
Kernel.Typespec.defspec(:callback, unquote(Macro.escape spec), __ENV__)
Kernel.Typespec.defspec(:callback, unquote(Macro.escape(spec, unquote: true)), __ENV__)
end
end
@@ -217,8 +326,8 @@ defmodule Kernel.Typespec do
"""
def defines_type?(module, name, arity) do
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
:lists.any(finder, Module.get_attribute(module, :type)) or
:lists.any(finder, Module.get_attribute(module, :opaque))
Enum.any?(Module.get_attribute(module, :type), finder) or
Enum.any?(Module.get_attribute(module, :opaque), finder)
end
@doc """
@@ -227,7 +336,7 @@ defmodule Kernel.Typespec do
"""
def defines_spec?(module, name, arity) do
tuple = { name, arity }
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :spec))
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
end
@doc """
@@ -236,15 +345,26 @@ defmodule Kernel.Typespec do
"""
def defines_callback?(module, name, arity) do
tuple = { name, arity }
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :callback))
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
end
@doc """
Converts a spec clause back to Elixir AST.
"""
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
ast = { :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }
vars = args ++ [result]
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Enum.map(&{ &1, { :var, [line: line], nil } })
unless vars == [] do
ast = { :when, [line: line], [ast, vars] }
end
ast
end
def spec_to_ast(name, { :type, line, :fun, [] }) do
@@ -252,15 +372,21 @@ defmodule Kernel.Typespec do
end
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
[h|t] =
lc {:type, line, :constraint, [{:atom, _, :is_subtype}, [var, type]]} inlist constraints do
{ :is_subtype, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
guards =
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
{ var, typespec_to_ast(type) }
end
args = lc arg inlist args, do: typespec_to_ast(arg)
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
{ :::, [line: line], [{ :when, [line: line], [{ name, [line: line], args }, guards] }, typespec_to_ast(result)] }
vars = args ++ [result]
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
vars = vars -- Keyword.keys(guards)
|> Enum.map(&{ &1, { :var, [line: line], nil } })
{ :when, [line: line], [{ :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }, guards ++ vars] }
end
@doc """
@@ -287,15 +413,14 @@ defmodule Kernel.Typespec do
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
# This is in Kernel.Typespec because it works very similar to beam_types and
# uses some of the introspection available here.
def beam_typedocs(module) do
@spec beam_typedocs(module | binary) :: [tuple] | nil
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
List.flatten(type_docs)
_ ->
[]
nil
end
end
@@ -308,7 +433,8 @@ defmodule Kernel.Typespec do
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
def beam_types(module) do
@spec beam_types(module | binary) :: [tuple] | nil
def beam_types(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
@@ -317,12 +443,12 @@ defmodule Kernel.Typespec do
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
cond do
kind == :opaque -> { :opaque, type }
:lists.member({ name, length(args) }, exported_types) -> { :type, type }
{ name, length(args) } in exported_types -> { :type, type }
true -> { :typep, type }
end
end
_ ->
[]
nil
end
end
@@ -335,7 +461,8 @@ defmodule Kernel.Typespec do
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
def beam_specs(module) do
@spec beam_specs(module | binary) :: [tuple] | nil
def beam_specs(module) when is_atom(module) or is_binary(module) do
from_abstract_code(module, :spec)
end
@@ -345,10 +472,11 @@ defmodule Kernel.Typespec do
It is returned as a list of tuples where the first
element is spec name and arity and the second is the spec.
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
The module has to have a corresponding beam file on the disk
which can be located by the runtime system.
"""
def beam_callbacks(module) do
@spec beam_callbacks(module | binary) :: [tuple] | nil
def beam_callbacks(module) when is_atom(module) or is_binary(module) do
from_abstract_code(module, :callback)
end
@@ -356,8 +484,8 @@ defmodule Kernel.Typespec do
case abstract_code(module) do
{ :ok, abstract_code } ->
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
_ ->
[]
:error ->
nil
end
end
@@ -366,7 +494,7 @@ defmodule Kernel.Typespec do
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
{ :ok, abstract_code }
_ ->
[]
:error
end
end
@@ -395,7 +523,7 @@ defmodule Kernel.Typespec do
def deftype(_kind, other, caller) do
type_spec = Macro.to_string(other)
compile_error caller, "invalid type specification #{type_spec}"
compile_error caller, "invalid type specification `#{type_spec}`"
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
@@ -416,17 +544,28 @@ defmodule Kernel.Typespec do
end
@doc false
def defspec(type, {:::, _, [{ :when, _, [{ name, meta, args }, constraints_guard] }, return] }, caller) do
def defspec(type, { :when, meta2, [{ :::, _, [{ name, meta, args }, return] }, guard] }, caller) do
if is_atom(args), do: args = []
constraints = guard_to_constraints(constraints_guard, caller)
spec = { :type, line(meta), :fun, fn_args(meta, args, return, Keyword.keys(constraints), caller) }
spec = { :type, line(meta), :bounded_fun, [spec, Keyword.values(constraints)] }
unless Keyword.keyword?(guard) do
guard = Macro.to_string(guard)
compile_error caller, "invalid guard in function type specification `#{guard}`"
end
vars = Keyword.keys(guard)
constraints = guard_to_constraints(guard, vars, meta2, caller)
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
if constraints != [] do
spec = { :type, line(meta), :bounded_fun, [spec, constraints] }
end
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
def defspec(type, {:::, _, [{ name, meta, args }, return]}, caller) do
def defspec(type, { :::, _, [{ name, meta, args }, return] }, caller) do
if is_atom(args), do: args = []
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
code = { { name, Kernel.length(args) }, spec }
@@ -436,23 +575,54 @@ defmodule Kernel.Typespec do
def defspec(_type, other, caller) do
spec = Macro.to_string(other)
compile_error caller, "invalid function type specification #{spec}"
compile_error caller, "invalid function type specification `#{spec}`"
end
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
defp guard_to_constraints(guard, vars, meta, caller) do
line = line(meta)
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
[{ name, { :type, line, :constraint, contraints } }]
end
defp guard_to_constraints({ :and, _, [left, right] }, caller) do
guard_to_constraints(left, caller) ++ guard_to_constraints(right, caller)
Enum.reduce(guard, [], fn
{ _name, { :var, _, context } }, acc when is_atom(context) ->
acc
{ name, type }, acc ->
constraint = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, vars, caller)]]
type = { :type, line, :constraint, constraint }
[type|acc]
end) |> Enum.reverse
end
## To AST conversion
defp collect_vars({ :ann_type, _line, args }) when is_list(args) do
Enum.flat_map(args, &collect_vars/1)
end
defp collect_vars({ :type, _line, _kind, args }) when is_list(args) do
Enum.flat_map(args, &collect_vars/1)
end
defp collect_vars({ :remote_type, _line, args }) when is_list(args) do
Enum.flat_map(args, &collect_vars/1)
end
defp collect_vars({ :typed_record_field, _line, type }) do
collect_vars(type)
end
defp collect_vars({:paren_type, _line, [type]}) do
collect_vars(type)
end
defp collect_vars({:var, _line, var}) do
[erl_to_ex_var(var)]
end
defp collect_vars(_) do
[]
end
defp typespec_to_ast({ :type, line, :tuple, :any }) do
typespec_to_ast({:type, line, :tuple, []})
{ :tuple, [line: line], [] }
end
defp typespec_to_ast({ :type, line, :tuple, args }) do
@@ -460,8 +630,8 @@ defmodule Kernel.Typespec do
{ :{}, [line: line], args }
end
defp typespec_to_ast({ :type, _line, :list, [arg] }) do
case unpack_typespec_kw(arg, []) do
defp typespec_to_ast({ :type, _line, :list, [{ :type, _, :union, unions } = arg] }) do
case unpack_typespec_kw(unions, []) do
{ :ok, ast } -> ast
:error -> [typespec_to_ast(arg)]
end
@@ -491,11 +661,11 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
end
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
[{ :->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)] }]
end
defp typespec_to_ast({ :type, line, :fun, [] }) do
@@ -512,14 +682,11 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({ :var, line, var }) do
var =
case atom_to_binary(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
end
{ var, line, nil }
{ erl_to_ex_var(var), line, nil }
end
defp typespec_to_ast({ :op, line, op, arg }) do
{ op, [line: line], [typespec_to_ast(arg)] }
end
# Special shortcut(s)
@@ -561,6 +728,15 @@ defmodule Kernel.Typespec do
defp typespec_to_ast(other), do: other
defp erl_to_ex_var(var) do
case atom_to_binary(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
end
end
## From AST conversion
defp line(meta) do
@@ -606,11 +782,7 @@ defmodule Kernel.Typespec do
end
# Handle funs
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
end
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
defp typespec([{:->, meta, [arguments, return]}], vars, caller) when is_list(arguments) do
args = fn_args(meta, arguments, return, vars, caller)
{ :type, line(meta), :fun, args }
end
@@ -644,11 +816,7 @@ defmodule Kernel.Typespec do
end
# Handle tuples
defp typespec({:tuple, meta, atom}, vars, caller) when is_atom(atom) do
typespec({:{}, meta, []}, vars, caller)
end
defp typespec({:{}, meta, []}, _, _) do
defp typespec({:tuple, meta, args}, _vars, _caller) when args == [] or is_atom(args) do
{ :type, line(meta), :tuple, :any }
end
@@ -657,6 +825,10 @@ defmodule Kernel.Typespec do
{ :type, line(meta), :tuple, args }
end
defp typespec({ left, right }, vars, caller) do
typespec({ :{}, [], [left, right] }, vars, caller)
end
# Handle blocks
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
typespec(arg, vars, caller)
@@ -664,7 +836,7 @@ defmodule Kernel.Typespec do
# Handle variables or local calls
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
if :lists.member(name, vars) do
if name in vars do
{ :var, line(meta), name }
else
typespec({name, meta, []}, vars, caller)
@@ -673,7 +845,7 @@ defmodule Kernel.Typespec do
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do
IO.write "warning: string() type use is discouraged. For character lists, use " <>
:elixir_errors.warn "warning: string() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), :string, arguments }
@@ -720,11 +892,6 @@ defmodule Kernel.Typespec do
typespec({ :list, [], [union] }, vars, caller)
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 compile_error(caller, desc) do
@@ -741,7 +908,7 @@ defmodule Kernel.Typespec do
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
defp validate_kw(_, original, caller) do
compile_error(caller, "unexpected list #{Macro.to_string original} in typespec")
compile_error(caller, "unexpected list `#{Macro.to_string original}` in typespec")
end
defp fn_args(meta, args, return, vars, caller) do
@@ -764,15 +931,12 @@ defmodule Kernel.Typespec do
{:var, line(meta), name}
end
defp unpack_typespec_kw({ :type, _, :union, [
next,
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
] }, acc) do
unpack_typespec_kw(next, [{atom, typespec_to_ast(type)}|acc])
defp unpack_typespec_kw([{ :type, _, :tuple, [{ :atom, _, atom }, type] }|t], acc) do
unpack_typespec_kw(t, [{atom, typespec_to_ast(type)}|acc])
end
defp unpack_typespec_kw({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
{ :ok, [{atom, typespec_to_ast(type)}|acc] }
defp unpack_typespec_kw([], acc) do
{ :ok, :lists.reverse(acc) }
end
defp unpack_typespec_kw(_, _acc) do
+144 -12
View File
@@ -4,18 +4,30 @@ defmodule Keyword do
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 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.
behaves exactly as a dictionary and mimic the API defined
by the `Dict` behaviour.
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.
A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without
removing duplicated keys, `get_values` returns all values for
a given key and `delete_first` deletes just one of the existing
entries.
Since a keyword list is simply a list, all the operations defined
in `Enum` and `List` can also be applied.
"""
@type key :: atom
@type value :: any
@type t :: [{key, value}]
@type t(value) :: [{ key, value }]
@doc """
Creates a Keyword from an enum. Unlike `Keyword.new`
@@ -23,16 +35,12 @@ defmodule Keyword do
duplicated entries.
"""
@spec from_enum(Enum.t) :: t
def from_enum(enum) when is_list(enum) do
enum
end
def from_enum(enum) do
Enum.map(enum, fn(x) -> x end)
Enum.to_list(enum)
end
@doc """
Checks if the given argument is a keywords list or not
Checks if the given argument is a keywords list or not.
"""
@spec keyword?(term) :: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
@@ -407,4 +415,128 @@ defmodule Keyword do
def update([], key, initial, _fun) when is_atom(key) do
[{key, initial}]
end
@doc """
Splits the given keywords in two given the given keys.
Duplicated keys are preserved in the split keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.split(d, [:a, :c, :e])
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.split(d, [:a, :c, :e])
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
"""
def split(dict, keys) do
acc = { [], [] }
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
case :lists.member(k, keys) do
true -> { [{k, v}|take], drop }
false -> { take, [{k, v}|drop] }
end
end
{ Enum.reverse(take), Enum.reverse(drop) }
end
@doc """
Takes the given keys from the dict.
Duplicated keys are preserved in the new keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.take(d, [:a, :c, :e])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.take(d, [:a, :c, :e])
[a: 1, c: 3, a: 5]
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
end
@doc """
Drops the given keys from the dict.
Duplicated keys are preserved in the new keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3, a: 5]
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`.
All duplicated entries are removed. See `pop_first/3` for
removing only the first entry.
## Examples
iex> Keyword.pop [a: 1], :a
{1,[]}
iex> Keyword.pop [a: 1], :b
{nil,[a: 1]}
iex> Keyword.pop [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop [a: 1, a: 2], :a
{1,[]}
"""
def pop(dict, key, default // nil) do
{ get(dict, key, default), delete(dict, key) }
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without that particular ocurrence
of `key`.
Duplicated entries are not removed.
## Examples
iex> Keyword.pop_first [a: 1], :a
{1,[]}
iex> Keyword.pop_first [a: 1], :b
{nil,[a: 1]}
iex> Keyword.pop_first [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop_first [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop_first [a: 1, a: 2], :a
{1,[a: 2]}
"""
def pop_first(dict, key, default // nil) do
{ get(dict, key, default), delete_first(dict, key) }
end
end
+129 -13
View File
@@ -4,6 +4,12 @@ defmodule List do
and cannot be part of the Enum protocol. In general,
favor using the Enum API instead of List.
Some functions in this module expect an index. Index
access for list is linear. Negative indexes are also
supported but they imply the list will be iterated twice,
one to calculate the proper index and another to the
operation.
A decision was taken to delegate most functions to
Erlang's standard library but follow Elixir's convention
of receiving the target (in this case, a list) as the
@@ -13,9 +19,9 @@ defmodule List do
@compile :inline_list_funcs
@doc """
Deletes the given item from the list. Returns a list without the item.
If the item occurs more than once in the list, just the first occurrence
is removed.
Deletes the given item from the list. Returns a list without
the item. If the item occurs more than once in the list, just
the first occurrence is removed.
## Examples
@@ -26,6 +32,7 @@ defmodule List do
[1, 2, 3]
"""
@spec delete(list, any) :: list
def delete(list, item) do
:lists.delete(item, list)
end
@@ -41,6 +48,7 @@ defmodule List do
iex> List.duplicate([1, 2], 2)
[[1,2],[1,2]]
"""
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
def duplicate(elem, n) do
:lists.duplicate(n, elem)
end
@@ -54,12 +62,13 @@ defmodule List do
[1,2,3]
"""
@spec flatten(deep_list) :: list when deep_list: [any | deep_list]
def flatten(list) do
:lists.flatten(list)
end
@doc """
Flattens the given `list` of nested lists.
Flattens the given `list` of nested lists.
The list `tail` will be added at the end of
the flattened list.
@@ -69,6 +78,7 @@ defmodule List do
[1,2,3,4,5]
"""
@spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
def flatten(list, tail) do
:lists.flatten(list, tail)
end
@@ -86,6 +96,7 @@ defmodule List do
2
"""
@spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
def foldl(list, acc, function) when is_list(list) and is_function(function) do
:lists.foldl(function, acc, list)
end
@@ -100,6 +111,7 @@ defmodule List do
-2
"""
@spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
def foldr(list, acc, function) when is_list(list) and is_function(function) do
:lists.foldr(function, acc, list)
end
@@ -117,6 +129,8 @@ defmodule List do
3
"""
@spec last([elem]) :: nil | elem when elem: var
def last([]), do: nil
def last(list) do
@@ -140,6 +154,7 @@ defmodule List do
nil
"""
@spec keyfind([tuple], any, non_neg_integer, any) :: any
def keyfind(list, key, position, default // nil) do
:lists.keyfind(key, position + 1, list) || default
end
@@ -161,6 +176,7 @@ defmodule List do
false
"""
@spec keymember?([tuple], any, non_neg_integer) :: any
def keymember?(list, key, position) do
:lists.keymember(key, position + 1, list)
end
@@ -175,6 +191,7 @@ defmodule List do
[a: 3, b: 2]
"""
@spec keyreplace([tuple], any, non_neg_integer, tuple) :: [tuple]
def keyreplace(list, key, position, new_tuple) do
:lists.keyreplace(key, position + 1, list, new_tuple)
end
@@ -192,6 +209,7 @@ defmodule List do
[a: 5, b: 3, c: 1]
"""
@spec keysort([tuple], non_neg_integer) :: [tuple]
def keysort(list, position) do
:lists.keysort(position + 1, list)
end
@@ -210,6 +228,7 @@ defmodule List do
[a: 1, b: 2, c: 3]
"""
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
end
@@ -222,15 +241,16 @@ defmodule List do
## Examples
iex> List.keydelete([a: 1, b: 2], :a, 0)
[{ :b, 2 }]
[b: 2]
iex> List.keydelete([a: 1, b: 2], 2, 1)
[{ :a, 1 }]
[a: 1]
iex> List.keydelete([a: 1, b: 2], :c, 0)
[{ :a, 1 }, { :b, 2 }]
[a: 1, b: 2]
"""
@spec keydelete([tuple], any, non_neg_integer) :: [tuple]
def keydelete(list, key, position) do
:lists.keydelete(key, position + 1, list)
end
@@ -252,6 +272,7 @@ defmodule List do
[]
"""
@spec wrap(list | any) :: list
def wrap(list) when is_list(list) do
list
end
@@ -276,6 +297,7 @@ defmodule List do
[{1, 3, 5}]
"""
@spec zip([list]) :: [tuple]
def zip([]), do: []
def zip(list_of_lists) when is_list(list_of_lists) do
do_zip(list_of_lists, [])
@@ -294,14 +316,15 @@ defmodule List do
[[1, 2, 3], [:a, :b, :c]]
"""
@spec unzip([tuple]) :: [list]
def unzip(list) when is_list(list) do
:lists.map &tuple_to_list/1, zip(list)
end
@doc """
Returns a list with `value` inserted at the specified `index`. Note that `index`
is capped at the list length. Negative indices indicate an offset from the
end of the list.
Returns a list with `value` inserted at the specified `index`.
Note that `index` is capped at the list length. Negative indices
indicate an offset from the end of the list.
## Examples
@@ -318,6 +341,7 @@ defmodule List do
[0, 1, 2, 3]
"""
@spec insert_at(list, integer, any) :: list
def insert_at(list, index, value) do
if index < 0 do
do_insert_at(list, length(list) + index + 1, value)
@@ -327,9 +351,9 @@ defmodule List do
end
@doc """
Returns a list with a replaced value at the specified `index`. Negative indices
indicate an offset from the end of the list. If `index` is out of bounds, the
original `list` is returned.
Returns a list with a replaced value at the specified `index`.
Negative indices indicate an offset from the end of the list.
If `index` is out of bounds, the original `list` is returned.
## Examples
@@ -346,6 +370,7 @@ defmodule List do
[1, 2, 3]
"""
@spec replace_at(list, integer, any) :: list
def replace_at(list, index, value) do
if index < 0 do
do_replace_at(list, length(list) + index, value)
@@ -354,6 +379,61 @@ defmodule List do
end
end
@doc """
Returns a list with an updated value at the specified `index`.
Negative indices indicate an offset from the end of the list.
If `index` is out of bounds, the original `list` is returned.
## Examples
iex> List.update_at([1, 2, 3], 0, &(&1 + 10))
[11, 2, 3]
iex> List.update_at([1, 2, 3], 10, &(&1 + 10))
[1, 2, 3]
iex> List.update_at([1, 2, 3], -1, &(&1 + 10))
[1, 2, 13]
iex> List.update_at([1, 2, 3], -10, &(&1 + 10))
[1, 2, 3]
"""
@spec update_at([elem], integer, (elem -> any)) :: list when elem: var
def update_at(list, index, fun) do
if index < 0 do
do_update_at(list, length(list) + index, fun)
else
do_update_at(list, index, fun)
end
end
@doc """
Produces a new list by removing the value at the specified `index`.
Negative indices indicate an offset from the end of the list.
If `index` is out of bounds, the original `list` is returned.
## Examples
iex> List.delete_at([1, 2, 3], 0)
[2, 3]
iex List.delete_at([1, 2, 3], 10)
[1, 2, 3]
iex> List.delete_at([1, 2, 3], -1)
[1, 2]
"""
@spec delete_at(list, integer) :: list
def delete_at(list, index) do
if index < 0 do
do_delete_at(list, length(list) + index)
else
do_delete_at(list, index)
end
end
## Helpers
# replace_at
@@ -388,6 +468,42 @@ defmodule List do
[ h | do_insert_at(t, index - 1, value) ]
end
# update_at
defp do_update_at([value|list], 0, fun) do
[ fun.(value) | list ]
end
defp do_update_at(list, index, _fun) when index < 0 do
list
end
defp do_update_at([h|t], index, fun) do
[ h | do_update_at(t, index - 1, fun) ]
end
defp do_update_at([], _index, _fun) do
[]
end
# delete_at
defp do_delete_at([], _index) do
[]
end
defp do_delete_at([_|t], 0) do
t
end
defp do_delete_at(list, index) when index < 0 do
list
end
defp do_delete_at([h|t], index) do
[h | do_delete_at(t, index-1)]
end
# zip
defp do_zip(list, acc) do
+7 -7
View File
@@ -9,8 +9,6 @@ defprotocol List.Chars do
by Kernel invokes this protocol.
"""
@only [BitString, List, Atom, Number, Record]
def to_char_list(thing)
end
@@ -38,13 +36,15 @@ defimpl List.Chars, for: List do
def to_char_list(list), do: list
end
defimpl List.Chars, for: Number do
@digits 20
@limit :math.pow(10, @digits)
def to_char_list(thing) when is_integer(thing) do
defimpl List.Chars, for: Integer do
def to_char_list(thing) do
integer_to_list(thing)
end
end
defimpl List.Chars, for: Float do
@digits 20
@limit :math.pow(10, @digits)
def to_char_list(thing) when thing > @limit do
float_to_list(thing, scientific: @digits)
+32 -31
View File
@@ -2,6 +2,10 @@ defmodule ListDict do
@moduledoc """
A Dict implementation that works on lists of two-items tuples.
This dictionary is only recommended for keeping a small amount
of values. Other dict alternatives are more viable for keeping
any other amount than a handful.
For more information about the functions and their APIs, please
consult the `Dict` module.
"""
@@ -38,28 +42,25 @@ defmodule ListDict do
length(dict)
end
def has_key?(dict, key) do
:lists.keymember(key, 1, dict)
end
def has_key?(dict, key)
def has_key?([{ key, _ }|_], key), do: true
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
def has_key?([], _key), do: false
def get(dict, key, default // nil) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
false -> default
end
end
def get(dict, key, default // nil)
def get([{ key, value }|_], key, _default), do: value
def get([{ _, _ }|t], key, default), do: get(t, key, default)
def get([], _key, default), do: default
def fetch(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
def fetch(dict, key)
def fetch([{ key, value }|_], key), do: { :ok, value }
def fetch([{ _, _ }|t], key), do: fetch(t, key)
def fetch([], _key), do: :error
def fetch!(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
end
end
@@ -72,26 +73,26 @@ defmodule ListDict do
end
def put_new(dict, key, val) do
case :lists.keyfind(key, 1, dict) do
{ ^key, _ } -> dict
case has_key?(dict, key) do
true -> dict
false -> [{key, val}|dict]
end
end
def delete(dict, key) do
lc { k, _ } = tuple inlist dict, key != k, do: tuple
end
def delete(dict, key)
def delete([{ key, _ }|t], key), do: t
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
def delete([], _key), do: []
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, fun) do
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end)
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce enum, dict, fn { k, v2 }, acc ->
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
end
end
def split(dict, keys) do
acc = { new(), new() }
acc = { [], [] }
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
{ [{k, v}|take], drop }
@@ -99,7 +100,7 @@ defmodule ListDict do
{ take, [{k, v}|drop] }
end
end
{Enum.reverse(take), Enum.reverse(drop)}
end
@@ -138,7 +139,7 @@ defmodule ListDict do
def empty(_dict), do: []
def equal?(dict, other) do
:lists.keysort(1, dict) == :lists.keysort(1, other)
:lists.keysort(1, dict) === :lists.keysort(1, other)
end
def to_list(dict), do: dict
+300 -229
View File
@@ -2,34 +2,36 @@ import Kernel, except: [to_string: 1]
defmodule Macro do
@moduledoc """
This module provides conveniences for working with macros.
Conveniences for working with macros.
"""
@typedoc "Abstract Syntax Tree (AST) node"
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
@typedoc "Abstract Syntax Tree (AST)"
@type t :: expr | { t, t } | atom | number | binary | pid | fun | [t]
@typedoc "Expr node (remaining ones are literals)"
@type expr :: { expr | atom, Keyword.t, atom | [t] }
@binary_ops [:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
@doc false
defmacro binary_ops do
[ :===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~ ]
end
defmacro binary_ops, do: @binary_ops
@unary_ops [:!, :@, :^, :not, :+, :-, :~~~, :&]
@doc false
defmacro unary_ops do
[:!, :@, :^, :not, :+, :-, :~~~, :&]
end
defmacro unary_ops, do: @unary_ops
@typep precedence :: integer # Higher means binds stronger
@spec binary_op_props(atom) :: { :left|:right, precedence }
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
::: -> {:right, 30}
:when -> {:right, 40}
:when -> {:right, 30}
::: -> {:right, 40}
o when o in [:inlist, :inbits] -> {:left, 50}
:// -> {:right, 60}
:| -> {:left, 70}
@@ -43,39 +45,124 @@ defmodule Macro do
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
o when o in [:<>] -> {:right, 230}
o when o in [:++, :--, :**] -> {:right, 240}
:^^^ -> {:left, 250}
:. -> {:left, 310}
end
end
@doc """
Receives an expresion representing a possible definition
and extracts its arguments. It returns a tuple with the
function name and the arguments list or `:error` if not
a valid call syntax.
Breaks a pipeline expression into a list.
This is useful for macros that want to provide the same
argument syntax available in def/defp/defmacro and friends.
Raises if the pipeline is ill-formed.
"""
@spec unpipe(Macro.t) :: [Macro.t]
def unpipe({ :|> , _, [left, right] }) do
[left|unpipe(right)]
end
def unpipe(other) do
[other]
end
@doc """
Pipes `expr` into the `call_expr` as the
argument in the given `position`.
"""
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
def pipe(expr, call_args, integer // 0)
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
{ call, line, List.insert_at([], integer, expr) }
end
def pipe(expr, { call, line, args }, integer) when is_list(args) do
{ call, line, List.insert_at(args, integer, expr) }
end
def pipe(expr, call_args, _integer) do
raise ArgumentError,
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
end
@doc """
Recurs the quoted expression applying the given function to
each metadata node.
This is often useful to remove information like lines and
hygienic counters from the expression for either storage or
comparison.
## Examples
extract_args(quote do: foo) == { :foo, [] }
extract_args(quote do: foo()) == { :foo, [] }
extract_args(quote do: foo(1, 2, 3)) == { :foo, [1, 2, 3] }
extract_args(quote do: 1.(1, 2, 3)) == :error
iex> quoted = quote line: 10, do: sample()
{:sample, [line: 10], []}
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
{:sample, [], []}
"""
@spec extract_args(Macro.t) :: { atom, [Macro.t] } | :error
def extract_args(expr) do
:elixir_clauses.extract_args(expr)
@spec update_meta(t, (Keyword.t -> Keyword.t)) :: t
def update_meta(quoted, fun)
def update_meta({ left, meta, right }, fun) when is_list(meta) do
{ update_meta(left, fun), fun.(meta), update_meta(right, fun) }
end
def update_meta({ left, right }, fun) do
{ update_meta(left, fun), update_meta(right, fun) }
end
def update_meta(list, fun) when is_list(list) do
lc x inlist list, do: update_meta(x, fun)
end
def update_meta(other, _fun) do
other
end
@doc """
Decomposes a local or remote call into its remote part (when provided),
function name and argument list.
Returns `:error` when an invalid call syntax is provided.
## Examples
iex> Macro.decompose_call(quote do: foo)
{ :foo, [] }
iex> Macro.decompose_call(quote do: foo())
{ :foo, [] }
iex> Macro.decompose_call(quote do: foo(1, 2, 3))
{ :foo, [1, 2, 3] }
iex> Macro.decompose_call(quote do: Elixir.M.foo(1, 2, 3))
{ { :__aliases__, [], [:Elixir, :M] }, :foo, [1, 2, 3] }
iex> Macro.decompose_call(quote do: 42)
:error
"""
@spec decompose_call(Macro.t) :: { atom, [Macro.t] } | { Macro.t, atom, [Macro.t] } | :error
def decompose_call({ { :., _, [remote, function] }, _, args }) when is_tuple(remote) or is_atom(remote),
do: { remote, function, args }
def decompose_call({ name, _, args }) when is_atom(name) and is_atom(args),
do: { name, [] }
def decompose_call({ name, _, args }) when is_atom(name) and is_list(args),
do: { name, args }
def decompose_call(_),
do: :error
@doc """
Recursively escapes a value so it can be inserted
into a syntax tree.
One may pass `unquote: true` to `escape/2`
which leaves unquote statements unescaped, effectively
which leaves `unquote` statements unescaped, effectively
unquoting the contents on escape.
## Examples
@@ -90,14 +177,16 @@ defmodule Macro do
1
"""
@spec escape(Macro.t) :: Macro.t
@spec escape(Macro.t, Keyword.t) :: Macro.t
@spec escape(term) :: Macro.t
@spec escape(term, Keyword.t) :: Macro.t
def escape(expr, opts // []) do
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc %S"""
Unescape the given chars. This is the unescaping behavior
Unescape the given chars.
This is the unescaping behavior
used by default in Elixir single- and double-quoted strings.
Check `unescape_string/2` for information on how to customize
the escaping map.
@@ -107,7 +196,7 @@ defmodule Macro do
also escaped according to the latin1 set they represent.
This function is commonly used on sigil implementations
(like `%r`, `%b` and others) which receive a raw, unescaped
(like `%r`, `%s` and others) which receive a raw, unescaped
string.
## Examples
@@ -116,7 +205,7 @@ defmodule Macro do
"example\n"
In the example above, we pass a string with `\n` escaped
and we return a version with it unescaped.
and return a version with it unescaped.
"""
@spec unescape_string(String.t) :: String.t
def unescape_string(chars) do
@@ -125,13 +214,14 @@ defmodule Macro do
@doc %S"""
Unescape the given chars according to the map given.
Check `unescape_string/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.
representing the codepoint of the character it wants to unescape.
Here is the default mapping function implemented by Elixir:
def unescape_map(?a), do: ?\a
@@ -152,16 +242,16 @@ defmodule Macro do
## Octals
Octals will by default be escaped unless the map function
returns false for ?0.
returns `false` for `?0`.
## Hex
Hexadecimals will by default be escaped unless the map function
returns false for ?x.
returns `false` for `?x`.
## Examples
Using the unescape_map defined above is easy:
Using the `unescape_map` function defined above is easy:
Macro.unescape_string "example\\n", &unescape_map(&1)
@@ -173,12 +263,13 @@ defmodule Macro do
@doc """
Unescape the given tokens according to the default map.
Check `unescape_string/1` and `unescape_string/2` for more
information about unescaping.
Only tokens that are binaries are unescaped, all others are
ignored. This function is useful when implementing your own
sigils. Check the implementation of `Kernel.sigil_b`
sigils. Check the implementation of `Kernel.sigil_s/2`
for examples.
"""
@spec unescape_tokens([Macro.t]) :: [Macro.t]
@@ -188,6 +279,7 @@ defmodule Macro do
@doc """
Unescape the given tokens according to the given map.
Check `unescape_tokens/1` and `unescape_string/2` for more information.
"""
@spec unescape_tokens([Macro.t], (non_neg_integer -> non_neg_integer | false)) :: [Macro.t]
@@ -210,7 +302,7 @@ defmodule Macro do
# Variables
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
fun.(ast, atom_to_binary(var, :utf8))
fun.(ast, atom_to_binary(var))
end
# Aliases
@@ -238,36 +330,49 @@ defmodule Macro do
# Tuple containers
def to_string({ :{}, _, args } = ast, fun) do
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
end
if match?([_], args) do
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
else
args = args_to_string(args, fun)
tuple = "{" <> args <> "}"
end
# List containers
def to_string({ :[], _, args } = ast, fun) do
fun.(ast, "[" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "]")
fun.(ast, tuple)
end
# Fn keyword
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] } = ast, fun)
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
end
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] } = ast, fun) do
def to_string({ :fn, _, [{ :->, _, _ }] = block } = ast, fun) do
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
end
def to_string({ :fn, _, [[do: block]] } = ast, fun) do
def to_string({ :fn, _, block } = ast, fun) do
block = adjust_new_lines block_to_string(block, fun), "\n "
fun.(ast, "fn\n " <> block <> "\nend")
end
# left -> right
def to_string({ :->, _, _ } = ast, fun) do
def to_string([{ :->, _, _ }|_] = ast, fun) do
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
end
# left when right
def to_string({ :when, _, [left, right] } = ast, fun) do
if right != [] and Keyword.keyword?(right) do
right = kw_list_to_string(right, fun)
else
right = fun.(ast, op_to_string(right, fun, :when, :right))
end
fun.(ast, op_to_string(left, fun, :when, :left) <> " when " <> right)
end
# Binary ops
def to_string({ op, _, [left, right] } = ast, fun) when op in binary_ops do
def to_string({ op, _, [left, right] } = ast, fun) when op in @binary_ops do
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
end
@@ -282,23 +387,19 @@ defmodule Macro do
fun.(ast, "not " <> to_string(arg, fun))
end
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
fun.(ast, atom_to_binary(op, :utf8) <> to_string(arg, fun))
def to_string({ op, _, [arg] } = ast, fun) when op in @unary_ops do
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
end
# Access
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
fun.(ast, if right != [] and Keyword.keyword?(right) do
to_string(left, fun) <> to_string(right, fun)
else
to_string(left, fun) <> "[" <> to_string(right, fun) <> "]"
end)
fun.(ast, to_string(left, fun) <> to_string(right, fun))
end
# All other calls
def to_string({ target, _, args } = ast, fun) when is_list(args) do
{ list, last } = :elixir_utils.split_last(args)
fun.(ast, case is_kw_blocks?(last) do
fun.(ast, case kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
@@ -310,51 +411,57 @@ defmodule Macro do
end
# Lists
def to_string(list = ast, fun) when is_list(list) do
if Keyword.keyword?(list) do
fun.(ast, "[" <> kw_list_to_string(list, fun) <> "]")
else
to_string({ :[], [], list }, fun)
end
def to_string(list, fun) when is_list(list) do
fun.(list, cond do
Keyword.keyword?(list) ->
"[" <> kw_list_to_string(list, fun) <> "]"
not match?([_], list) ->
args = args_to_string(list, fun)
"[" <> args <> "]"
true ->
"[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]"
end)
end
# All other structures
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
# Block keywords
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
@kw_keywords [:do, :catch, :rescue, :after, :else]
defp is_kw_blocks?([_|_] = kw) do
Enum.all?(kw, &match?({x, _} when x in kw_keywords, &1))
defp kw_blocks?([_|_] = kw) do
Enum.all?(kw, &match?({x, _} when x in @kw_keywords, &1))
end
defp is_kw_blocks?(_), do: false
defp kw_blocks?(_), do: false
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
defp call_to_string_with_args(target, args, fun) do
{ list, last } = :elixir_utils.split_last(args)
target = call_to_string(target, fun)
args = args_to_string(args, fun)
target <> "(" <> args <> ")"
end
case last != [] and Keyword.keyword?(last) do
true ->
args = Enum.map_join(list, ", ", &to_string(&1, fun))
if list != [], do: args = args <> ", "
args = args <> kw_list_to_string(last, fun)
target <> "(" <> args <> ")"
false ->
args = Enum.map_join(args, ", ", &to_string(&1, fun))
target <> "(" <> args <> ")"
defp args_to_string(args, fun) do
{ list, last } = :elixir_utils.split_last(args)
if last != [] and Keyword.keyword?(last) do
args = Enum.map_join(list, ", ", &to_string(&1, fun))
if list != [], do: args = args <> ", "
args <> kw_list_to_string(last, fun)
else
Enum.map_join(args, ", ", &to_string(&1, fun))
end
end
defp kw_blocks_to_string(kw, fun) do
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
Enum.reduce(@kw_keywords, " ", fn(x, acc) ->
case Keyword.has_key?(kw, x) do
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x), fun)
false -> acc
@@ -364,11 +471,11 @@ defmodule Macro do
defp kw_block_to_string(key, value, fun) do
block = adjust_new_lines block_to_string(value, fun), "\n "
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
atom_to_binary(key) <> "\n " <> block <> "\n"
end
defp block_to_string({ :->, _, exprs }, fun) do
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
defp block_to_string([{ :->, _, _ }|_] = block, fun) do
Enum.map_join(block, "\n", fn({ :->, _, [left, right] }) ->
left = comma_join_or_empty_paren(left, fun, false)
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
end)
@@ -390,7 +497,7 @@ defmodule Macro do
"(" <> to_string(expr, fun) <> ")"
end
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in binary_ops do
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in @binary_ops do
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
{ _, prec } = binary_op_props(op)
cond do
@@ -408,8 +515,8 @@ defmodule Macro do
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
defp arrow_to_string({ :->, _, pairs }, fun, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
defp arrow_to_string(pairs, fun, paren // false) do
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
left = comma_join_or_empty_paren(left, fun, paren)
left <> "-> " <> to_string(right, fun)
end)
@@ -432,18 +539,19 @@ defmodule Macro do
end
@doc """
Receives a AST node and expands it once. The following contents are expanded:
Receives an AST node and expands it once.
The following contents are expanded:
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (`__FILE__`, `__MODULE__`, etc);
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
* Module attributes reader (`@foo`);
In case the expression cannot be expanded, it returns the expression
If the expression cannot be expanded, it returns the expression
itself. Notice that `expand_once/2` performs the expansion just
once and it is not recursive. Check `expand/2` for expansion
until the node no longer represents a macro and `expand_all/2`
for recursive expansion.
until the node can no longer be expanded.
## Examples
@@ -488,7 +596,7 @@ defmodule Macro do
end
The final module name will be `MyHelpers.Module` and not
`My.Module`. With `Macro.expand`, such aliases are taken
`My.Module`. With `Macro.expand/2`, such aliases are taken
into consideration. Local and remote macros are also
expanded. We could rewrite our macro above to use this
function as:
@@ -507,192 +615,155 @@ defmodule Macro do
"""
def expand_once(ast, env) do
expand_once(ast, env, nil) |> elem(0)
elem(do_expand_once(ast, env), 0)
end
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
defp do_expand_once({ :__aliases__, _, _ } = original, env) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
receiver when is_atom(receiver) ->
:elixir_tracker.record_alias(receiver, env.module)
{ receiver, true, cache }
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true }
aliases ->
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
aliases = lc alias inlist aliases, do: elem(do_expand_once(alias, env), 0)
case :lists.all(&is_atom/1, aliases) do
true ->
receiver = :elixir_aliases.concat(aliases)
:elixir_tracker.record_alias(receiver, env.module)
{ receiver, true, cache }
false -> { original, false, cache }
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true }
false ->
{ original, false }
end
end
end
# Expand @ calls
defp expand_once({ :@, _, [{ name, _, args }] } = original, env, cache) when is_atom(args) or args == [] do
defp do_expand_once({ :@, _, [{ 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), true, cache }
false -> { original, false, cache }
true -> { Module.get_attribute(module, name), true }
false -> { original, false }
end
end
# Expand pseudo-variables
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
do: { env.module, true, cache }
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
do: { env.file, true, cache }
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
do: { :filename.dirname(env.file), true, cache }
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
do: { env, true, cache }
defp do_expand_once({ :__MODULE__, _, atom }, env) when is_atom(atom),
do: { env.module, true }
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
do: { :filename.dirname(env.file), true }
defp do_expand_once({ :__FILE__, _, atom }, env) when is_atom(atom) do
IO.write "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead\n#{Exception.format_stacktrace}"
{ env.file, true }
end
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
do: { { :{}, [], tuple_to_list(env) }, true }
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
is_atom(atom) and is_atom(field) do
case :erlang.function_exported(Macro.Env, field, 1) do
true -> { apply(env, field, []), true }
false -> { original, false }
end
end
# Expand possible macro import invocation
defp expand_once({ atom, line, args } = original, env, cache) when is_atom(atom) do
args = case is_atom(args) do
true -> []
false -> args
defp do_expand_once({ atom, meta, context } = original, env)
when is_atom(atom) and is_list(meta) and is_atom(context) do
if :lists.member({ atom, Keyword.get(meta, :counter, context) }, env.vars) do
{ original, false }
else
case do_expand_once({ atom, meta, [] }, env) do
{ _, true } = exp -> exp
{ _, false } -> { original, false }
end
end
end
defp do_expand_once({ atom, meta, args } = original, env)
when is_atom(atom) and is_list(args) and is_list(meta) do
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
case not is_partial?(args) do
false -> { original, false, cache }
true ->
module = env.module
arity = length(args)
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
if :elixir_import.special_form(atom, arity) do
{ original, false }
else
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
:elixir_env.ex_to_env(env), extra)
cache = to_erl_env(env, cache)
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
env.module, extra, cache)
case expand do
{ :ok, _, expanded } -> { expanded, true, cache }
{ :error, _ } -> { original, false, cache }
end
case expand do
{ :ok, receiver, quoted } ->
next = :elixir_counter.next
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
{ :ok, _receiver } ->
{ original, false }
:error ->
{ original, false }
end
end
end
# Expand possible macro require invocation
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
{ receiver, _, _ } = expand_once(left, env, cache)
defp do_expand_once({ { :., _, [left, right] }, meta, args } = original, env) when is_atom(right) do
{ receiver, _ } = do_expand_once(left, env)
case is_atom(receiver) and not is_partial?(args) do
false -> { original, false, cache }
case is_atom(receiver) do
false -> { original, false }
true ->
cache = to_erl_env(env, cache)
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
args, env.module, cache)
expand = :elixir_dispatch.expand_require(meta, receiver, { right, length(args) },
args, :elixir_env.ex_to_env(env))
case expand do
{ :ok, _receiver, expanded } -> { expanded, true, cache }
{ :error, _ } -> { original, false, cache }
{ :ok, receiver, quoted } ->
next = :elixir_counter.next
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
:error ->
{ original, false }
end
end
end
# Anything else is just returned
defp expand_once(other, _env, cache), do: { other, false, cache }
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
defp to_erl_env(_env, cache), do: cache
defp is_partial?(args) do
:lists.any(&match?({ :&, _, [_] }, &1), args)
end
defp do_expand_once(other, _env), do: { other, false }
@doc """
Receives a AST node and expands it until it no longer represents
a macro. Check `expand_once/2` for more information on how
expansion works and `expand_all/2` for recursive expansion.
Receives an AST node and expands it until it no longer represents
a macro.
Check `expand_once/2` for more information on how
expansion works.
"""
def expand(tree, env) do
expand(tree, env, nil) |> elem(0)
expand_until({ tree, true }, env)
end
@doc false # Used internally by Elixir
def expand(tree, env, cache) do
expand_until({ tree, true, cache }, env)
defp expand_until({ tree, true }, env) do
expand_until(do_expand_once(tree, env), env)
end
defp expand_until({ tree, true, cache }, env) do
expand_until(expand_once(tree, env, cache), env)
end
defp expand_until({ tree, false, cache }, _env) do
{ tree, cache }
end
@doc %S"""
Receives a AST node and expands it until it no longer represents
a macro. Then it expands all of its children recursively.
The documentation for `expand_once/2` goes into more detail
on how expansion works. Keep in mind that `expand_all/2` is
naive as it doesn't mutate the environment. Take this example:
quoted = quote do: __MODULE__
Macro.to_string Macro.expand_all(quoted, __ENV__)
#=> "nil"
The example above, works as expected. Outside of a module,
`__MODULE__` returns `nil`. Now consider this variation:
quoted = quote do
defmodule Foo, do: __MODULE__
end
Macro.to_string Macro.expand_all(quoted, __ENV__)
#=> "defmodule(Foo) do\n nil\nend"
One would expect `__MODULE__` to be expanded to `Foo`
but that is not the case since all the expansion is done
based on the environment `__ENV__`. This behaviour is on
purpose, as the proper expansion would require passing through
the whole Elixir compiler by actually executing the code, which
is beyond the scope of this function.
"""
def expand_all(tree, env) do
expand_all(tree, env, nil) |> elem(0)
end
@doc false # Used internally by Elixir
def expand_all(tree, env, cache) do
expand_all_until(expand(tree, env, cache), env)
end
defp expand_all_until({ { left, meta, right }, cache }, env) do
{ left, cache } = expand_all(left, env, cache)
{ right, cache } = expand_all(right, env, cache)
{ { left, meta, right }, cache }
end
defp expand_all_until({ { left, right }, cache }, env) do
{ left, cache } = expand_all(left, env, cache)
{ right, cache } = expand_all(right, env, cache)
{ { left, right }, cache }
end
defp expand_all_until({ list, cache }, env) when is_list(list) do
:lists.mapfoldl(&expand_all(&1, env, &2), cache, list)
end
defp expand_all_until({ other, cache }, _env) do
{ other, cache }
defp expand_until({ tree, false }, _env) do
tree
end
@doc """
Recurs the quoted expression checking if all sub-terms are
safe (i.e. they represent data structures and don't actually
evaluate code) and returns `:ok` unless a given term is unsafe,
Recursively traverses the quoted expression checking if all sub-terms are
safe.
Terms are considered safe if they represent data structures and don't actually
evaluate code. 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
defp do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
defp do_safe_term({ local, _, terms }) when local in [:{}, :__aliases__] do
do_safe_term(terms)
end
+13 -9
View File
@@ -28,20 +28,24 @@ defmodule Macro.Env do
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{ module, module }]
@type macro_aliases :: [{ module, { integer, module } }]
@type context :: :match | :guard | nil
@type requires :: [module]
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
@type context_modules :: [module]
@type vars :: [{ atom, atom }]
@type vars :: [{ atom, atom | non_neg_integer }]
@type lexical_tracker :: pid
@type local :: module | nil
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
:functions, :macros, :context_modules, :macro_aliases, :vars]
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
:macros, :macro_aliases, :context_modules, :vars, :lexical_tracker, :local]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros, context_modules: context_modules,
macro_aliases: aliases, vars: vars]
function: name_arity, context: context, requires: requires, aliases: aliases,
functions: functions, macros: macros, macro_aliases: aliases,
context_modules: context_modules, vars: vars,
lexical_tracker: lexical_tracker, local: local]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
@@ -55,13 +59,13 @@ defmodule Macro.Env do
end
@doc """
Returns wether the compilation environment is currently
Returns whether the compilation environment is currently
inside a guard.
"""
def in_guard?(record), do: context(record) == :guard
@doc """
Returns wether the compilation environment is currently
Returns whether the compilation environment is currently
inside a match clause.
"""
def in_match?(record), do: context(record) == :match
@@ -72,7 +76,7 @@ defmodule Macro.Env do
def stacktrace(record) do
cond do
nil?(record.module) ->
[{ :elixir_compiler, :__FILE__, 2, location(record) }]
[{ :elixir_compiler, :__FILE__, 1, location(record) }]
nil?(record.function) ->
[{ module(record), :__MODULE__, 0, location(record) }]
true ->
+52 -31
View File
@@ -29,7 +29,7 @@ defmodule Module do
When just a module is provided, the function is assumed to be
`__after_compile__/2`.
**Example**
### Example
defmodule M do
@after_compile __MODULE__
@@ -44,18 +44,20 @@ defmodule Module do
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
function/macro must take one argument: the module environment. If it\'s a
function/macro must take one argument: the module environment. If it's a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
**Example**
Note: differently from `@after_compile`, the callback function/macro must
be placed in a separate module (because when the callback is invoked,
the current module does not yet exist).
defmodule M do
@before_compile __MODULE__
### Example
defmodule A do
defmacro __before_compile__(_env) do
quote do
def hello, do: "world"
@@ -63,11 +65,15 @@ defmodule Module do
end
end
defmodule B do
@before_compile A
end
* `@behaviour` (notice the british spelling)
Specify an OTP or user-defined behaviour.
**Example**
### Example
defmodule M do
@behaviour gen_event
@@ -85,7 +91,7 @@ defmodule Module do
See http://www.erlang.org/doc/man/compile.html for the list of supported
options.
**Example**
### Example
defmodule M do
@compile { :inline, myfun: 1 }
@@ -106,7 +112,7 @@ defmodule Module do
Can be invoked more than once.
**Example**
### Example
defmodule M do
@doc "Hello world"
@@ -129,7 +135,7 @@ defmodule Module do
Accepts a string. Can be used more than once.
**Example**
### Example
defmodule M do
@doc "Hello world"
@@ -147,7 +153,7 @@ defmodule Module do
`@moduledoc false` will make the module invisible to the
documentation extraction tools like ExDoc.
**Example**
### Example
defmodule M do
@moduledoc """
@@ -183,7 +189,7 @@ defmodule Module do
function (i.e. `env.function` returns the current function), the hook
can only be a function, not a macro.
**Example**
### Example
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
@@ -216,7 +222,7 @@ defmodule Module do
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
loading of the module will be aborted.
**Example**
### Example
defmodule M do
@on_load :load_check
@@ -238,7 +244,7 @@ defmodule Module do
Specify the module version. Accepts any valid Elixir value.
**Example**
### Example
defmodule M do
@vsn "1.0"
@@ -346,7 +352,10 @@ defmodule Module do
def eval_quoted(module, quoted, binding, opts) do
assert_not_compiled!(:eval_quoted, module)
:elixir_module.eval_quoted(module, quoted, binding, opts)
:elixir_def.reset_last(module)
{ value, binding, _env, _scope } =
:elixir.eval_quoted quoted, binding, Keyword.put(opts, :module, module)
{ value, binding }
end
@doc """
@@ -385,13 +394,11 @@ defmodule Module do
end
def create(module, quoted, opts) when is_atom(module) do
line = Keyword.get(opts, :line, 1)
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
end
@doc """
Concatenates the list of aliases and returns a new alias.
It handles char lists, binaries and atoms.
## Examples
@@ -399,17 +406,15 @@ defmodule Module do
Foo.Bar
iex> Module.concat([Foo, "Bar"])
Foo.Bar
iex> Module.concat([Foo, 'Bar'])
Foo.Bar
"""
@spec concat([binary | atom]) :: atom
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
@@ -417,10 +422,9 @@ defmodule Module do
Foo.Bar
iex> Module.concat(Foo, "Bar")
Foo.Bar
iex> Module.concat(Foo, 'Bar')
Foo.Bar
"""
@spec concat(binary | atom, binary | atom) :: atom
def concat(left, right) do
:elixir_aliases.concat([left, right])
end
@@ -440,6 +444,7 @@ defmodule Module do
List.Chars
"""
@spec safe_concat([binary | atom]) :: atom | no_return
def safe_concat(list) when is_list(list) do
:elixir_aliases.safe_concat(list)
end
@@ -459,6 +464,7 @@ defmodule Module do
List.Chars
"""
@spec safe_concat(binary | atom, binary | atom) :: atom | no_return
def safe_concat(left, right) do
:elixir_aliases.safe_concat([left, right])
end
@@ -670,7 +676,12 @@ defmodule Module do
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
clause ->
:elixir_def.delete_definition(module, tuple)
neighbours = Module.DispatchTracker.yank(module, tuple)
neighbours = if loaded?(Module.LocalsTracker) do
Module.LocalsTracker.yank(module, tuple)
else
[]
end
old = get_attribute(module, :__overridable)
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
@@ -686,7 +697,7 @@ defmodule Module do
Returns true if the given tuple in module is marked as overridable.
"""
def overridable?(module, tuple) do
!! List.keyfind(get_attribute(module, :__overridable), tuple, 0)
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
end
@doc """
@@ -736,7 +747,10 @@ defmodule Module do
Notice the third argument is used to indicate if a warning
should be emitted when the attribute was not previously defined.
This is true for `@foo` attributes but false for direct calls.
`warn` may also be a stacktrace, which will be used in the
warning. The default value for `warn` is false for direct calls
but the `@foo` macro sets it to the proper stacktrace automatically,
warning every time `@foo` is used but not set previously.
## Examples
@@ -750,6 +764,7 @@ defmodule Module do
end
"""
@spec get_attribute(module, atom, warn :: boolean | [tuple]) :: term
def get_attribute(module, key, warn // false) when is_atom(key) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
@@ -759,12 +774,16 @@ defmodule Module do
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
if :lists.member(key, acc) do
[]
else
warn && :elixir_errors.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
cond do
:lists.member(key, acc) ->
[]
warn ->
stack = is_list(warn) and warn
:elixir_errors.warn "#{Exception.format_caller(stack)} undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
true ->
nil
end
end
end
@@ -927,4 +946,6 @@ defmodule Module do
raise ArgumentError,
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
end
defp loaded?(module), do: is_tuple :code.is_loaded(module)
end
@@ -5,44 +5,33 @@
# ## Implementation
#
# The implementation uses the digraph module to track
# all dependencies. The graph starts with three main
# vertices:
# all dependencies. The graph starts with one main vertice:
#
# * `:local` - points to local functions
# * `:import` - points to imported modules
# * `:warn` - points to imported modules that should be warned
# * `:remote` - points to remote modules
#
# Besides those, we have can the following vertices:
# We also have can the following vertices:
#
# * `Module` - a module that was invoked via an import or remotely
# * `Module` - a module that was invoked via an import
# * `{ name, arity }` - a local function/arity pair
# * `{ :import, name, arity }` - an invoked function/arity import
# * `{ :remote, name, arity }` - an remotely invoked function/arity
#
# Each of those vertices can associate to other vertices
# as described below:
#
# * `Module`
# * in neighbours: `:import`, `:remote`, `:warn`,
# `{ :import, name, arity }` and `{ :remote, name arity }`
# * out neighbours: `:warn`
# * in neighbours: `{ :import, name, arity }`
#
# * `{ name, arity }`
# * in neighbours: `:local`, `{ name, arity }`
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
# * out neighbours: `{ :import, name, arity }`
#
# * `{ :import, name, arity }`
# * in neighbours: `{ name, arity }`
# * out neighbours: `Module`
#
# * `{ :remote, name, arity }`
# * in neighbours: `{ name, arity }`
# * out neighbours: `Module`
#
# Note that since this is required for bootstrap, we can't use
# any of the `GenServer.Behaviour` conveniences.
defmodule Module.DispatchTracker do
defmodule Module.LocalsTracker do
@moduledoc false
@timeout 30_000
@@ -54,53 +43,9 @@ defmodule Module.DispatchTracker do
@type local :: { name, arity }
@type import :: { :import, name, arity }
@type remote :: { :remote, name, arity }
# Public API
@doc """
Receives a dispatch or a module and returns all dispatches
that calls it.
In case the argument is a module, the response will be
made by import and remote dispatches.
In case the argument is another dispatch, the response
will be made by local dispatches.
This function is not recursive, so if A dispatches to
B which dispatches to C, A does not appear in the result,
only B.
"""
@spec dispatches_to(ref, module) :: [import | remote]
@spec dispatches_to(ref, local | import | remote) :: [local]
def dispatches_to(ref, dispatch) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.in_neighbours(d, dispatch) |> only_tuples
end
@doc """
Receives a local and returns all dispatches from that local.
This function is not recursive, so if A dispatches to
B which dispatches to C, C does not appear in the result,
only B.
"""
@spec dispatches_from(ref, local) :: [local | import | remote]
def dispatches_from(ref, { name, arity }) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
end
@doc """
Returns all the modules which were imported.
"""
@spec imports(ref) :: [module]
def imports(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :import)
end
@doc """
Returns all imported modules that had the given
`{ name, arity }` invoked.
@@ -111,34 +56,6 @@ defmodule Module.DispatchTracker do
:digraph.out_neighbours(d, { :import, name, arity })
end
@doc """
Returns all the aliases defined in the given module.
"""
@spec aliases(ref) :: [module]
def aliases(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :alias)
end
@doc """
Returns all the modules which were remotely dispatched to.
"""
@spec remotes(ref) :: [module]
def remotes(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :remote)
end
@doc """
Returns all modules that had the given `{ name, arity }`
invoked remotely.
"""
@spec remotes_with_dispatch(ref, name_arity) :: [module]
def remotes_with_dispatch(ref, { name, arity }) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { :remote, name, arity })
end
@doc """
Returns all locals that are reachable.
@@ -163,14 +80,10 @@ defmodule Module.DispatchTracker do
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__dispatch_tracker)
[{ _, val }] = :ets.lookup(table, :__locals_tracker)
val
end
defp only_tuples(list) do
lc x inlist list, is_tuple(x), do: x
end
# Internal API
# Starts the tracker and returns its pid.
@@ -206,50 +119,10 @@ defmodule Module.DispatchTracker do
:gen_server.cast(pid, { :add_local, from, to })
end
# Adds an alias.
@doc false
def add_alias(pid, module) when is_atom(module) do
:gen_server.cast(pid, { :add_alias, module })
end
# Adds a remote dispatch to the given target.
@doc false
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_external, :remote, function, module, target })
end
# Adds a import dispatch to the given target.
@doc false
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_external, :import, function, module, target })
end
# Associates a module with a warn. This adds the given
# module and associates it with the `:import` vertex
# permanently, even if warn is false.
@doc false
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
:gen_server.cast(pid, { :add_warnable, module, warn, line })
end
# Collect all unused imports where warn has been set to true.
def collect_unused_imports(pid) do
d = :gen_server.call(pid, :digraph, @timeout)
warnable = :digraph.out_neighbours(d, :warn)
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
{ mod, line }
end
end
defp get_warn_line(d, mod) do
[edge] = :digraph.out_edges(d, mod)
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
line
end
defp has_imports?(d, mod) do
Enum.any?(:digraph.in_neighbours(d, mod), &match?({ :import, _, _ }, &1))
:gen_server.cast(pid, { :add_import, function, module, target })
end
# Yanks a local node. Returns its in and out vertices in a tuple.
@@ -258,6 +131,8 @@ defmodule Module.DispatchTracker do
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
end
# Reattach a previously yanked node
@doc false
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
@@ -311,6 +186,16 @@ defmodule Module.DispatchTracker do
end
end
@doc false
def cache_env(pid, env) do
:gen_server.call(pid, { :cache_env, env }, @timeout)
end
@doc false
def get_cached_env(pid, ref) do
:gen_server.call(pid, { :get_cached_env, ref }, @timeout)
end
# Stops the gen server
@doc false
def stop(pid) do
@@ -322,101 +207,92 @@ defmodule Module.DispatchTracker do
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
:digraph.add_vertex(d, :alias)
:digraph.add_vertex(d, :import)
:digraph.add_vertex(d, :remote)
:digraph.add_vertex(d, :warn)
{ :ok, d }
{ :ok, { d, [] } }
end
def handle_call({ :yank, local }, _from, d) do
def handle_call({ :cache_env, env }, _from, { d, cache }) do
case cache do
[{i,^env}|_] ->
{ :reply, i, { d, cache } }
t ->
i = length(t)
{ :reply, i, { d, [{i,env}|t] } }
end
end
def handle_call({ :get_cached_env, ref }, _from, { _, cache } = state) do
{ ^ref, env } = :lists.keyfind(ref, 1, cache)
{ :reply, env, state }
end
def handle_call({ :yank, local }, _from, { d, _ } = state) do
in_vertices = :digraph.in_neighbours(d, local)
out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_vertex(d, local)
{ :reply, { in_vertices, out_vertices }, d }
{ :reply, { in_vertices, out_vertices }, state }
end
def handle_call(:digraph, _from, d) do
{ :reply, d, d }
def handle_call(:digraph, _from, { d, _ } = state) do
{ :reply, d, state }
end
def handle_call(_request, _from, d) do
{ :noreply, d }
def handle_call(request, _from, state) do
{ :stop, { :bad_call, request }, state }
end
def handle_info(_msg, d) do
{ :noreply, d }
def handle_info(_msg, state) do
{ :noreply, state }
end
def handle_cast({ :add_local, from, to }, d) do
def handle_cast({ :add_local, from, to }, { d, _ } = state) do
handle_add_local(d, from, to)
{ :noreply, d }
{ :noreply, state }
end
def handle_cast({ :add_alias, module }, d) do
:digraph.add_vertex(d, module)
replace_edge!(d, :alias, module)
{ :noreply, d }
def handle_cast({ :add_import, function, module, { name, arity } }, { d, _ } = state) do
handle_import(d, function, module, name, arity)
{ :noreply, state }
end
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
handle_import_or_remote(d, kind, function, module, name, arity)
{ :noreply, d }
end
def handle_cast({ :add_warnable, module, warn, line }, d) do
:digraph.add_vertex(d, module)
replace_edge!(d, :import, module)
if warn do
:digraph.add_edge(d, :warn, module, line)
:digraph.add_edge(d, module, :warn, line)
else
:digraph.del_path(d, :warn, module)
end
{ :noreply, d }
end
def handle_cast({ :add_definition, kind, tuple }, d) do
def handle_cast({ :add_definition, kind, tuple }, { d, _ } = state) do
handle_add_definition(d, kind, tuple)
{ :noreply, d }
{ :noreply, state }
end
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) do
lc i inlist :lists.seq(arity - defaults, arity - 1) do
handle_add_definition(d, kind, { name, i })
handle_add_local(d, { name, i }, { name, i + 1 })
end
{ :noreply, d }
{ :noreply, state }
end
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
{ :noreply, d }
{ :noreply, state }
end
def handle_cast(:stop, d) do
{ :stop, :normal, d }
def handle_cast(:stop, state) do
{ :stop, :normal, state }
end
def handle_cast(_msg, d) do
{ :noreply, d }
def handle_cast(msg, state) do
{ :stop, { :bad_cast, msg }, state }
end
def terminate(_reason, _d) do
def terminate(_reason, _state) do
:ok
end
def code_change(_old, d, _extra) do
{ :ok, d }
def code_change(_old, state, _extra) do
{ :ok, state }
end
defp handle_import_or_remote(d, kind, function, module, name, arity) do
defp handle_import(d, function, module, name, arity) do
:digraph.add_vertex(d, module)
replace_edge!(d, kind, module)
tuple = { kind, name, arity }
tuple = { :import, name, arity }
:digraph.add_vertex(d, tuple)
replace_edge!(d, tuple, module)
+2 -2
View File
@@ -156,12 +156,12 @@ defmodule OptionParser do
:boolean in kinds ->
{ nil, value in [true, "true"] }
:integer in kinds ->
case String.to_integer(value) do
case Integer.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
:float in kinds ->
case String.to_float(value) do
case Float.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
+3 -3
View File
@@ -304,11 +304,11 @@ defmodule Process do
end
@doc """
Sets the group leader of Pid to GroupLeader. Typically, this is used when a processes
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
started from a certain shell should have another group leader than `:init`.
"""
@spec group_leader(leader :: pid, pid) :: true
def group_leader(leader, pid) do
@spec group_leader(pid, leader :: pid) :: true
def group_leader(pid, leader) do
:erlang.group_leader(leader, pid)
end
+162 -307
View File
@@ -1,58 +1,90 @@
defmodule Protocol do
@moduledoc false
# 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 keyword list with the protocol functions
when :functions is given;
* `__impl_for__/1` - receives one argument and returns a module
that implements the protocol for the given
data type. If no implementation matches, returns nil;
* `__impl_for__!/1` - same as above but raises an error if an implementation is not found
"""
# Callback for defprotocol.
@doc false
def defprotocol(name, [do: block]) do
quote do
defmodule unquote(name) do
# We don't allow function definition inside protocols
import Kernel, except: [
defmacrop: 1, defmacrop: 2, defmacrop: 4,
defmacro: 1, defmacro: 2, defmacro: 4,
defp: 1, defp: 2, defp: 4,
def: 1, def: 2, def: 4
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
defp: 1, defp: 2, def: 1, def: 2
]
# Import the new dsl that holds the new def
import Protocol.DSL, only: :macros
# Compile with debug info for consolidation
@compile :debug_info
# Set up a clear slate to store defined functions
@functions []
@only nil
@except nil
@fallback_to_any false
# Invoke the user given block
unquote(block)
# Define callbacks and meta information
{ conversions, fallback, returns_nil } = Protocol.conversions_for(__MODULE__, @only, @except)
Protocol.impl_for(conversions, fallback, returns_nil, __ENV__)
Protocol.meta(@functions, conversions, fallback, returns_nil, __ENV__)
# Finalize expansion
unquote(after_defprotocol)
end
end
end
@doc """
Implement the given protocol for the given module.
It also defines a `__impl__` function which
returns the protocol being implemented.
"""
defp after_defprotocol do
quote unquote: false do
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
@spec impl_for(term) :: module | nil
Kernel.def impl_for(data)
lc { guard, body } inlist bodies do
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
end
@spec impl_for!(term) :: module | no_return
Kernel.def impl_for!(data) do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
# Handle special Record type
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
# Handle special Any type
if @fallback_to_any do
Kernel.defp any_impl_for do
try do
__MODULE__.Any.__impl__(:name)
catch
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
nil
end
end
else
Kernel.defp any_impl_for, do: nil
end
# Inline both helpers
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
# Store information as an attribute so it
# can be read without loading the module.
Module.register_attribute(__MODULE__, :protocol, persist: true)
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
# Callback for defimpl.
@doc false
def defimpl(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts))
end
@@ -76,6 +108,10 @@ defmodule Protocol do
unquote(block)
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
@doc false
def __impl__(:name), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
@@ -100,203 +136,88 @@ defmodule Protocol do
end
end
# Implements the function that detects the protocol and returns
# the module to dispatch to. Returns module.Record for records
# which should be properly handled by the dispatching function.
# Builtin types.
@doc false
def impl_for(conversions, fallback, returns_nil, env) do
contents = lc kind inlist conversions do
each_impl_for(kind, conversions, fallback)
end
if returns_nil do
contents = contents ++ [quote do
defp __raw_impl__(_) do
nil
end
end]
end
Module.eval_quoted env.module, contents, [], env.location
def builtin do
[ Tuple, Atom, List, BitString, Integer, Float,
Function, PID, Port, Reference, Any ]
end
# Defines meta information about the protocol and internal callbacks.
# Implements the function that detects the protocol and
# returns the module to dispatch to.
@doc false
def meta(functions, conversions, fallback, returns_nil, env) do
any = L.keyfind(Any, 1, conversions) != false
records = L.keyfind(Record, 1, conversions) != false
def impl_for(current) do
arg = quote(do: arg)
all = [Record|builtin]
meta = quote location: :keep do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: unquote(generate_type(conversions, any))
end
@doc false
def __protocol__(:name), do: __MODULE__
def __protocol__(:functions), do: unquote(:lists.sort(functions))
end
impl_for = cond do
records and fallback ->
quote location: :keep do
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
target = Module.concat(__MODULE__, :erlang.element(1, arg))
try do
target.__impl__
target
catch
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
unquote(fallback)
end
other ->
other
end
end
end
records ->
quote location: :keep do
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
Module.concat(__MODULE__, :erlang.element(1, arg))
other ->
other
end
end
end
true ->
quote location: :keep do
def __impl_for__(arg), do: __raw_impl__(arg)
end
end
impl_bang = if returns_nil do
quote do
def __impl_for__!(arg) do
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: arg)
end
end
else
quote do
def __impl_for__!(arg), do: __impl_for__(arg)
end
end
Module.eval_quoted env.module, [meta, impl_for, impl_bang], [], env.location
{ arg,
lc(mod inlist all, do: impl_for(current, mod, arg)),
rec_impl_for(current, arg) }
end
# Returns the default conversions according to the given
# only/except options.
@doc false
def conversions_for(module, only, except) do
kinds = all_types
defp rec_impl_for(current, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
conversions =
if only do
L.map(fn i -> L.keyfind(i, 1, kinds) end, only)
else
except = except || [Any]
L.foldl(fn i, list -> L.keydelete(i, 1, list) end, kinds, except)
end
fallback = cond do
L.keyfind(Tuple, 1, conversions) ->
Module.concat module, Tuple
L.keyfind(Any, 1, conversions) ->
Module.concat module, Any
true ->
nil
end
# If any is not in the list and we don't implement all
# protocols, we need to handle nil cases.
returns_nil = L.keyfind(Any, 1, conversions) == false and length(conversions) < 10
{ conversions, fallback, returns_nil }
end
## Helpers
defp generate_type(_conversions, true) do
quote(do: any)
end
defp generate_type(conversions, false) do
or_function = fn({ _, _, x }, acc) -> { :|, [], [acc, x] } end
{ _, _, first } = hd(conversions)
:lists.foldl(or_function, first, tl(conversions))
end
defp all_types do
[ { Record, :is_record, quote do: tuple },
{ Tuple, :is_tuple, quote do: tuple },
{ Atom, :is_atom, quote do: atom },
{ List, :is_list, quote do: list },
{ BitString, :is_bitstring, quote do: <<>> },
{ Number, :is_number, quote do: number },
{ Function, :is_function, quote do: (... -> any) },
{ PID, :is_pid, quote do: pid },
{ Port, :is_port, quote do: port },
{ Reference, :is_reference, quote do: reference },
{ Any, :is_any, quote do: any } ]
end
# Returns a quoted expression that allows to check
# if the first item in the tuple is a built-in or not.
defp is_builtin?([{h, _, _}]) do
quote do
first == unquote(h)
end
end
defp is_builtin?([{h, _, _}|t]) do
quote do
first == unquote(h) or unquote(is_builtin?(t))
end
end
# We don't have a fallback, so we assume what was given
# as a record is indeed a record
defp each_impl_for({ _, :is_record, _ }, _conversions, nil) do
quote do
defp __raw_impl__(arg) when is_record(arg) do
__MODULE__.Record
target = Module.concat(unquote(current), unquote(arg))
try do
target.__impl__(:name)
catch
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
unquote(fallback)
end
end
end
# Specially handle records in the case we have fallbacks.
defp each_impl_for({ _, :is_record, _ }, conversions, fallback) do
quote do
defp __raw_impl__(arg) when is_record(arg) do
first = :erlang.element(1, arg)
case unquote(is_builtin?(conversions)) do
true -> unquote(fallback)
false ->
case atom_to_list(first) do
'Elixir.' ++ _ -> __MODULE__.Record
_ -> unquote(fallback)
end
end
defp impl_for(current, Record, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
dispatch = quote do
atom = :erlang.element(1, unquote(arg))
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
true -> rec_impl_for(atom)
false -> unquote(fallback)
end
end
quote do
{ is_record(unquote(arg)), unquote(dispatch) }
end
end
# Special case any as we don't need to generate a guard.
defp each_impl_for({ _, :is_any, _ }, _, _) do
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, arg)
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, arg)
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, arg)
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, arg)
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, arg)
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, arg)
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, arg)
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, arg)
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, arg)
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, arg)
defp impl_for(_current, Any, _arg) do
{ true, quote(do: any_impl_for) }
end
# Defines an implementation with fallback to the given module.
defp impl_with_fallback(mod, guard, current, arg) do
quote do
defp __raw_impl__(_) do
__MODULE__.Any
end
{ :erlang.unquote(guard)(unquote(arg)),
unquote(with_fallback(Module.concat(current, mod))) }
end
end
# Generate all others protocols.
defp each_impl_for({ kind, fun, _ }, _, _) do
# Tries to dispatch to a given target, fallbacks to the
# given `fallback` implementation if the target does not exist.
defp with_fallback(target) when is_atom(target) do
quote do
defp __raw_impl__(arg) when unquote(fun)(arg) do
__MODULE__.unquote(kind)
try do
unquote(target).__impl__(:name)
catch
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
any_impl_for
end
end
end
@@ -305,108 +226,22 @@ end
defmodule Protocol.DSL do
@moduledoc false
# We need to use :lists because Enum is not available yet
require :lists, as: L
@doc false
def args_and_body(module, name, arity) do
# Generate arguments according the arity. The arguments
# are named xa, xb and so forth. We cannot use string
# interpolation to generate the arguments because of compile
# dependencies, so we use the <<>> instead.
args = lc i inlist :lists.seq(1, arity) do
{ binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
end
{ conversions, fallback, returns_nil } = conversions_for(module)
clauses = [default_clause(name, args)]
if returns_nil do
clauses = [nil_clause()|clauses]
end
if L.keyfind(Record, 1, conversions) do
clauses = [record_clause(name, args, fallback)|clauses]
end
body =
quote do
case __raw_impl__(xA), do: unquote({ :->, [], clauses })
end
{ args, body }
defmacro def({ _, _, args }) when args == [] or is_atom(args) do
raise ArgumentError, message: "protocol functions expect at least one argument"
end
@doc false
def callback_from_spec(module, name, arity) do
tuple = { name, arity }
specs = Module.get_attribute(module, :spec)
found = lc { k, v } inlist specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
found != []
end
defp conversions_for(module) do
only = Module.get_attribute(module, :only)
except = Module.get_attribute(module, :except)
Protocol.conversions_for(module, only, except)
end
defp default_clause(name, args) do
{ [quote do: other],
[],
quote(do: apply(other, unquote(name), [unquote_splicing(args)])) }
end
defp nil_clause() do
{ [nil],
[],
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, value: xA)) }
end
defp record_clause(name, args, nil) do
{ [quote(do: __MODULE__.Record)],
[],
quote(do: Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args))) }
end
defp record_clause(name, args, fallback) do
arity = length(args)
{ [quote(do: __MODULE__.Record)],
[],
quote do
target = Module.concat(__MODULE__, :erlang.element(1, xA))
try do
target.unquote(name)(unquote_splicing(args))
catch
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
name == unquote(name) and length(args) == unquote(arity) ->
apply unquote(fallback), name, [unquote_splicing(args)]
end
end }
end
defmacro def(expression) do
case expression do
{ _, _, args } when args == [] or is_atom(args) ->
raise ArgumentError, message: "protocol functions expect at least one argument"
{ name, _, args } when is_atom(name) and is_list(args) ->
:ok
_ ->
raise ArgumentError, message: "invalid args for defprotocol"
end
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
arity = length(args)
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
meta = quote do
call_args = lc i inlist :lists.seq(2, arity),
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
call_args = [quote(do: t) | call_args]
quote do
name = unquote(name)
arity = unquote(arity)
@@ -416,17 +251,37 @@ defmodule Protocol.DSL do
# signature that will be used by docs
Kernel.def unquote(name)(unquote_splicing(args))
# Generate the actual implementation
Kernel.def unquote(name)(unquote_splicing(call_args)) do
impl_for!(t).unquote(name)(unquote_splicing(call_args))
end
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
Protocol.DSL.__spec__?(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
end
impl = quote bind_quoted: [name: name, arity: arity] do
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
Kernel.def unquote(name)(unquote_splicing(args)), do: unquote(body)
defmacro def(_) do
raise ArgumentError, message: "invalid args for def inside defprotocol"
end
@doc false
def __spec__?(module, name, arity) do
case :code.ensure_loaded(Kernel.Typespec) do
{ :module, Kernel.Typespec } ->
tuple = { name, arity }
specs = Module.get_attribute(module, :spec)
found = lc { k, v } inlist specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
found != []
{ :error, _ } ->
true
end
[meta, impl]
end
end
+266
View File
@@ -0,0 +1,266 @@
defmodule Protocol.Consolidation do
@moduledoc """
Module responsible for consolidating protocols and helpers for
extracting protocols and implementations from code paths for
consolidation.
"""
@doc """
Extract all protocols from the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_protocols([path])
iex> Enumerable in mods
true
"""
@spec extract_protocols([char_list | String.t]) :: [atom]
def extract_protocols(paths) do
extract_matching_by_attribute paths, 'Elixir.',
fn module, attributes ->
case attributes[:protocol] do
[fallback_to_any: _, consolidated: _] -> module
_ -> nil
end
end
end
@doc """
Extract all types implemented for the given protocol from
the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
iex> List in mods
true
"""
@spec extract_impls(module, [char_list | String.t]) :: [atom]
def extract_impls(protocol, paths) when is_atom(protocol) do
prefix = atom_to_list(protocol) ++ '.'
extract_matching_by_attribute paths, prefix, fn
_mod, attributes ->
case attributes[:impl] do
[protocol: ^protocol, for: for] -> for
_ -> nil
end
end
end
defp extract_matching_by_attribute(paths, prefix, callback) do
lc path inlist paths,
file inlist list_dir(path),
mod = extract_from_file(path, file, prefix, callback),
do: mod
end
defp list_dir(path) when is_list(path) do
case :file.list_dir(path) do
{ :ok, files } -> files
_ -> []
end
end
defp list_dir(path), do: list_dir(to_char_list(path))
defp extract_from_file(path, file, prefix, callback) do
if :lists.prefix(prefix, file) and Path.extname(file) == '.beam' do
extract_from_beam(Path.join(path, file), callback)
end
end
defp extract_from_beam(file, callback) do
case :beam_lib.chunks(file, [:attributes]) do
{:ok, { module, [attributes: attributes] } } ->
callback.(module, attributes)
_ ->
nil
end
end
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{ :ok, var } -> unquote(Macro.pipe(quote(do: var), call))
other -> other
end
end
end
@doc """
Receives a protocol and a list of implementations and
consolidates the given protocol. Consolidation happens
by changing the protocol `impl_for` in the abstract
format to have fast lookup rules.
It returns the updated version of the protocol bytecode.
A given bytecode or protocol implementation can be checked
to be consolidated or not by analyzing the protocol
attribute:
Enumerable.__info__(:attributes)[:protocol]
If the first element of the tuple is true, it means
the protocol was consolidated.
This function does not load the protocol at any point
nor loads the new bytecode for the compiled module.
"""
@spec apply_to(module, [module]) ::
{ :ok, binary } |
{ :error, :not_a_protocol } |
{ :error, :no_beam_info }
def apply_to(protocol, types) when is_atom(protocol) do
ensure_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
end
# Ensure the given module is loaded and is a protocol.
defp ensure_protocol(protocol) do
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
{ :ok, { ^protocol, [abstract_code: { _raw, abstract_code },
attributes: attributes] } } ->
case attributes[:protocol] do
[fallback_to_any: any, consolidated: _] ->
{ :ok, { protocol, any, abstract_code } }
_ ->
{ :error, :not_a_protocol }
end
_ ->
{ :error, :no_beam_info }
end
end
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
end
end
# Change the debug information to the optimized
# impl_for/1 dispatch version.
defp change_debug_info({ protocol, any, code }, types) do
types = if any, do: types, else: List.delete(types, Any)
records = types -- Protocol.builtin
builtin = Protocol.builtin -- (Protocol.builtin -- types)
builtin = if records != [], do: [Record|builtin], else: builtin
change_impl_for(code, protocol, builtin, records, false, [])
end
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
attr = [fallback_to_any: Any in builtin, consolidated: true]
change_impl_for(t, protocol, builtin, records, true,
[{ :attribute, line, :protocol, attr }|acc])
end
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
unless Any in builtin do
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
end
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :impl_for, 1, clauses }|acc])
end
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
end
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
if is_protocol do
{ :ok, { protocol, Enum.reverse(acc) } }
else
{ :error, :not_a_protocol }
end
end
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
defp clause_for(Any, protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, Module.concat(protocol, Any) }]}
end
defp clause_for(Record, _protocol, line) do
{:clause, line, [{:var, line, :x}],
[[{:op, line, :andalso,
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
[{:var, line, :x}]},
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]
}]},
}]],
[{:call, line,
{:atom, line, :rec_impl_for},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]}]}]}
end
defp builtin_clause_for(mod, guard, protocol, line) do
{:clause, line,
[{:var, line, :x}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
[{:var, line, :x}],
}]],
[{:atom, line, Module.concat(protocol, mod)}]}
end
defp record_clause_for(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, Module.concat(protocol, other)}]}
end
defp fallback_clause_for(value, _protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, value }]}
end
# Finally compile the module and emit its bytecode.
defp compile({ protocol, code }) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{ :ok, ^protocol, binary, _warnings } = :compile.forms(code, [:return|opts])
{ :ok, binary }
end
end
+43 -36
View File
@@ -6,9 +6,9 @@ end
defprotocol Range.Iterator do
@doc """
Reduces the range based on the type of the first argument.
Returns the function that calculates the next item.
"""
def reduce(first, range, acc, fun)
def next(first, range)
@doc """
Count how many items are in the range.
@@ -17,51 +17,58 @@ defprotocol Range.Iterator do
end
defimpl Enumerable, for: Range do
def reduce(Range[first: first] = range, acc, fun) do
Range.Iterator.reduce(first, range, acc, fun)
def reduce(first .. last = range, acc, fun) do
reduce(first, last, acc, fun, Range.Iterator.next(first, range), last >= first)
end
def member?(Range[first: first, last: last], value) do
value in first..last
defp reduce(_x, _y, { :halt, acc }, _fun, _next, _up) do
{ :halted, acc }
end
def count(Range[first: first] = range) do
Range.Iterator.count(first, range)
defp reduce(x, y, { :suspend, acc }, fun, next, up) do
{ :suspended, acc, &reduce(x, y, &1, fun, next, up) }
end
defp reduce(x, y, { :cont, acc }, fun, next, true) when x <= y do
reduce(next.(x), y, fun.(x, acc), fun, next, true)
end
defp reduce(x, y, { :cont, acc }, fun, next, false) when x >= y do
reduce(next.(x), y, fun.(x, acc), fun, next, false)
end
defp reduce(_, _, { :cont, acc }, _fun, _next, _up) do
{ :done, acc }
end
def member?(first .. last, value) do
if first <= last do
{ :ok, first <= value and value <= last }
else
{ :ok, last <= value and value <= first }
end
end
def count(first .. _ = range) do
{ :ok, Range.Iterator.count(first, range) }
end
end
defimpl Range.Iterator, for: Number do
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
reducer = if last >= first do
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
defimpl Range.Iterator, for: Integer do
def next(first, Range[last: last]) when is_integer(last) do
if last >= first do
&(&1 + 1)
else
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
&(&1 - 1)
end
Enumerable.Function.reduce(reducer, acc, fun)
end
defp do_reducer_up(counter, last, acc, _fun) when counter > last do
acc
end
defp do_reducer_up(counter, last, acc, fun) do
do_reducer_up(counter + 1, last, fun.(counter, acc), fun)
end
defp do_reducer_down(counter, last, acc, _fun) when counter < last do
acc
end
defp do_reducer_down(counter, last, acc, fun) do
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
last - first + 1
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
first - last + 1
def count(first, Range[last: last]) when is_integer(last) do
if last >= first do
last - first + 1
else
first - last + 1
end
end
end
+101 -69
View File
@@ -1,19 +1,19 @@
defmodule Record do
@moduledoc %S"""
Convenience functions for working with Records.
Functions for working with Records.
A record is a tagged tuple which contains one or more elements
where the first element is an atom. We can manually create a record
by simply defining such a tuple:
iex> record = { User, "josé", 25 }
iex> record = { User, "José", 25 }
iex> is_record(record, User)
true
However, manually constructing tuples can be quite error prone.
In case we need to add a new field to our User, it would require
If we need to add a new field to our User, it would require
us to carefully change all places where the tuple is used.
Furthermore, as more and more items are added to the tuple, they
Furthermore, as more items are added to the tuple, they
lose semantic value.
This module solves these problems by allowing us to name each
@@ -38,7 +38,7 @@ defmodule Record do
In the example above, `defrecordp` is going to generate a set of
macros named `user` that allows us to create, update and match
on a record. Our record is going to have two fields, a `name` with
default value of "José" and `age` of 25.
default value of "José" and `age` with default value 25.
Let's see some examples:
@@ -75,7 +75,7 @@ defmodule Record do
As the name says, `defrecordp` is useful when you don't want to
expose the record definition. The `user` macro used above, for
example, is only available inside the `User` module and nowhere else.
You can find more information in `Kernel.defrecordp/2` docs.
You can find more information in `Kernel.defrecordp/3` docs.
## defrecord
@@ -120,12 +120,12 @@ defmodule Record do
## Runtime access
All the functionality discussed above happens at compilation time.
This means that both `user(user, age: 26)` and `User[user, age: 26]`
will be transformed into a simple tuple operation at compile time.
This means that both `user(age: 26)` and `User[age: 26]` are expanded
into a tuple at compile time.
However, there are some situations where we want to set or update
fields dynamically. `defrecord` (and `defrecord` only) supports
it out of the box:
fields dynamically. `defrecord` (and not `defrecordp` ) supports
this behaviour out of the box:
defrecord User, name: "José", age: 25
@@ -135,20 +135,23 @@ defmodule Record do
user.update(age: 26)
#=> User[name: "Hello", age: 26]
All the calls above happen at runtime. It gives Elixir
records flexibility at the cost of performance since
there is more work happening at runtime.
All the calls above happen at runtime. It gives Elixir records
flexibility at the cost of performance since there is more work
happening at runtime.
The above calls (new and update) can interchangeably accept both
atom and string keys for field names, however not both at the same time.
Please also note that atom keys are faster. This feature allows to
"sanitize" untrusted dictionaries and initialize/update records without
using `binary_to_existing_atom/1`.
The above calls (`new` and `update`) can accept both atom and string
keys for field names, however not both at the same time. This feature
allows to "sanitize" untrusted dictionaries and initialize/update
records without using `Kernel.binary_to_existing_atom/1`.
To sum up, `defrecordp` should be used when you don't want
to expose the record information while `defrecord` should be used
whenever you want to share a record within your code or with other
libraries or whenever you need to dynamically set or update fields.
To sum up, `defrecordp` should be used when you don't want to expose
the record information while `defrecord` should be used whenever you
want to share a record within your code or with other libraries or
whenever you need to dynamically set or update fields.
The standard library contains excellent examples of both use cases,
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
and [`Range`](Range.html) with `defrecord`.
You can learn more about records in the `Kernel.defrecord/3` docs. Now
let's discuss the usefulness of combining records with protocols.
@@ -173,22 +176,30 @@ defmodule Record do
end
end
Now we can explicitly convert our WeekDate:
Now we can explicitly convert our `WeekDate`:
to_string WeekDate[year: 2013, week: 26, week_day: 4]
"2013-W26-4"
A protocol can be implemented for any record.
A protocol can be implemented for any record, whether
generated with `defrecordp` or `defrecord`.
"""
@type t :: tuple
@doc """
Extract record information from an Erlang file and
return the fields as a list of tuples.
Extract record information from an Erlang file.
Returns the fields as a list of tuples.
## Examples
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
#=> [size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
major_device: :undefined, minor_device: :undefined, inode: :undefined,
uid: :undefined, gid: :undefined]
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
"""
@@ -197,8 +208,9 @@ defmodule Record do
end
@doc """
Imports a public record definition as a set of private macros,
as defined by `Kernel.defrecordp/2`. This is useful when one
Import a public record definition as a set of private macros.
Macros defined as in `Kernel.defrecordp/3`. This is useful when one
desires to manipulate a record via a set of macros instead
of the regular access syntax.
@@ -234,14 +246,14 @@ defmodule Record do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
import Elixir.Record.DSL
import Record.DSL
@record_fields []
@record_types []
Elixir.Record.deffunctions(unquoted_fields, __ENV__)
Record.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Elixir.Record.deftypes(@record_fields, @record_types, __ENV__)
Record.deftypes(@record_fields, @record_types, __ENV__)
value
end
end
@@ -256,35 +268,58 @@ defmodule Record do
defp record_check!(_), do: :ok
@doc false
def defrecordp(name, tag, fields) when is_atom(name) and is_atom(tag) and is_list(fields) do
{ fields, types, def_type } = recordp_split(fields, [], [], false)
type = binary_to_atom(atom_to_binary(name) <> "_t")
tag = tag || name
def defrecordp(name, tag, fields) do
case recordp_split(fields, [], [], false) do
{ :ok, fields, types, def_type } ->
types = Macro.escape(types)
quote do
Record.defmacros(unquote(name), unquote(fields), __ENV__, unquote(tag))
# bind_quoted isn't available when bootstrapping record
quoted = quote [unquote: false] do
Record.defmacros(name, fields, __ENV__, tag)
if unquote(def_type) do
@typep unquote(type)() :: { unquote(tag), unquote_splicing(types) }
end
if def_type do
type = binary_to_atom(atom_to_binary(name) <> "_t")
@typep unquote(type)() :: { unquote(tag || name), unquote_splicing(types) }
end
end
quote do
def_type = unquote(def_type)
fields = unquote(fields)
types = unquote(types)
tag = unquote(tag)
name = unquote(name)
unquote(quoted)
end
:error ->
quote do
name = unquote(name)
Record.defmacros(name, unquote(fields), __ENV__, unquote(tag))
end
end
end
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
recordp_split t, [{ field, default }|defaults], [type|types], true
recordp_split(t, [{ field, default }|defaults], [type|types], true)
end
defp recordp_split([other|t], defaults, types, def_type) do
recordp_split t, [other|defaults], [quote(do: term)|types], def_type
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
end
defp recordp_split([], defaults, types, def_type) do
{ :lists.reverse(defaults), :lists.reverse(types), def_type }
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
end
defp recordp_split(_, _, _, _) do
:error
end
@doc """
Defines record functions skipping the module definition.
This is called directly by `defrecord`. It expects the record
Define record functions skipping the module definition.
This is called directly by `Kernel.defrecord/3`. It expects the record
values, a set of options and the module environment.
## Examples
@@ -311,7 +346,7 @@ defmodule Record do
contents = [quote(do: @record_fields unquote(escaped))|contents]
# Special case for bootstraping purposes
# Special case for bootstrapping purposes
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
@@ -320,7 +355,7 @@ defmodule Record do
end
@doc """
Defines types and specs for the record.
Define types and specs for the record.
"""
def deftypes(values, types, env) do
types = types || []
@@ -334,16 +369,22 @@ defmodule Record do
accessor_specs(values, 1, [])
]
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], env.location)
# We need to handle bootstraping
cond do
:code.ensure_loaded(Kernel.Typespec) != { :module, Kernel.Typespec } ->
nil
env == Macro.Env ->
Module.eval_quoted(env, contents, [], [])
true ->
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@doc """
Defines macros for manipulating records. This is called
directly by `defrecordp`. It expects the macro name, the
Define macros for manipulating records.
This is called
directly by `Kernel.defrecordp/3`. It expects the macro name, the
record values and the environment.
## Examples
@@ -353,7 +394,9 @@ defmodule Record do
end
"""
def defmacros(name, values, env, tag // nil) do
def defmacros(name, values, env, tag // nil)
when is_atom(name) and is_list(values) and is_atom(tag) do
escaped = lc value inlist values do
{ key, value } = convert_value(value)
{ key, Macro.escape(value) }
@@ -417,7 +460,7 @@ defmodule Record do
@doc false
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
raise ArgumentError, message: "expected contents inside brackets to be a keyword list, got: #{inspect keyword}"
end
in_match = caller.in_match?
@@ -792,8 +835,7 @@ defmodule Record do
defp core_specs(values) do
types = lc { _, _, spec } inlist values, do: spec
options = if values == [], do: [], else: [options_specs(values)]
values_specs = if values == [], do: [], else: values_specs(values)
options = lc { k, _, v } inlist values, do: { k, v }
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@@ -801,7 +843,7 @@ defmodule Record do
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@type options :: unquote(options) | [{String.t, unquote(values_specs)}]
@type options :: unquote(options) | [{String.t, term}]
end
@spec new :: t
@@ -814,17 +856,6 @@ defmodule Record do
end
end
defp options_specs([{ k, _, v }|t]) do
:lists.foldl fn { k, _, v }, acc ->
{ :|, [], [{ k, v }, acc] }
end, { k, v }, t
end
defp values_specs([{ _, _, v }|t]) do
:lists.foldl fn { _, _, v }, acc ->
{ :|, [], [v, acc] }
end, v, t
end
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
accessor_specs(t, 2, acc)
end
@@ -901,6 +932,7 @@ defmodule Record.DSL do
@doc """
Defines the type for each field in the record.
Expects a keyword list.
"""
defmacro record_type(opts) when is_list(opts) do
+7 -6
View File
@@ -6,10 +6,11 @@ defmodule Record.Extractor do
def retrieve(name, from: file) when is_binary(file) do
file = String.to_char_list!(file)
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> :ok
end
realfile =
case :code.where_is_file(file) do
:non_existing -> file
realfile -> realfile
end
retrieve_record(name, realfile)
end
@@ -19,7 +20,7 @@ defmodule Record.Extractor do
def retrieve(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(String.to_char_list!(file))
case :code.lib_dir(app) do
case :code.lib_dir(list_to_atom(app)) do
{ :error, _ } ->
raise ArgumentError, message: "lib file #{file} could not be found"
libpath ->
@@ -88,4 +89,4 @@ defmodule Record.Extractor do
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
record
end
end
end
+13 -15
View File
@@ -4,19 +4,18 @@ defmodule Regex do
in the Erlang Standard Library. More information can be found
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
Regular expressions in Elixir can be created using `Regex.compile!`
or using the special form with `%r`:
Regular expressions in Elixir can be created using `Regex.compile!/2`
or using the special form with [`%r`](Kernel.html#sigil_r/2):
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
# A regular expression with case insensitive options and handling for unicode chars
%r/foo/iu
# A regular expression with case insensitive options
%r/foo/i
The `re` module provides several options, the ones available in Elixir, followed by
their shortcut in parenthesis, are:
* `unicode` (u) - enables unicode specific patterns like \p
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
@@ -44,7 +43,7 @@ defmodule Regex do
"""
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, binary, binary, [atom] }
@type t :: { Regex, term, binary, binary, [atom] | nil }
defexception CompileError, message: "regex could not be compiled"
@@ -66,7 +65,7 @@ defmodule Regex do
{:error, {'nothing to repeat', 0}}
"""
@spec compile(binary, binary | [term]) :: t
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
def compile(source, options // "")
def compile(source, options) when is_binary(options) do
@@ -75,7 +74,9 @@ defmodule Regex do
{ :error, { :invalid_option, rest } }
translated_options ->
compile(source, translated_options, options)
# Always use the unicode option, we don't have a latin1 legacy like
# Erlang.
compile(source, [:unicode|translated_options], options)
end
end
@@ -156,12 +157,6 @@ defmodule Regex do
end
end
@doc false
def captures(regex, string, options // []) do
IO.write "Regex.captures/3 is deprecated, please use Regex.named_captures/3\n#{Exception.format_stacktrace}"
named_captures(regex, string, options)
end
@doc """
Returns the given captures as a keyword list or `nil` if no captures
are found. Requires the regex to be compiled with the groups option.
@@ -373,7 +368,10 @@ defmodule Regex do
defp return_for(element) when is_binary(element), do: :binary
defp return_for(element) when is_list(element), do: :list
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?u, t :: binary>>) do
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
translate_options(t)
end
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)]
+15
View File
@@ -15,6 +15,21 @@ defmodule Set do
For simplicity's sake, in the examples below every time
`new` is used, it implies one of the module-specific
calls like above.
## Protocols
Sets are required to implement the `Enumerable` protocol,
allowing one to write:
Enum.each(set, fn k ->
IO.inspect k
end)
## Match
Sets are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
"""
use Behaviour
+780 -269
View File
File diff suppressed because it is too large Load Diff
+188
View File
@@ -0,0 +1,188 @@
defmodule Stream.Reducers do
# Collection of reducers shared by Enum and Stream.
@moduledoc false
defmacro chunk(n, step, limit, f // nil) do
quote do
fn entry, acc(h, { buffer, count }, t) ->
buffer = [entry|buffer]
count = count + 1
new =
if count >= unquote(limit) do
left = count - unquote(step)
{ Enum.take(buffer, left), left }
else
{ buffer, count }
end
if count == unquote(n) do
cont_with_acc(unquote(f), :lists.reverse(buffer), h, new, t)
else
{ :cont, acc(h, new, t) }
end
end
end
end
defmacro chunk_by(callback, f // nil) do
quote do
fn
entry, acc(h, { buffer, value }, t) ->
new_value = unquote(callback).(entry)
if new_value == value do
{ :cont, acc(h, { [entry|buffer], value }, t) }
else
cont_with_acc(unquote(f), :lists.reverse(buffer), h, { [entry], new_value }, t)
end
entry, acc(h, nil, t) ->
{ :cont, acc(h, { [entry], unquote(callback).(entry) }, t) }
end
end
end
defmacro drop(f // nil) do
quote do
fn
_entry, acc(h, n, t) when n > 0 ->
{ :cont, acc(h, n-1, t) }
entry, acc(h, n, t) ->
cont_with_acc(unquote(f), entry, h, n, t)
end
end
end
defmacro drop_while(callback, f // nil) do
quote do
fn entry, acc(h, bool, t) = orig ->
if bool and unquote(callback).(entry) do
{ :cont, orig }
else
cont_with_acc(unquote(f), entry, h, false, t)
end
end
end
end
defmacro filter(callback, f // nil) do
quote do
fn(entry, acc) ->
if unquote(callback).(entry) do
cont(unquote(f), entry, acc)
else
{ :cont, acc }
end
end
end
end
defmacro filter_map(filter, mapper, f // nil) do
quote do
fn(entry, acc) ->
if unquote(filter).(entry) do
cont(unquote(f), unquote(mapper).(entry), acc)
else
{ :cont, acc }
end
end
end
end
defmacro map(callback, f // nil) do
quote do
fn(entry, acc) ->
cont(unquote(f), unquote(callback).(entry), acc)
end
end
end
defmacro reject(callback, f // nil) do
quote do
fn(entry, acc) ->
unless unquote(callback).(entry) do
cont(unquote(f), entry, acc)
else
{ :cont, acc }
end
end
end
end
defmacro scan_2(callback, f // nil) do
quote do
fn
entry, acc(h, :first, t) ->
cont_with_acc(unquote(f), entry, h, { :ok, entry }, t)
entry, acc(h, { :ok, acc }, t) ->
value = unquote(callback).(entry, acc)
cont_with_acc(unquote(f), value, h, { :ok, value }, t)
end
end
end
defmacro scan_3(callback, f // nil) do
quote do
fn(entry, acc(h, acc, t)) ->
value = unquote(callback).(entry, acc)
cont_with_acc(unquote(f), value, h, value, t)
end
end
end
defmacro take(f // nil) do
quote do
fn(entry, acc(h, n, t) = orig) ->
if n >= 1 do
cont_with_acc(unquote(f), entry, h, n-1, t)
else
{ :halt, orig }
end
end
end
end
defmacro take_every(nth, f // nil) do
quote do
fn
entry, acc(h, n, t) when n === :first
when n === unquote(nth) ->
cont_with_acc(unquote(f), entry, h, 1, t)
entry, acc(h, n, t) ->
{ :cont, acc(h, n+1, t) }
end
end
end
defmacro take_while(callback, f // nil) do
quote do
fn(entry, acc) ->
if unquote(callback).(entry) do
cont(unquote(f), entry, acc)
else
{ :halt, acc }
end
end
end
end
defmacro uniq(callback, f // nil) do
quote do
fn(entry, acc(h, prev, t) = acc) ->
value = unquote(callback).(entry)
if :lists.member(value, prev) do
{ :cont, acc }
else
cont_with_acc(unquote(f), entry, h, [value|prev], t)
end
end
end
end
defmacro with_index(f // nil) do
quote do
fn(entry, acc(h, counter, t)) ->
cont_with_acc(unquote(f), { entry, counter }, h, counter + 1, t)
end
end
end
end
+99 -87
View File
@@ -1,3 +1,5 @@
import Kernel, except: [length: 1]
defmodule String do
@moduledoc %S"""
A String in Elixir is a UTF-8 encoded binary.
@@ -5,7 +7,7 @@ defmodule String do
## String and binary operations
The functions in this module act according to the
Unicode Standard, version 6.2.0. For example,
Unicode Standard, version 6.3.0. For example,
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
module.
@@ -16,7 +18,7 @@ defmodule String do
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of conversion functions, like `Kernel.binary_to_atom/2`,
* Plus a number of conversion functions, like `Kernel.binary_to_atom/1`,
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
like `Kernel.integer_to_binary/2`
@@ -30,10 +32,14 @@ defmodule String do
For example, the character "é" is represented with two
bytes:
iex> string = "é"
...> byte_size(string)
iex> byte_size("é")
2
However, this module returns the proper length:
iex> String.length("é")
1
Furthermore, this module also presents the concept of
graphemes, which are multiple characters that may be
"perceived as a single character" by readers. For example,
@@ -43,6 +49,8 @@ defmodule String do
iex> string = "\x{0065}\x{0301}"
...> byte_size(string)
3
iex> String.length(string)
1
Although the example above is made of two characters, it is
perceived by users as one.
@@ -57,6 +65,11 @@ defmodule String do
Standard, but does not contain any of the locale specific
behaviour.
More information about graphemes can be found in the [Unicode
Standard Annex #29](http://www.unicode.org/reports/tr29/).
This current Elixir version implements Extended Grapheme Cluster
algorithm.
## Integer codepoints
Although codepoints could be represented as integers, this
@@ -139,7 +152,7 @@ defmodule String do
def printable?(_), do: false
@doc """
Splits a string on substrings at each Unicode whitespace
Divides a string into substrings at each Unicode whitespace
occurrence with leading and trailing whitespace ignored.
## Examples
@@ -209,7 +222,7 @@ defmodule String do
def split("", _pattern, _options), do: [""]
def split(binary, "", options), do: split(binary, %r""u, options)
def split(binary, "", options), do: split(binary, %r"", options)
def split(binary, pattern, options) when is_regex(pattern) do
Regex.split(pattern, binary, options)
@@ -454,13 +467,13 @@ defmodule String do
end
defp do_justify(subject, len, padding, type) when is_integer(padding) do
subject_len = String.length(subject)
subject_len = length(subject)
cond do
subject_len >= len ->
subject
subject_len < len ->
fill = String.duplicate(<<padding :: utf8>>, len - subject_len)
fill = duplicate(<<padding :: utf8>>, len - subject_len)
case type do
:left -> subject <> fill
@@ -484,7 +497,7 @@ defmodule String do
"a-b,c"
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific catpure in the regex:
in the `replacement` string to access a specific capture in the regex:
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
"a,bb,cc"
@@ -542,11 +555,11 @@ defmodule String do
"""
@spec reverse(t) :: t
def reverse(string) do
do_reverse(String.Unicode.next_grapheme(string), [])
do_reverse(next_grapheme(string), [])
end
defp do_reverse({grapheme, rest}, acc) do
do_reverse(String.Unicode.next_grapheme(rest), [grapheme|acc])
do_reverse(next_grapheme(rest), [grapheme|acc])
end
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
@@ -603,6 +616,7 @@ defmodule String do
{ "j", "osé" }
"""
@compile { :inline, next_codepoint: 1 }
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
defdelegate next_codepoint(string), to: String.Unicode
@@ -661,17 +675,10 @@ defmodule String do
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc false
def valid_codepoint?(binary) do
IO.write "String.valid_codepoint?/1 is deprecated, please use match against << _ :: utf8 >> instead\n#{Exception.format_stacktrace}"
do_valid_codepoint?(binary)
end
defp do_valid_codepoint?(<<_ :: utf8>>), do: true
defp do_valid_codepoint?(_), do: false
@doc """
Returns unicode graphemes in the string.
Returns unicode graphemes in the string as per Extended Grapheme
Cluster algorithm outlined in the [Unicode Standard Annex #29,
Unicode Text Segmentation](http://www.unicode.org/reports/tr29/).
## Examples
@@ -680,7 +687,7 @@ defmodule String do
"""
@spec graphemes(t) :: [grapheme]
defdelegate graphemes(string), to: String.Unicode
defdelegate graphemes(string), to: String.Graphemes
@doc """
Returns the next grapheme in a String.
@@ -695,8 +702,9 @@ defmodule String do
{ "j", "osé" }
"""
@compile { :inline, next_grapheme: 1 }
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
defdelegate next_grapheme(string), to: String.Unicode
defdelegate next_grapheme(string), to: String.Graphemes
@doc """
Returns the first grapheme from an utf8 string,
@@ -788,7 +796,7 @@ defmodule String do
end
def at(string, position) when position < 0 do
real_pos = do_length(next_grapheme(string)) - abs(position)
real_pos = length(string) - abs(position)
case real_pos >= 0 do
true -> do_at(next_grapheme(string), real_pos, 0)
false -> nil
@@ -833,7 +841,7 @@ defmodule String do
@spec slice(t, integer, integer) :: grapheme | nil
def slice(string, start, 0) do
case abs(start) <= String.length(string) do
case abs(start) <= length(string) do
true -> ""
false -> nil
end
@@ -844,13 +852,72 @@ defmodule String do
end
def slice(string, start, len) when start < 0 and len >= 0 do
real_start_pos = do_length(next_grapheme(string)) - abs(start)
real_start_pos = length(string) - abs(start)
case real_start_pos >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
false -> nil
end
end
@doc """
Returns a substring from the offset given by the start of the
range to the offset given by the end of the range.
If the start of the range is not a valid offset for the given
string or if the range is in reverse order, returns `nil`.
## Examples
iex> String.slice("elixir", 1..3)
"lix"
iex> String.slice("elixir", 1..10)
"lixir"
iex> String.slice("elixir", 10..3)
nil
iex> String.slice("elixir", -4..-1)
"ixir"
iex> String.slice("elixir", 2..-1)
"ixir"
iex> String.slice("elixir", -4..6)
"ixir"
iex> String.slice("elixir", -1..-4)
nil
iex> String.slice("elixir", -10..-7)
nil
iex> String.slice("a", 0..1500)
"a"
iex> String.slice("a", 1..1500)
""
iex> String.slice("a", 2..1500)
nil
"""
@spec slice(t, Range.t) :: t | nil
def slice(string, range)
def slice(string, first..last) when first >= 0 and last >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
end
def slice(string, first..last) do
total = length(string)
if first < 0 do
first = total + first
end
if last < 0 do
last = total + last
end
if first > 0 do
do_slice(next_grapheme(string), first, last, 0, "")
end
end
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
nil
end
@@ -878,64 +945,6 @@ defmodule String do
end
end
@doc """
Converts a string to an integer. If successful, returns a
tuple of the form `{integer, remainder of string}`. If unsuccessful,
returns `:error`.
## Examples
iex> String.to_integer("34")
{34,""}
iex> String.to_integer("34.5")
{34,".5"}
iex> String.to_integer("three")
:error
"""
@spec to_integer(t) :: {integer, t} | :error
def to_integer(string) do
{result, remainder} = :string.to_integer(:binary.bin_to_list(string))
case result do
:error -> :error
_ -> {result, :binary.list_to_bin(remainder)}
end
end
@doc """
Converts a string to a float. If successful, returns a
tuple of the form `{float, remainder of string}`.
If unsuccessful, returns `:error`.
## Examples
iex> String.to_float("34")
{34.0,""}
iex> String.to_float("34.25")
{34.25,""}
iex> String.to_float("56.5xyz")
{56.5,"xyz"}
iex> String.to_float("pi")
:error
"""
@spec to_float(t) :: {integer, t} | :error
def to_float(string) do
charlist = :binary.bin_to_list(string)
{result, remainder} = :string.to_float(charlist)
case result do
:error ->
{int_result, int_remainder} = :string.to_integer(charlist)
case int_result do
:error -> :error
_ -> {:erlang.float(int_result), :binary.list_to_bin(int_remainder)}
end
_ -> {result, :binary.list_to_bin(remainder)}
end
end
@doc """
Returns `true` if `string` starts with any of the prefixes given, otherwise
`false`. `prefixes` can be either a single prefix or a list of prefixes.
@@ -1047,9 +1056,12 @@ defmodule String do
raise ArgumentError
end
defexception UnicodeConversionError, encoded: nil, rest: nil, kind: nil do
def message(exception) do
"#{exception.kind} #{detail(exception.rest)}"
defexception UnicodeConversionError, [:encoded, :message] do
def exception(opts) do
UnicodeConversionError[
encoded: opts[:encoded],
message: "#{opts[:kind]} #{detail(opts[:rest])}"
]
end
defp detail(rest) when is_binary(rest) do
+9 -6
View File
@@ -14,8 +14,6 @@ defprotocol String.Chars do
as `"foo" <> to_string(bar)`.
"""
@only [BitString, List, Number, Atom, Record]
def to_string(thing)
end
@@ -29,7 +27,7 @@ defimpl String.Chars, for: Atom do
end
def to_string(atom) do
atom_to_binary(atom, :utf8)
atom_to_binary(atom)
end
end
@@ -67,14 +65,19 @@ defimpl String.Chars, for: List do
def to_string(char_list), do: String.from_char_list!(char_list)
end
defimpl String.Chars, for: Number do
defimpl String.Chars, for: Integer do
@doc """
Simply converts the number (integer or a float) to a binary.
Simply converts the integer to a string.
"""
def to_string(thing) when is_integer(thing) do
def to_string(thing) do
integer_to_binary(thing)
end
end
defimpl String.Chars, for: Float do
@doc """
Simply converts the float to a string.
"""
def to_string(thing) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
+58 -25
View File
@@ -9,8 +9,8 @@ defmodule System do
message: "could not get a current working directory, the current location is not accessible"
@moduledoc """
The System module provides access to some variables used or
maintained by the VM and to functions that interact strongly
The System module provides access to variables used or
maintained by the VM and to functions that interact directly
with the VM or the host system.
"""
@@ -57,13 +57,17 @@ defmodule System do
end
@doc """
Elixir version information.
Returns Elixir's version as binary.
"""
@spec version() :: String.t
def version, do: get_version
@doc """
Returns a keywords list with version, git tag info and date.
Elixir build information.
Returns a keyword list with Elixir version, git tag info and compilation date.
"""
@spec build_info() :: Keyword.t
def build_info do
@@ -71,7 +75,9 @@ defmodule System do
end
@doc """
Returns the list of command-line arguments passed to the program.
List command line arguments.
Returns the list of command line arguments passed to the program.
"""
@spec argv() :: [String.t]
def argv do
@@ -79,15 +85,19 @@ defmodule System do
end
@doc """
Changes the list of command-line arguments. Use it with caution,
as it destory any previous argv information.
Modify command line arguments.
Changes the list of command line arguments. Use it with caution,
as it destroys any previous argv information.
"""
@spec argv([String.t]) :: :ok
def argv(argv) do
:elixir_code_server.cast({ :argv, argv })
def argv(args) do
:elixir_code_server.cast({ :argv, args })
end
@doc """
Current working directory.
Returns the current working directory or `nil` if one
is not available.
"""
@@ -99,6 +109,8 @@ defmodule System do
end
@doc """
Current working directory, exception on error.
Returns the current working directory or raises `System.NoAccessCwdError`.
"""
def cwd! do
@@ -106,8 +118,10 @@ defmodule System do
end
@doc """
Returns the user home (platform independent).
It returns `nil` if no user home is set.
User home directory.
Returns the user home directory (platform independent).
Returns `nil` if no user home is set.
"""
def user_home do
case :os.type() do
@@ -117,6 +131,8 @@ defmodule System do
end
@doc """
User home directory, exception on error.
Same as `user_home/0` but raises `System.NoHomeError`
instead of returning `nil` if no user home is set.
"""
@@ -139,8 +155,10 @@ defmodule System do
end
@doc %S"""
Writable temporary directory.
Returns a writable temporary directory.
It searches for directories in the following order:
Searches for directories in the following order:
1. The directory named by the TMPDIR environment variable
2. The directory named by the TEMP environment variable
@@ -159,7 +177,9 @@ defmodule System do
end
@doc """
Same as `tmp_dir` but raises `System.NoTmpDirError`
Writable temporary directory, exception on error.
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
instead of returning `nil` if no temp dir is set.
"""
def tmp_dir! do
@@ -189,18 +209,22 @@ defmodule System do
end
@doc """
Register a program exit handler function.
Registers a function that will be invoked
at the end of program execution. Useful for
invoking a hook in a "script" mode.
invoking a hook in "script" mode.
The function must expect the exit status code
as argument.
The function must receive the exit status code
as an argument.
"""
def at_exit(fun) when is_function(fun, 1) do
:elixir_code_server.cast { :at_exit, fun }
end
@doc """
Execute a system command.
Executes `command` in a command shell of the target OS,
captures the standard output of the command and returns
the result as a binary.
@@ -222,6 +246,8 @@ defmodule System do
end
@doc """
Locate an executable on the system.
This function looks up an executable program given
its name using the environment variable PATH on Unix
and Windows. It also considers the proper executable
@@ -248,6 +274,8 @@ defmodule System do
end
@doc """
System environment variables.
Returns a list of all environment variables. Each variable is given as a
`{name, value}` tuple where both `name` and `value` are strings.
"""
@@ -261,6 +289,8 @@ defmodule System do
end
@doc """
Environment variable value.
Returns the value of the environment variable
`varname` as a binary, or `nil` if the environment
variable is undefined.
@@ -274,6 +304,8 @@ defmodule System do
end
@doc """
Erlang VM process identifier.
Returns the process identifier of the current Erlang emulator
in the format most commonly used by the operating system environment.
@@ -283,6 +315,8 @@ defmodule System do
def get_pid, do: iolist_to_binary(:os.getpid)
@doc """
Set an environment variable value.
Sets a new `value` for the environment variable `varname`.
"""
@spec put_env(binary, binary) :: :ok
@@ -292,6 +326,8 @@ defmodule System do
end
@doc """
Set multiple environment variables.
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
@@ -301,22 +337,24 @@ defmodule System do
end
@doc """
Gets Elixir's stacktrace.
Last exception stacktrace.
Notice the Erlang VM (and therefore this function) does not
Note that the Erlang VM (and therefore this function) does not
return the current stacktrace but rather the stacktrace of the
latest exception.
"""
def stacktrace do
filter_stacktrace :erlang.get_stacktrace
:erlang.get_stacktrace
end
@doc """
Halts the Erlang runtime system where the first argument status must be a
Halt the Erlang runtime system.
Halts the Erlang runtime system where the argument `status` must be a
non-negative integer, the atom `:abort` or a binary.
* If an integer, the runtime system exits with the integer value which
is returned to the Operating System;
is returned to the operating system;
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system;
@@ -347,9 +385,4 @@ defmodule System do
def halt(status) when is_binary(status) do
:erlang.halt(:binary.bin_to_list(status))
end
## Helpers
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: t
defp filter_stacktrace(t), do: t
end
+60
View File
@@ -0,0 +1,60 @@
defmodule Tuple do
@moduledoc """
Functions for working with tuples.
"""
@doc """
Create a new tuple.
Creates a tuple of size `size` containing the
given `data` at every position.
## Examples
iex> Tuple.duplicate(:hello, 3)
{ :hello, :hello, :hello }
"""
@spec duplicate(term, non_neg_integer) :: tuple
def duplicate(data, size) do
:erlang.make_tuple(size, data)
end
@doc """
Insert an element into a tuple.
Inserts `value` into `tuple` at the given zero-based `index`.
Raises an `ArgumentError` if `index` is greater than the
length of `tuple`.
## Examples
iex> tuple = { :bar, :baz }
...> Tuple.insert_at(tuple, 0, :foo)
{ :foo, :bar, :baz }
"""
@spec insert_at(tuple, non_neg_integer, term) :: tuple
def insert_at(tuple, index, term) do
:erlang.insert_element(index + 1, tuple, term)
end
@doc """
Remove an element from a tuple.
Deletes the element at the zero-based `index` from `tuple`.
Raises an `ArgumentError` if `index` is greater than
or equal to the length of `tuple`.
## Examples
iex> tuple = { :foo, :bar, :baz }
...> Tuple.delete_at(tuple, 0)
{ :bar, :baz }
"""
@spec delete_at(tuple, non_neg_integer) :: tuple
def delete_at(tuple, index) do
:erlang.delete_element(index + 1, tuple)
end
end
+80 -32
View File
@@ -31,8 +31,18 @@ defmodule URI do
@doc """
Returns the default port for a given scheme.
If the scheme is unknown to URI, returns `nil`.
Any scheme may be registered via `default_port/2`.
## Examples
iex> URI.default_port("ftp")
21
iex> URI.default_port("ponzi")
nil
"""
def default_port(scheme) when is_binary(scheme) do
{ :ok, dict } = :application.get_env(:elixir, :uri)
@@ -48,20 +58,43 @@ defmodule URI do
end
@doc """
Encodes an enumerable into a query string.
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of the form "k=v&k2=v2..." where keys and values are
URL encoded as per `encode/1`. Keys and values can be any term
that implements the `String.Chars` protocol (i.e. can be converted
and returns a string of the form "key1=value1&key2=value2..." where
keys and values are URL encoded as per `encode/1`. Keys and values can
be any term that implements the `String.Chars` protocol (i.e. can be converted
to a binary).
## Examples
iex> hd = HashDict.new([{"foo", 1}, {"bar", "2"}])
iex> URI.encode_query(hd)
"bar=2&foo=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.
Decodes a query string into a `HashDict`.
Given a query string of the form "key1=value1&key2=value2...", produces a
`HashDict` with one entry for each key-value pair. Each key and value will be a
binary. Keys and values will be percent-unescaped.
Use `query_decoder/1` if you want to iterate over each value manually.
## Examples
iex> URI.decode_query("foo=1&bar=2") |> Dict.to_list
[{"bar", "2"}, {"foo", "1"}]
iex> hd = HashDict.new()
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
["bar", "foo"]
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.values
["2", "1"]
"""
def decode_query(q, dict // HashDict.new) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
@@ -70,22 +103,26 @@ defmodule URI do
@doc """
Returns an iterator function over the query string that decodes
the query string in steps.
## Examples
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
[{"foo", "1"}, {"bar", "2"}]
"""
def query_decoder(q) when is_binary(q) do
fn(acc, fun) ->
do_decoder(q, acc, fun)
end
Stream.unfold(q, &do_decoder/1)
end
defp do_decoder("", acc, _fun) do
acc
defp do_decoder("") do
nil
end
defp do_decoder(q, acc, fun) do
next =
defp do_decoder(q) do
{ first, next } =
case :binary.split(q, "&") do
[first, rest] -> rest
[first] -> ""
[first, rest] -> { first, rest }
[first] -> { first, "" }
end
current =
@@ -94,7 +131,7 @@ defmodule URI do
[ key ] -> { decode(key), nil }
end
do_decoder(next, fun.(current, acc), fun)
{ current, next }
end
defp pair({k, v}) do
@@ -102,7 +139,13 @@ defmodule URI do
end
@doc """
Percent (URL) encodes a URI.
Percent-escape a URI.
## Example
iex> URI.encode("http://elixir-lang.com/getting_started/2.html")
"http%3A%2F%2Felixir-lang.com%2Fgetting_started%2F2.html"
"""
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
@@ -124,7 +167,13 @@ defmodule URI do
defp hex(n), do: <<n + ?A - 10>>
@doc """
Unpercent (URL) decodes a URI.
Percent-unescape a URI.
## Examples
iex> URI.decode("http%3A%2F%2Felixir-lang.com")
"http://elixir-lang.com"
"""
def decode(<<?%, hex1, hex2, tail :: binary >>) do
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
@@ -137,6 +186,7 @@ defmodule URI do
def decode(<<>>), do: <<>>
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
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
@@ -145,20 +195,18 @@ defmodule URI do
@doc """
Parses a URI into components.
URIs have portions that are handled specially for the
particular scheme of the URI. For example, http and https
have different default ports. Sometimes the parsing
of portions themselves are different. This parser
is extensible via behavior modules. If you have a
module named `URI.MYSCHEME` with a function called
`parse` that takes a single argument, the generically
parsed URI, that function will be called when this
parse function is passed a URI of that scheme. This
allows you to build on top of what the URI library
currently offers. You also need to define `default_port`
which takes no arguments and returns the default port
for that particular scheme. Take a look at `URI.HTTPS` for an
example of one of these extension modules.
URIs have portions that are handled specially for the particular
scheme of the URI. For example, http and https have different
default ports. Such values can be accessed and registered via
`URI.default_port/1` and `URI.default_port/2`.
## Examples
iex> URI.parse("http://elixir-lang.org/")
URI.Info[scheme: "http", path: "/", query: nil, fragment: nil,
authority: "elixir-lang.org", userinfo: nil,
host: "elixir-lang.org", port: 80]
"""
def parse(s) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
+63 -25
View File
@@ -1,15 +1,13 @@
defmodule Version do
@moduledoc %S"""
This module provides functions for parsing and matching
versions with requirements.
Functions for parsing and matching versions against requirements.
A version is a string or a `Version.Schema` generated
after parsing via `Version.parse/1`. A requirement is
a string that follows a specific format.
A version is a string in a specific format or a `Version.Schema`
generated after parsing via `Version.parse/1`.
`Version` parsing and requirements follows
`Version` parsing and requirements follow
[SemVer 2.0 schema](http://semver.org/) and you will get
the most of Mix' version system by following it. In order
the most of Mix's version system by following it. In order
to support integration with projects that may
follow different versioning schemas, Elixir won't choke
on unknown versions, however you won't be able to use
@@ -33,7 +31,7 @@ defmodule Version do
Requirements allow you to specify which versions of a given
dependency you are willing to work against. It supports common
operators like `>=`, `<=`, `>`, `==` and friends that would
operators like `>=`, `<=`, `>`, `==` and friends that
work as one would expect:
# Only version 2.0.0
@@ -67,19 +65,33 @@ defmodule Version do
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
defrecord Requirement, source: nil, matchspec: nil
defexception InvalidRequirement, reason: :invalid_requirement do
def message(InvalidRequirement[reason: reason]) when is_binary(reason) do
{ first, rest } = String.next_grapheme(reason)
String.downcase(first) <> rest
end
defexception InvalidRequirement, [:message] do
def exception(opts) do
message =
if is_binary opts[:reason] do
{ first, rest } = String.next_grapheme(opts[:reason])
String.downcase(first) <> rest
else
"invalid version specification"
end
def message(InvalidRequirement[]) do
"invalid version specification"
InvalidRequirement[message: message]
end
end
@doc """
Checks if the given version matches the specification.
Check if the given version matches the specification.
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
## Examples
iex> Version.match?("2.0", ">1.0")
true
iex> Version.match?("2.0", "==1.0")
false
"""
@spec match?(t, requirement) :: boolean
def match?(version, requirement) when is_binary(requirement) do
@@ -105,7 +117,15 @@ defmodule Version do
end
@doc """
Checks if a version string is compatible with [semver](http://semver.org/).
Check if a version string is compatible with [semver](http://semver.org/).
## Examples
iex> Version.valid?("2.0")
true
iex> Version.valid?("invalid")
false
"""
@spec valid?(String.t | Schema.t) :: boolean
def valid?(string) when is_binary(string) do
@@ -116,9 +136,15 @@ defmodule Version do
def valid?(Version.Schema[]), do: true
@doc """
Parse a version into a matchable value.
Parse a version string into a `Version.Schema`.
## Examples
iex> Version.parse("2.0.1-alpha1")
#Version.Schema<2.0.1-alpha1>
"""
@spec parse(String.t) :: { :ok, Schema.t } | { :error, term }
@spec parse(String.t) :: Schema.t
def parse(string) when is_binary(string) do
case Version.Parser.parse_version(string) do
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
@@ -137,7 +163,13 @@ defmodule Version do
end
@doc """
Get the matchable representation.
Convert a version to a `Version.matchable`
## Examples
iex> Version.to_matchable("2.0.1-alpha.1")
{2, 0, 1, ["alpha", 1]}
"""
@spec to_matchable(String.t | Schema.t) :: Version.matchable
def to_matchable(Schema[major: nil, source: source]) do
@@ -157,7 +189,13 @@ defmodule Version do
end
@doc """
Convert a matchable to a `Version`.
Convert a matchable to a `Version.Schema`.
## Examples
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
#Version.Schema<2.0.1-alpha.1>
"""
@spec from_matchable(Version.matchable) :: Schema.t
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
@@ -366,16 +404,16 @@ defmodule Version do
defp approximate(version) do
Version.from_matchable(case Regex.run(@version_regex, version) do
[_, major] ->
{ binary_to_integer(major) + 1, 0, 0, [] }
{ binary_to_integer(major) + 1, 0, 0, [0] }
[_, major, _] ->
{ binary_to_integer(major) + 1, 0, 0, [] }
{ binary_to_integer(major) + 1, 0, 0, [0] }
[_, major, minor, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [] }
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
[_, major, minor, _, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [] }
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
end)
end
-420
View File
@@ -1,420 +0,0 @@
LATIN CAPITAL LETTER A WITH MACRON AND GRAVE;0100 0300
LATIN SMALL LETTER A WITH MACRON AND GRAVE;0101 0300
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW;0045 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW;0065 0329
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00C8 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00E8 0329
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00C9 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00E9 0329
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND MACRON;00CA 0304
LATIN SMALL LETTER E WITH CIRCUMFLEX AND MACRON;00EA 0304
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND CARON;00CA 030C
LATIN SMALL LETTER E WITH CIRCUMFLEX AND CARON;00EA 030C
LATIN CAPITAL LETTER I WITH MACRON AND GRAVE;012A 0300
LATIN SMALL LETTER I WITH MACRON AND GRAVE;012B 0300
LATIN SMALL LETTER I WITH DOT ABOVE AND ACUTE;0069 0307 0301
LATIN SMALL LETTER NG WITH TILDE ABOVE;006E 0360 0067
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW;004F 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW;006F 0329
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00D2 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00F2 0329
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00D3 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00F3 0329
LATIN CAPITAL LETTER S WITH VERTICAL LINE BELOW;0053 0329
LATIN SMALL LETTER S WITH VERTICAL LINE BELOW;0073 0329
LATIN CAPITAL LETTER U WITH MACRON AND GRAVE;016A 0300
LATIN SMALL LETTER U WITH MACRON AND GRAVE;016B 0300
LATIN CAPITAL LETTER A WITH OGONEK AND ACUTE;0104 0301
LATIN SMALL LETTER A WITH OGONEK AND ACUTE;0105 0301
LATIN CAPITAL LETTER A WITH OGONEK AND TILDE;0104 0303
LATIN SMALL LETTER A WITH OGONEK AND TILDE;0105 0303
LATIN CAPITAL LETTER E WITH OGONEK AND ACUTE;0118 0301
LATIN SMALL LETTER E WITH OGONEK AND ACUTE;0119 0301
LATIN CAPITAL LETTER E WITH OGONEK AND TILDE;0118 0303
LATIN SMALL LETTER E WITH OGONEK AND TILDE;0119 0303
LATIN CAPITAL LETTER E WITH DOT ABOVE AND ACUTE;0116 0301
LATIN SMALL LETTER E WITH DOT ABOVE AND ACUTE;0117 0301
LATIN CAPITAL LETTER E WITH DOT ABOVE AND TILDE;0116 0303
LATIN SMALL LETTER E WITH DOT ABOVE AND TILDE;0117 0303
LATIN SMALL LETTER I WITH DOT ABOVE AND GRAVE;0069 0307 0300
LATIN SMALL LETTER I WITH DOT ABOVE AND TILDE;0069 0307 0303
LATIN CAPITAL LETTER I WITH OGONEK AND ACUTE;012E 0301
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND ACUTE;012F 0307 0301
LATIN CAPITAL LETTER I WITH OGONEK AND TILDE;012E 0303
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND TILDE;012F 0307 0303
LATIN CAPITAL LETTER J WITH TILDE;004A 0303
LATIN SMALL LETTER J WITH DOT ABOVE AND TILDE;006A 0307 0303
LATIN CAPITAL LETTER L WITH TILDE;004C 0303
LATIN SMALL LETTER L WITH TILDE;006C 0303
LATIN CAPITAL LETTER M WITH TILDE;004D 0303
LATIN SMALL LETTER M WITH TILDE;006D 0303
LATIN CAPITAL LETTER R WITH TILDE;0052 0303
LATIN SMALL LETTER R WITH TILDE;0072 0303
LATIN CAPITAL LETTER U WITH OGONEK AND ACUTE;0172 0301
LATIN SMALL LETTER U WITH OGONEK AND ACUTE;0173 0301
LATIN CAPITAL LETTER U WITH OGONEK AND TILDE;0172 0303
LATIN SMALL LETTER U WITH OGONEK AND TILDE;0173 0303
LATIN CAPITAL LETTER U WITH MACRON AND ACUTE;016A 0301
LATIN SMALL LETTER U WITH MACRON AND ACUTE;016B 0301
LATIN CAPITAL LETTER U WITH MACRON AND TILDE;016A 0303
LATIN SMALL LETTER U WITH MACRON AND TILDE;016B 0303
LATIN SMALL LETTER AE WITH GRAVE;00E6 0300
LATIN SMALL LETTER OPEN O WITH GRAVE;0254 0300
LATIN SMALL LETTER OPEN O WITH ACUTE;0254 0301
LATIN SMALL LETTER TURNED V WITH GRAVE;028C 0300
LATIN SMALL LETTER TURNED V WITH ACUTE;028C 0301
LATIN SMALL LETTER SCHWA WITH GRAVE;0259 0300
LATIN SMALL LETTER SCHWA WITH ACUTE;0259 0301
LATIN SMALL LETTER HOOKED SCHWA WITH GRAVE;025A 0300
LATIN SMALL LETTER HOOKED SCHWA WITH ACUTE;025A 0301
BENGALI LETTER KHINYA;0995 09CD 09B7
TAMIL CONSONANT K; 0B95 0BCD
TAMIL CONSONANT NG; 0B99 0BCD
TAMIL CONSONANT C; 0B9A 0BCD
TAMIL CONSONANT NY; 0B9E 0BCD
TAMIL CONSONANT TT; 0B9F 0BCD
TAMIL CONSONANT NN; 0BA3 0BCD
TAMIL CONSONANT T; 0BA4 0BCD
TAMIL CONSONANT N; 0BA8 0BCD
TAMIL CONSONANT P; 0BAA 0BCD
TAMIL CONSONANT M; 0BAE 0BCD
TAMIL CONSONANT Y; 0BAF 0BCD
TAMIL CONSONANT R; 0BB0 0BCD
TAMIL CONSONANT L; 0BB2 0BCD
TAMIL CONSONANT V; 0BB5 0BCD
TAMIL CONSONANT LLL;0BB4 0BCD
TAMIL CONSONANT LL; 0BB3 0BCD
TAMIL CONSONANT RR; 0BB1 0BCD
TAMIL CONSONANT NNN;0BA9 0BCD
TAMIL CONSONANT J; 0B9C 0BCD
TAMIL CONSONANT SH; 0BB6 0BCD
TAMIL CONSONANT SS; 0BB7 0BCD
TAMIL CONSONANT S; 0BB8 0BCD
TAMIL CONSONANT H; 0BB9 0BCD
TAMIL CONSONANT KSS;0B95 0BCD 0BB7 0BCD
TAMIL SYLLABLE KAA; 0B95 0BBE
TAMIL SYLLABLE KI; 0B95 0BBF
TAMIL SYLLABLE KII; 0B95 0BC0
TAMIL SYLLABLE KU; 0B95 0BC1
TAMIL SYLLABLE KUU; 0B95 0BC2
TAMIL SYLLABLE KE; 0B95 0BC6
TAMIL SYLLABLE KEE; 0B95 0BC7
TAMIL SYLLABLE KAI; 0B95 0BC8
TAMIL SYLLABLE KO; 0B95 0BCA
TAMIL SYLLABLE KOO; 0B95 0BCB
TAMIL SYLLABLE KAU; 0B95 0BCC
TAMIL SYLLABLE NGAA; 0B99 0BBE
TAMIL SYLLABLE NGI; 0B99 0BBF
TAMIL SYLLABLE NGII; 0B99 0BC0
TAMIL SYLLABLE NGU; 0B99 0BC1
TAMIL SYLLABLE NGUU; 0B99 0BC2
TAMIL SYLLABLE NGE; 0B99 0BC6
TAMIL SYLLABLE NGEE; 0B99 0BC7
TAMIL SYLLABLE NGAI; 0B99 0BC8
TAMIL SYLLABLE NGO; 0B99 0BCA
TAMIL SYLLABLE NGOO; 0B99 0BCB
TAMIL SYLLABLE NGAU; 0B99 0BCC
TAMIL SYLLABLE CI; 0B9A 0BBF
TAMIL SYLLABLE CII; 0B9A 0BC0
TAMIL SYLLABLE CU; 0B9A 0BC1
TAMIL SYLLABLE CUU; 0B9A 0BC2
TAMIL SYLLABLE CE; 0B9A 0BC6
TAMIL SYLLABLE CEE; 0B9A 0BC7
TAMIL SYLLABLE CAI; 0B9A 0BC8
TAMIL SYLLABLE CO; 0B9A 0BCA
TAMIL SYLLABLE COO; 0B9A 0BCB
TAMIL SYLLABLE CAU; 0B9A 0BCC
TAMIL SYLLABLE NYAA; 0B9E 0BBE
TAMIL SYLLABLE NYI; 0B9E 0BBF
TAMIL SYLLABLE NYII; 0B9E 0BC0
TAMIL SYLLABLE NYU; 0B9E 0BC1
TAMIL SYLLABLE NYUU; 0B9E 0BC2
TAMIL SYLLABLE NYE; 0B9E 0BC6
TAMIL SYLLABLE NYEE; 0B9E 0BC7
TAMIL SYLLABLE NYAI; 0B9E 0BC8
TAMIL SYLLABLE NYO; 0B9E 0BCA
TAMIL SYLLABLE NYOO; 0B9E 0BCB
TAMIL SYLLABLE NYAU; 0B9E 0BCC
TAMIL SYLLABLE TTAA; 0B9F 0BBE
TAMIL SYLLABLE TTI; 0B9F 0BBF
TAMIL SYLLABLE TTII; 0B9F 0BC0
TAMIL SYLLABLE TTU; 0B9F 0BC1
TAMIL SYLLABLE TTUU; 0B9F 0BC2
TAMIL SYLLABLE TTE; 0B9F 0BC6
TAMIL SYLLABLE TTEE; 0B9F 0BC7
TAMIL SYLLABLE TTAI; 0B9F 0BC8
TAMIL SYLLABLE TTO; 0B9F 0BCA
TAMIL SYLLABLE TTOO; 0B9F 0BCB
TAMIL SYLLABLE TTAU; 0B9F 0BCC
TAMIL SYLLABLE NNAA; 0BA3 0BBE
TAMIL SYLLABLE NNI; 0BA3 0BBF
TAMIL SYLLABLE NNII; 0BA3 0BC0
TAMIL SYLLABLE NNU; 0BA3 0BC1
TAMIL SYLLABLE NNUU; 0BA3 0BC2
TAMIL SYLLABLE NNE; 0BA3 0BC6
TAMIL SYLLABLE NNEE; 0BA3 0BC7
TAMIL SYLLABLE NNAI; 0BA3 0BC8
TAMIL SYLLABLE NNO; 0BA3 0BCA
TAMIL SYLLABLE NNOO; 0BA3 0BCB
TAMIL SYLLABLE NNAU; 0BA3 0BCC
TAMIL SYLLABLE TAA; 0BA4 0BBE
TAMIL SYLLABLE TI; 0BA4 0BBF
TAMIL SYLLABLE TII; 0BA4 0BC0
TAMIL SYLLABLE TU; 0BA4 0BC1
TAMIL SYLLABLE TUU; 0BA4 0BC2
TAMIL SYLLABLE TE; 0BA4 0BC6
TAMIL SYLLABLE TEE; 0BA4 0BC7
TAMIL SYLLABLE TAI; 0BA4 0BC8
TAMIL SYLLABLE TO; 0BA4 0BCA
TAMIL SYLLABLE TOO; 0BA4 0BCB
TAMIL SYLLABLE TAU; 0BA4 0BCC
TAMIL SYLLABLE NAA; 0BA8 0BBE
TAMIL SYLLABLE NI; 0BA8 0BBF
TAMIL SYLLABLE NII; 0BA8 0BC0
TAMIL SYLLABLE NU; 0BA8 0BC1
TAMIL SYLLABLE NUU; 0BA8 0BC2
TAMIL SYLLABLE NE; 0BA8 0BC6
TAMIL SYLLABLE NEE; 0BA8 0BC7
TAMIL SYLLABLE NAI; 0BA8 0BC8
TAMIL SYLLABLE NO; 0BA8 0BCA
TAMIL SYLLABLE NOO; 0BA8 0BCB
TAMIL SYLLABLE NAU; 0BA8 0BCC
TAMIL SYLLABLE PAA; 0BAA 0BBE
TAMIL SYLLABLE PI; 0BAA 0BBF
TAMIL SYLLABLE PII; 0BAA 0BC0
TAMIL SYLLABLE PU; 0BAA 0BC1
TAMIL SYLLABLE PUU; 0BAA 0BC2
TAMIL SYLLABLE PE; 0BAA 0BC6
TAMIL SYLLABLE PEE; 0BAA 0BC7
TAMIL SYLLABLE PAI; 0BAA 0BC8
TAMIL SYLLABLE PO; 0BAA 0BCA
TAMIL SYLLABLE POO; 0BAA 0BCB
TAMIL SYLLABLE PAU; 0BAA 0BCC
TAMIL SYLLABLE MAA; 0BAE 0BBE
TAMIL SYLLABLE MI; 0BAE 0BBF
TAMIL SYLLABLE MII; 0BAE 0BC0
TAMIL SYLLABLE MU; 0BAE 0BC1
TAMIL SYLLABLE MUU; 0BAE 0BC2
TAMIL SYLLABLE ME; 0BAE 0BC6
TAMIL SYLLABLE MEE; 0BAE 0BC7
TAMIL SYLLABLE MAI; 0BAE 0BC8
TAMIL SYLLABLE MO; 0BAE 0BCA
TAMIL SYLLABLE MOO; 0BAE 0BCB
TAMIL SYLLABLE MAU; 0BAE 0BCC
TAMIL SYLLABLE YAA; 0BAF 0BBE
TAMIL SYLLABLE YI; 0BAF 0BBF
TAMIL SYLLABLE YII; 0BAF 0BC0
TAMIL SYLLABLE YU; 0BAF 0BC1
TAMIL SYLLABLE YUU; 0BAF 0BC2
TAMIL SYLLABLE YE; 0BAF 0BC6
TAMIL SYLLABLE YEE; 0BAF 0BC7
TAMIL SYLLABLE YAI; 0BAF 0BC8
TAMIL SYLLABLE YO; 0BAF 0BCA
TAMIL SYLLABLE YOO; 0BAF 0BCB
TAMIL SYLLABLE YAU; 0BAF 0BCC
TAMIL SYLLABLE RAA; 0BB0 0BBE
TAMIL SYLLABLE RI; 0BB0 0BBF
TAMIL SYLLABLE RII; 0BB0 0BC0
TAMIL SYLLABLE RU; 0BB0 0BC1
TAMIL SYLLABLE RUU; 0BB0 0BC2
TAMIL SYLLABLE RE; 0BB0 0BC6
TAMIL SYLLABLE REE; 0BB0 0BC7
TAMIL SYLLABLE RAI; 0BB0 0BC8
TAMIL SYLLABLE RO; 0BB0 0BCA
TAMIL SYLLABLE ROO; 0BB0 0BCB
TAMIL SYLLABLE RAU; 0BB0 0BCC
TAMIL SYLLABLE LAA; 0BB2 0BBE
TAMIL SYLLABLE LI; 0BB2 0BBF
TAMIL SYLLABLE LII; 0BB2 0BC0
TAMIL SYLLABLE LU; 0BB2 0BC1
TAMIL SYLLABLE LUU; 0BB2 0BC2
TAMIL SYLLABLE LE; 0BB2 0BC6
TAMIL SYLLABLE LEE; 0BB2 0BC7
TAMIL SYLLABLE LAI; 0BB2 0BC8
TAMIL SYLLABLE LO; 0BB2 0BCA
TAMIL SYLLABLE LOO; 0BB2 0BCB
TAMIL SYLLABLE LAU; 0BB2 0BCC
TAMIL SYLLABLE VAA; 0BB5 0BBE
TAMIL SYLLABLE VI; 0BB5 0BBF
TAMIL SYLLABLE VII; 0BB5 0BC0
TAMIL SYLLABLE VU; 0BB5 0BC1
TAMIL SYLLABLE VUU; 0BB5 0BC2
TAMIL SYLLABLE VE; 0BB5 0BC6
TAMIL SYLLABLE VEE; 0BB5 0BC7
TAMIL SYLLABLE VAI; 0BB5 0BC8
TAMIL SYLLABLE VO; 0BB5 0BCA
TAMIL SYLLABLE VOO; 0BB5 0BCB
TAMIL SYLLABLE VAU; 0BB5 0BCC
TAMIL SYLLABLE LLLAA; 0BB4 0BBE
TAMIL SYLLABLE LLLI; 0BB4 0BBF
TAMIL SYLLABLE LLLII; 0BB4 0BC0
TAMIL SYLLABLE LLLU; 0BB4 0BC1
TAMIL SYLLABLE LLLUU; 0BB4 0BC2
TAMIL SYLLABLE LLLE; 0BB4 0BC6
TAMIL SYLLABLE LLLEE; 0BB4 0BC7
TAMIL SYLLABLE LLLAI; 0BB4 0BC8
TAMIL SYLLABLE LLLO; 0BB4 0BCA
TAMIL SYLLABLE LLLOO; 0BB4 0BCB
TAMIL SYLLABLE LLLAU; 0BB4 0BCC
TAMIL SYLLABLE LLAA; 0BB3 0BBE
TAMIL SYLLABLE LLI; 0BB3 0BBF
TAMIL SYLLABLE LLII; 0BB3 0BC0
TAMIL SYLLABLE LLU; 0BB3 0BC1
TAMIL SYLLABLE LLUU; 0BB3 0BC2
TAMIL SYLLABLE LLE; 0BB3 0BC6
TAMIL SYLLABLE LLEE; 0BB3 0BC7
TAMIL SYLLABLE LLAI; 0BB3 0BC8
TAMIL SYLLABLE LLO; 0BB3 0BCA
TAMIL SYLLABLE LLOO; 0BB3 0BCB
TAMIL SYLLABLE LLAU; 0BB3 0BCC
TAMIL SYLLABLE RRAA; 0BB1 0BBE
TAMIL SYLLABLE RRI; 0BB1 0BBF
TAMIL SYLLABLE RRII; 0BB1 0BC0
TAMIL SYLLABLE RRU; 0BB1 0BC1
TAMIL SYLLABLE RRUU; 0BB1 0BC2
TAMIL SYLLABLE RRE; 0BB1 0BC6
TAMIL SYLLABLE RREE; 0BB1 0BC7
TAMIL SYLLABLE RRAI; 0BB1 0BC8
TAMIL SYLLABLE RRO; 0BB1 0BCA
TAMIL SYLLABLE RROO; 0BB1 0BCB
TAMIL SYLLABLE RRAU; 0BB1 0BCC
TAMIL SYLLABLE NNNAA; 0BA9 0BBE
TAMIL SYLLABLE NNNI; 0BA9 0BBF
TAMIL SYLLABLE NNNII; 0BA9 0BC0
TAMIL SYLLABLE NNNU; 0BA9 0BC1
TAMIL SYLLABLE NNNUU; 0BA9 0BC2
TAMIL SYLLABLE NNNE; 0BA9 0BC6
TAMIL SYLLABLE NNNEE; 0BA9 0BC7
TAMIL SYLLABLE NNNAI; 0BA9 0BC8
TAMIL SYLLABLE NNNO; 0BA9 0BCA
TAMIL SYLLABLE NNNOO; 0BA9 0BCB
TAMIL SYLLABLE NNNAU; 0BA9 0BCC
TAMIL SYLLABLE JAA; 0B9C 0BBE
TAMIL SYLLABLE JI; 0B9C 0BBF
TAMIL SYLLABLE JII; 0B9C 0BC0
TAMIL SYLLABLE JU; 0B9C 0BC1
TAMIL SYLLABLE JUU; 0B9C 0BC2
TAMIL SYLLABLE JE; 0B9C 0BC6
TAMIL SYLLABLE JEE; 0B9C 0BC7
TAMIL SYLLABLE JAI; 0B9C 0BC8
TAMIL SYLLABLE JO; 0B9C 0BCA
TAMIL SYLLABLE JOO; 0B9C 0BCB
TAMIL SYLLABLE JAU; 0B9C 0BCC
TAMIL SYLLABLE SHAA; 0BB6 0BBE
TAMIL SYLLABLE SHI; 0BB6 0BBF
TAMIL SYLLABLE SHII; 0BB6 0BC0
TAMIL SYLLABLE SHU; 0BB6 0BC1
TAMIL SYLLABLE SHUU; 0BB6 0BC2
TAMIL SYLLABLE SHE; 0BB6 0BC6
TAMIL SYLLABLE SHEE; 0BB6 0BC7
TAMIL SYLLABLE SHAI; 0BB6 0BC8
TAMIL SYLLABLE SHO; 0BB6 0BCA
TAMIL SYLLABLE SHOO; 0BB6 0BCB
TAMIL SYLLABLE SHAU; 0BB6 0BCC
TAMIL SYLLABLE SSAA; 0BB7 0BBE
TAMIL SYLLABLE SSI; 0BB7 0BBF
TAMIL SYLLABLE SSII; 0BB7 0BC0
TAMIL SYLLABLE SSU; 0BB7 0BC1
TAMIL SYLLABLE SSUU; 0BB7 0BC2
TAMIL SYLLABLE SSE; 0BB7 0BC6
TAMIL SYLLABLE SSEE; 0BB7 0BC7
TAMIL SYLLABLE SSAI; 0BB7 0BC8
TAMIL SYLLABLE SSO; 0BB7 0BCA
TAMIL SYLLABLE SSOO; 0BB7 0BCB
TAMIL SYLLABLE SSAU; 0BB7 0BCC
TAMIL SYLLABLE SAA; 0BB8 0BBE
TAMIL SYLLABLE SI; 0BB8 0BBF
TAMIL SYLLABLE SII; 0BB8 0BC0
TAMIL SYLLABLE SU; 0BB8 0BC1
TAMIL SYLLABLE SUU; 0BB8 0BC2
TAMIL SYLLABLE SE; 0BB8 0BC6
TAMIL SYLLABLE SEE; 0BB8 0BC7
TAMIL SYLLABLE SAI; 0BB8 0BC8
TAMIL SYLLABLE SO; 0BB8 0BCA
TAMIL SYLLABLE SOO; 0BB8 0BCB
TAMIL SYLLABLE SAU; 0BB8 0BCC
TAMIL SYLLABLE HAA; 0BB9 0BBE
TAMIL SYLLABLE HI; 0BB9 0BBF
TAMIL SYLLABLE HII; 0BB9 0BC0
TAMIL SYLLABLE HU; 0BB9 0BC1
TAMIL SYLLABLE HUU; 0BB9 0BC2
TAMIL SYLLABLE HE; 0BB9 0BC6
TAMIL SYLLABLE HEE; 0BB9 0BC7
TAMIL SYLLABLE HAI; 0BB9 0BC8
TAMIL SYLLABLE HO; 0BB9 0BCA
TAMIL SYLLABLE HOO; 0BB9 0BCB
TAMIL SYLLABLE HAU; 0BB9 0BCC
TAMIL SYLLABLE KSSA; 0B95 0BCD 0BB7
TAMIL SYLLABLE KSSAA; 0B95 0BCD 0BB7 0BBE
TAMIL SYLLABLE KSSI; 0B95 0BCD 0BB7 0BBF
TAMIL SYLLABLE KSSII; 0B95 0BCD 0BB7 0BC0
TAMIL SYLLABLE KSSU; 0B95 0BCD 0BB7 0BC1
TAMIL SYLLABLE KSSUU; 0B95 0BCD 0BB7 0BC2
TAMIL SYLLABLE KSSE; 0B95 0BCD 0BB7 0BC6
TAMIL SYLLABLE KSSEE; 0B95 0BCD 0BB7 0BC7
TAMIL SYLLABLE KSSAI; 0B95 0BCD 0BB7 0BC8
TAMIL SYLLABLE KSSO; 0B95 0BCD 0BB7 0BCA
TAMIL SYLLABLE KSSOO; 0B95 0BCD 0BB7 0BCB
TAMIL SYLLABLE KSSAU; 0B95 0BCD 0BB7 0BCC
TAMIL SYLLABLE SHRII; 0BB6 0BCD 0BB0 0BC0
SINHALA CONSONANT SIGN YANSAYA;0DCA 200D 0DBA
SINHALA CONSONANT SIGN RAKAARAANSAYA;0DCA 200D 0DBB
SINHALA CONSONANT SIGN REPAYA;0DBB 0DCA 200D
GEORGIAN LETTER U-BRJGU;10E3 0302
KHMER CONSONANT SIGN COENG KA;17D2 1780
KHMER CONSONANT SIGN COENG KHA;17D2 1781
KHMER CONSONANT SIGN COENG KO;17D2 1782
KHMER CONSONANT SIGN COENG KHO;17D2 1783
KHMER CONSONANT SIGN COENG NGO;17D2 1784
KHMER CONSONANT SIGN COENG CA;17D2 1785
KHMER CONSONANT SIGN COENG CHA;17D2 1786
KHMER CONSONANT SIGN COENG CO;17D2 1787
KHMER CONSONANT SIGN COENG CHO;17D2 1788
KHMER CONSONANT SIGN COENG NYO;17D2 1789
KHMER CONSONANT SIGN COENG DA;17D2 178A
KHMER CONSONANT SIGN COENG TTHA;17D2 178B
KHMER CONSONANT SIGN COENG DO;17D2 178C
KHMER CONSONANT SIGN COENG TTHO;17D2 178D
KHMER CONSONANT SIGN COENG NA;17D2 178E
KHMER CONSONANT SIGN COENG TA;17D2 178F
KHMER CONSONANT SIGN COENG THA;17D2 1790
KHMER CONSONANT SIGN COENG TO;17D2 1791
KHMER CONSONANT SIGN COENG THO;17D2 1792
KHMER CONSONANT SIGN COENG NO;17D2 1793
KHMER CONSONANT SIGN COENG BA;17D2 1794
KHMER CONSONANT SIGN COENG PHA;17D2 1795
KHMER CONSONANT SIGN COENG PO;17D2 1796
KHMER CONSONANT SIGN COENG PHO;17D2 1797
KHMER CONSONANT SIGN COENG MO;17D2 1798
KHMER CONSONANT SIGN COENG YO;17D2 1799
KHMER CONSONANT SIGN COENG RO;17D2 179A
KHMER CONSONANT SIGN COENG LO;17D2 179B
KHMER CONSONANT SIGN COENG VO;17D2 179C
KHMER CONSONANT SIGN COENG SHA;17D2 179D
KHMER CONSONANT SIGN COENG SSA;17D2 179E
KHMER CONSONANT SIGN COENG SA;17D2 179F
KHMER CONSONANT SIGN COENG HA;17D2 17A0
KHMER CONSONANT SIGN COENG LA;17D2 17A1
KHMER VOWEL SIGN COENG QA;17D2 17A2
KHMER INDEPENDENT VOWEL SIGN COENG QU;17D2 17A7
KHMER INDEPENDENT VOWEL SIGN COENG RY;17D2 17AB
KHMER INDEPENDENT VOWEL SIGN COENG RYY;17D2 17AC
KHMER INDEPENDENT VOWEL SIGN COENG QE;17D2 17AF
KHMER VOWEL SIGN OM;17BB 17C6
KHMER VOWEL SIGN AAM;17B6 17C6
HIRAGANA LETTER BIDAKUON NGA;304B 309A
HIRAGANA LETTER BIDAKUON NGI;304D 309A
HIRAGANA LETTER BIDAKUON NGU;304F 309A
HIRAGANA LETTER BIDAKUON NGE;3051 309A
HIRAGANA LETTER BIDAKUON NGO;3053 309A
KATAKANA LETTER BIDAKUON NGA;30AB 309A
KATAKANA LETTER BIDAKUON NGI;30AD 309A
KATAKANA LETTER BIDAKUON NGU;30AF 309A
KATAKANA LETTER BIDAKUON NGE;30B1 309A
KATAKANA LETTER BIDAKUON NGO;30B3 309A
KATAKANA LETTER AINU CE;30BB 309A
KATAKANA LETTER AINU TU;30C4 309A
KATAKANA LETTER AINU TO;30C8 309A
KATAKANA LETTER AINU P;31F7 309A
MODIFIER LETTER EXTRA-HIGH EXTRA-LOW CONTOUR TONE BAR;02E5 02E9
MODIFIER LETTER EXTRA-LOW EXTRA-HIGH CONTOUR TONE BAR;02E9 02E5
-221
View File
@@ -1,221 +0,0 @@
# This file has its own compilation step because
# it needs to parse String.Unicode data and
# compile a digested module.
defmodule String.Unicode do
@moduledoc false
def version, do: {6,2,0}
to_binary = fn
"" ->
nil
codepoints ->
codepoints = :binary.split(codepoints, " ", [:global])
Enum.reduce codepoints, "", fn(codepoint, acc) ->
acc <> << binary_to_integer(codepoint, 16) :: utf8 >>
end
end
data_path = Path.join(__DIR__, "UnicodeData.txt")
{ codes, whitespace } = Enum.reduce File.stream!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
[ codepoint, _name, _category,
_class, bidi, _decomposition,
_numeric_1, _numeric_2, _numeric_3,
_bidi_mirror, _unicode_1, _iso,
upper, lower, title ] = :binary.split(line, ";", [:global])
title = :binary.part(title, 0, size(title) - 1)
cond do
upper != "" or lower != "" or title != "" ->
{ [{ to_binary.(codepoint), to_binary.(upper), to_binary.(lower), to_binary.(title) } | cacc], wacc }
bidi in ["B", "S", "WS"] ->
{ cacc, [to_binary.(codepoint) | wacc] }
true ->
{ cacc, wacc }
end
end
special_path = Path.join(__DIR__, "SpecialCasing.txt")
codes = Enum.reduce File.stream!(special_path), codes, fn(line, acc) ->
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
key = to_binary.(codepoint)
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
end
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
seqs = Enum.map File.stream!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
codepoints = :binary.split(codepoints, " ", [:global])
codepoints = Enum.map codepoints, &Regex.replace(%r/\s+/, &1, "")
codepoints = Enum.filter codepoints, fn(x) -> size(x) > 0 end
Enum.map codepoints, to_binary
end
# Downcase
def downcase(string), do: do_downcase(string) |> iolist_to_binary
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
defp do_downcase(unquote(codepoint) <> rest) do
unquote(:binary.bin_to_list(lower)) ++ downcase(rest)
end
end
defp do_downcase(<< char, rest :: binary >>) do
[char|do_downcase(rest)]
end
defp do_downcase(""), do: []
# Upcase
def upcase(string), do: do_upcase(string) |> iolist_to_binary
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
defp do_upcase(unquote(codepoint) <> rest) do
unquote(:binary.bin_to_list(upper)) ++ do_upcase(rest)
end
end
defp do_upcase(<< char, rest :: binary >>) do
[char|do_upcase(rest)]
end
defp do_upcase(""), do: []
# Titlecase once
def titlecase_once(""), do: { "", "" }
lc { codepoint, _upper, _lower, title } inlist codes, title && title != codepoint do
def titlecase_once(unquote(codepoint) <> rest) do
{ unquote(title), rest }
end
end
def titlecase_once(<< char, rest :: binary >>) do
{ << char >>, rest }
end
# Strip
def lstrip(""), do: ""
lc codepoint inlist whitespace do
def lstrip(unquote(codepoint) <> rest) do
lstrip(rest)
end
end
def lstrip(other) when is_binary(other), do: other
def rstrip(string) when is_binary(string) do
do_rstrip(string, [], [])
end
lc codepoint inlist whitespace do
c = :binary.bin_to_list(codepoint) |> :lists.reverse
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
end
end
defp do_rstrip(<< char, rest :: binary >>, nil, acc2) do
do_rstrip(rest, nil, [char|acc2])
end
defp do_rstrip(<< char, rest :: binary >>, acc1, _acc2) do
do_rstrip(rest, nil, [char|acc1])
end
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> iolist_to_binary
# Split
def split(""), do: [""]
def split(string) when is_binary(string) do
:lists.reverse do_split(string, "", [])
end
lc codepoint inlist whitespace do
defp do_split(unquote(codepoint) <> rest, buffer, acc) do
do_split(rest, "", add_buffer_to_acc(buffer, acc))
end
end
defp do_split(<< char, rest :: binary >>, buffer, acc) do
do_split(rest, << buffer :: binary, char >>, acc)
end
defp do_split(<<>>, buffer, acc) do
add_buffer_to_acc(buffer, acc)
end
@compile { :inline, add_buffer_to_acc: 2 }
defp add_buffer_to_acc("", acc), do: acc
defp add_buffer_to_acc(buffer, acc), do: [buffer|acc]
# Graphemes
lc codepoints inlist seqs do
def next_grapheme(<< unquote_splicing(codepoints), t :: binary >>) do
{ << unquote_splicing(codepoints) >>, t }
end
end
def next_grapheme(<<>>) do
:no_grapheme
end
def next_grapheme(binary) when is_binary(binary) do
case next_codepoint(binary) do
:no_codepoint -> :no_grapheme
other -> other
end
end
def graphemes(binary) when is_binary(binary) do
do_graphemes(next_grapheme(binary))
end
defp do_graphemes({ c, rest }) do
[c|do_graphemes(next_grapheme(rest))]
end
defp do_graphemes(:no_grapheme) do
[]
end
# Codepoints
def next_codepoint(<< cp :: utf8, rest :: binary >>) do
{ <<cp :: utf8>>, rest }
end
def next_codepoint(<< cp, rest :: binary >>) do
{ <<cp>>, rest }
end
def next_codepoint(<<>>) do
:no_codepoint
end
def codepoints(binary) when is_binary(binary) do
do_codepoints(next_codepoint(binary))
end
defp do_codepoints({ c, rest }) do
[c|do_codepoints(next_codepoint(rest))]
end
defp do_codepoints(:no_codepoint) do
[]
end
end
+1 -1
View File
@@ -24,4 +24,4 @@
{verbose, false}
]}.
{require_otp_vsn,"(R16B).*"}.
{require_otp_vsn,"(R16B|R17A).*"}.
+89 -55
View File
@@ -1,10 +1,11 @@
%% Main entry point for Elixir functions. All of those functions are
%% private to the Elixir compiler and reserved to be used by Elixir only.
-module(elixir).
-behaviour(application).
-export([main/1, start_cli/0,
scope_for_eval/1, scope_for_eval/2,
eval/2, eval/3, eval/4,
eval_quoted/2, eval_quoted/3, eval_quoted/4,
eval_forms/3, translate_forms/3]).
string_to_quoted/4, 'string_to_quoted!'/4,
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
-include("elixir.hrl").
%% Top level types
@@ -12,7 +13,7 @@
-type char_list() :: string().
-type as_boolean(T) :: T.
% OTP APPLICATION API
%% OTP Application API
-export([start/2, stop/1, config_change/3]).
@@ -23,7 +24,7 @@ start(_Type, _Args) ->
io:setopts(standard_io, [binary,{encoding,unicode}]),
io:setopts(standard_error, [binary,{encoding,unicode}])
end),
elixir_sup:start_link([]).
elixir_sup:start_link().
stop(_S) ->
ok.
@@ -45,96 +46,129 @@ start_cli() ->
%% EVAL HOOKS
scope_for_eval(Opts) ->
scope_for_eval(#elixir_scope{
file = <<"nofile">>,
env_for_eval(Opts) ->
env_for_eval(#elixir_env{
local = nil,
aliases = [],
requires = elixir_dispatch:default_requires(),
functions = elixir_dispatch:default_functions(),
macros = elixir_dispatch:default_macros()
}, Opts).
scope_for_eval(Scope, Opts) ->
env_for_eval(Env, Opts) ->
Line = case lists:keyfind(line, 1, Opts) of
{ line, RawLine } when is_integer(RawLine) -> RawLine;
false -> Env#elixir_env.line
end,
File = case lists:keyfind(file, 1, Opts) of
{ file, RawFile } when is_binary(RawFile) -> RawFile;
false -> Scope#elixir_scope.file
false -> Env#elixir_env.file
end,
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, LocalOpt } -> LocalOpt;
false -> Scope#elixir_scope.local
false -> Env#elixir_env.local
end,
Aliases = case lists:keyfind(aliases, 1, Opts) of
{ aliases, AliasesOpt } -> AliasesOpt;
false -> Scope#elixir_scope.aliases
false -> Env#elixir_env.aliases
end,
Requires = case lists:keyfind(requires, 1, Opts) of
{ requires, List } -> ordsets:from_list(List);
false -> Scope#elixir_scope.requires
false -> Env#elixir_env.requires
end,
Functions = case lists:keyfind(functions, 1, Opts) of
{ functions, FunctionsOpt } -> FunctionsOpt;
false -> Scope#elixir_scope.functions
false -> Env#elixir_env.functions
end,
Macros = case lists:keyfind(macros, 1, Opts) of
{ macros, MacrosOpt } -> MacrosOpt;
false -> Scope#elixir_scope.macros
false -> Env#elixir_env.macros
end,
Scope#elixir_scope{
file=File, local=Local,
Module = case lists:keyfind(module, 1, Opts) of
{ module, ModuleOpt } when is_atom(ModuleOpt) -> ModuleOpt;
false -> nil
end,
Env#elixir_env{
file=File, local=Local, module=Module,
macros=Macros, functions=Functions,
requires=Requires, aliases=Aliases }.
requires=Requires, aliases=Aliases, line=Line
}.
%% String evaluation
eval(String, Binding) -> eval(String, Binding, []).
eval(String, Binding) ->
eval(String, Binding, []).
eval(String, Binding, Opts) ->
case lists:keyfind(line, 1, Opts) of
false -> Line = 1;
{ line, Line } -> []
end,
eval(String, Binding, Line, scope_for_eval(Opts)).
eval(String, Binding, Line, #elixir_scope{file=File} = S) when
eval(String, Binding, Opts) when is_list(Opts) ->
eval(String, Binding, env_for_eval(Opts));
eval(String, Binding, #elixir_env{line=Line,file=File} = E) 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).
Forms = 'string_to_quoted!'(String, Line, File, []),
eval_forms(Forms, Binding, E).
%% Quoted evaluation
eval_quoted(Tree, Binding) -> eval_quoted(Tree, Binding, []).
eval_quoted(Tree, Binding, Opts) when is_list(Opts) ->
eval_quoted(Tree, Binding, env_for_eval(Opts));
eval_quoted(Tree, Binding, #elixir_env{line=Line} = E) ->
eval_forms(elixir_quote:linify(Line, Tree), Binding, E).
eval_quoted(Tree, Binding, Opts) ->
case lists:keyfind(line, 1, Opts) of
{ line, Line } -> [];
false -> Line = 1
end,
eval_quoted(Tree, Binding, Line, scope_for_eval(Opts)).
%% Handle forms evaluation. The main difference to
%% to eval_quoted is that it does not linefy the given
%% args.
eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
eval_forms(elixir_quote:linify(Line, Tree), Binding, S).
eval_forms(Tree, Binding, #elixir_env{} = E) ->
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E));
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
eval_forms(Tree, Binding, env_for_eval(Opts)).
eval_forms(Tree, Binding, Env, Scope) ->
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope),
ParsedEnv = Env#elixir_env{vars=[K || {K,_} <- ParsedScope#elixir_scope.vars]},
{ Erl, NewEnv, NewScope } = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
%% Handle forms evaluation internally, it is an
%% internal API not meant for external usage.
translate_forms(Tree, Binding, Opts) when is_list(Opts) ->
translate_forms(Tree, Binding, scope_for_eval(Opts));
translate_forms(Tree, Binding, #elixir_scope{} = Scope) ->
elixir_translator:translate(Tree, elixir_scope:vars_from_binding(Scope, Binding)).
eval_forms(Tree, Binding, Scope) ->
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
case ParseTree of
[] -> { nil, Binding, NewScope };
case Erl of
{ atom, _, Atom } ->
{ Atom, Binding, NewEnv, NewScope };
_ ->
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, elixir_scope:binding_for_eval(Binding, nil)),
{Value, elixir_scope:binding_from_vars(NewScope, NewBinding), NewScope }
{ value, Value, NewBinding } = erl_eval:expr(Erl, ParsedBinding),
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope }
end.
%% Converts a quoted expression to erlang abstract format
quoted_to_erl(Quoted, Env) ->
quoted_to_erl(Quoted, Env, elixir_env:env_to_scope(Env)).
quoted_to_erl(Quoted, Env, Scope) ->
{ Expanded, NewEnv } = elixir_exp:expand(Quoted, Env),
{ Erl, NewScope } = elixir_translator:translate(Expanded, Scope),
{ Erl, NewEnv, NewScope }.
%% Converts a given string (char list) into quote expression
string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) ->
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
{ ok, _Line, Tokens } ->
try elixir_parser:parse(Tokens) of
{ ok, Forms } -> { ok, Forms };
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
catch
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
end;
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
end.
'string_to_quoted!'(String, StartLine, File, Opts) ->
case string_to_quoted(String, StartLine, File, Opts) of
{ ok, Forms } ->
Forms;
{ error, { Line, Error, Token } } ->
elixir_errors:parse_error(Line, File, Error, Token)
end.
+86 -107
View File
@@ -1,85 +1,90 @@
-module(elixir_aliases).
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
-export([inspect/1, last/1, concat/1, safe_concat/1, format_error/1,
ensure_loaded/3, expand/4, store/7]).
-include("elixir.hrl").
%% Store an alias in the given scope
store(_Meta, New, New, S) -> S;
store(Meta, New, Old, S) ->
SA = S#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
},
case lists:keymember(context, 1, Meta) of
true ->
SA#elixir_scope{
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
};
false ->
SA
inspect(Atom) when is_atom(Atom) ->
case elixir_compiler:get_opt(internal) of
true -> atom_to_binary(Atom, utf8);
false -> 'Elixir.Inspect.Atom':inspect(Atom)
end.
%% Store an alias in the given scope
store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
{ Aliases, MacroAliases };
store(Meta, New, Old, TKV, Aliases, MacroAliases, Lexical) ->
record_warn(Meta, New, TKV, Lexical),
{ store_alias(New, Old, Aliases),
store_macro_alias(Meta, New, Old, MacroAliases) }.
store_alias(New, Old, Aliases) ->
lists:keystore(New, 1, Aliases, { New, Old }).
store_macro_alias(Meta, New, Old, Aliases) ->
case lists:keymember(context, 1, Meta) andalso
lists:keyfind(counter, 1, Meta) of
{ counter, Counter } when is_integer(Counter) ->
lists:keystore(New, 1, Aliases, { New, { Counter, Old } });
_ ->
Aliases
end.
record_warn(Meta, Ref, Opts, Lexical) ->
Warn =
case lists:keyfind(warn, 1, Opts) of
{ warn, false } -> false;
{ warn, true } -> true;
false -> not lists:keymember(context, 1, Meta)
end,
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, Lexical).
%% Expand an alias. It returns an atom (meaning that there
%% was an expansion) or a list of atoms.
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases) ->
expand({ '__aliases__', _Meta, ['Elixir'|_] = List }, _Aliases, _MacroAliases, _LexicalTracker) ->
concat(List);
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
case lists:keyfind(alias, 1, Meta) of
{ alias, false } ->
expand(Alias, MacroAliases);
expand(Alias, MacroAliases, LexicalTracker);
{ alias, Atom } when is_atom(Atom) ->
case expand(Alias, MacroAliases) of
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
OtherAliases when is_list(OtherAliases) -> Atom
end;
Atom;
false ->
expand(Alias, Aliases)
expand(Alias, Aliases, LexicalTracker)
end.
expand({ '__aliases__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
case expand_one(H, Aliases) of
false -> [H];
Atom -> Atom
end;
expand({ '__aliases__', _Meta, [H|T] }, Aliases) when is_atom(H) ->
case H of
'Elixir' ->
concat(T);
_ ->
case expand_one(H, Aliases) of
false -> [H|T];
Atom -> concat([Atom|T])
expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
Counter = case lists:keyfind(counter, 1, Meta) of
{ counter, C } -> C;
_ -> nil
end,
case lookup(Lookup, Aliases, Counter) of
Lookup -> [H|T];
Atom ->
elixir_lexical:record_alias(Lookup, LexicalTracker),
case T of
[] -> Atom;
_ -> concat([Atom|T])
end
end;
expand({ '__aliases__', _Meta, List }, _Aliases) ->
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
List.
expand_one(H, Aliases) ->
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
case lookup(Lookup, Aliases) of
Lookup -> false;
Else -> Else
end.
%% Ensure a module is loaded before its usage.
ensure_loaded(Line, Ref, S) ->
ensure_loaded(Line, S#elixir_scope.file, Ref, S#elixir_scope.context_modules).
ensure_loaded(_Line, _File, 'Elixir.Kernel', _FileModules) ->
ok;
ensure_loaded(Line, File, Ref, FileModules) ->
ensure_loaded(_Meta, 'Elixir.Kernel', _E) -> ok;
ensure_loaded(Meta, Ref, E) ->
try
Ref:module_info(compile)
catch
error:undef ->
Kind = case lists:member(Ref, FileModules) of
Kind = case lists:member(Ref, E#elixir_env.context_modules) of
true -> scheduled_module;
false -> unloaded_module
end,
elixir_errors:form_error(Line, File, ?MODULE, { Kind, Ref })
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, { Kind, Ref })
end.
%% Receives an atom and returns the last bit as an alias.
@@ -92,74 +97,48 @@ last([$.|_], Acc) -> Acc;
last([H|T], Acc) -> last(T, [H|Acc]);
last([], Acc) -> Acc.
%% Gets two modules names and return an alias
%% which can be passed down to the alias directive
%% and it will create a proper shortcut representing
%% the given nesting.
%%
%% Examples:
%%
%% nesting_alias('Elixir.Foo.Bar', 'Elixir.Foo.Bar.Baz.Bat')
%% { 'Elixir.Baz', 'Elixir.Foo.Bar.Baz' }
%%
%% When passed to alias, the example above will generate an
%% alias like:
%%
%% 'Elixir.Baz' => 'Elixir.Foo.Bar.Baz'
%%
nesting_alias(nil, _Full) -> false;
nesting_alias(Prefix, Full) ->
PrefixList = list_nesting(Prefix),
FullList = list_nesting(Full),
(PrefixList /= []) andalso do_nesting(PrefixList, FullList, []).
do_nesting([X|PreTail], [X|Tail], Acc) ->
do_nesting(PreTail, Tail, [X|Acc]);
do_nesting([], [H|_], Acc) ->
{ list_to_atom("Elixir." ++ H), concat(lists:reverse([H|Acc])) };
do_nesting(_, _, _Acc) ->
false.
list_nesting(Atom) ->
case string:tokens(atom_to_list(Atom), ".") of
["Elixir"|T] -> T;
_ -> []
end.
%% Receives a list of atoms, binaries or lists
%% representing modules and concatenates them.
concat(Args) -> list_to_atom(raw_concat(Args)).
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
concat(Args) -> binary_to_atom(do_concat(Args), utf8).
safe_concat(Args) -> binary_to_existing_atom(do_concat(Args), utf8).
raw_concat(['Elixir'|Args]) -> do_concat(Args);
raw_concat([nil|Args]) -> do_concat(Args);
raw_concat(Args) -> do_concat(Args).
do_concat([H|T]) when is_atom(H), H /= nil ->
do_concat([atom_to_binary(H, utf8)|T]);
do_concat([<<"Elixir.", _/binary>>=H|T]) ->
do_concat(T, H);
do_concat([<<"Elixir">>=H|T]) ->
do_concat(T, H);
do_concat(T) ->
do_concat(T, <<"Elixir">>).
do_concat(Args) ->
Aliases = [to_partial(Arg) || Arg <- Args],
"Elixir" ++ lists:concat(Aliases).
do_concat([nil|T], Acc) ->
do_concat(T, Acc);
do_concat([H|T], Acc) when is_atom(H) ->
do_concat(T, <<Acc/binary, $., (to_partial(atom_to_binary(H, utf8)))/binary>>);
do_concat([H|T], Acc) when is_binary(H) ->
do_concat(T, <<Acc/binary, $., (to_partial(H))/binary>>);
do_concat([], Acc) ->
Acc.
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) -> [$.|Arg];
to_partial([$.|_] = Arg) -> Arg;
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
to_partial(<<"Elixir.", Arg/binary>>) -> Arg;
to_partial(<<".", Arg/binary>>) -> Arg;
to_partial(Arg) when is_binary(Arg) -> Arg.
%% Lookup an alias in the current scope.
lookup(Else, Dict) ->
case orddict:find(Else, Dict) of
{ ok, Value } when Value /= Else -> lookup(Value, Dict);
lookup(Else, Dict, Counter) ->
case lists:keyfind(Else, 1, Dict) of
{ Else, { Counter, Value } } -> lookup(Value, Dict, Counter);
{ Else, Value } when is_atom(Value) -> lookup(Value, Dict, Counter);
_ -> Else
end.
%% Errors
format_error({unloaded_module, Module}) ->
io_lib:format("module ~ts is not loaded and could not be found", [elixir_errors:inspect(Module)]);
io_lib:format("module ~ts is not loaded and could not be found", [elixir_aliases:inspect(Module)]);
format_error({scheduled_module, Module}) ->
io_lib:format("module ~ts 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)]).
[inspect(Module)]).
+182
View File
@@ -0,0 +1,182 @@
%% Handle translation of Elixir literals to Erlang AST.
-module(elixir_bitstring).
-export([translate/3, expand/3]).
-include("elixir.hrl").
%% Expansion
expand(Meta, Args, E) ->
case E#elixir_env.context of
match ->
{ EArgs, EA } = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
{ { '<<>>', Meta, EArgs }, EA };
_ ->
{ EArgs, { EC, EV } } = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], { E, E }),
{ { '<<>>', Meta, EArgs }, elixir_env:mergea(EV, EC) }
end.
expand_bitstr(_Fun, [], Acc, E) ->
{ lists:reverse(Acc), E };
expand_bitstr(Fun, [{'::',Meta,[Left,Right]}|T], Acc, E) ->
{ ELeft, EL } = Fun(Left, E),
%% Variables defined outside the binary can be accounted
%% on subparts, however we can't assign new variables.
case E of
{ ER, _ } -> ok; %% expand_arg, no assigns
_ -> ER = E#elixir_env{context=nil} %% expand_each, revert assigns
end,
ERight = expand_bit_info(Meta, Right, ER),
expand_bitstr(Fun, T, [{'::',Meta,[ELeft,ERight]}|Acc], EL);
expand_bitstr(Fun, [H|T], Acc, E) ->
{ Expr, ES } = Fun(H, E),
expand_bitstr(Fun, T, [Expr|Acc], ES).
%% Expand bit info
expand_bit_info(Meta, Info, E) when is_list(Info) ->
expand_bit_info(Meta, Info, default, [], E);
expand_bit_info(Meta, Info, E) ->
expand_bit_info(Meta, [Info], E).
expand_bit_info(Meta, [{ Expr, ExprMeta, Args }|T], Size, Types, E) when is_atom(Expr) ->
EArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
case expand_bit_type_or_size(Expr, EArgs) of
type ->
expand_bit_info(Meta, T, Size, [{ Expr, [], EArgs }|Types], E);
size ->
case Size of
default -> ok;
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file, "duplicated size definition in bitstring")
end,
expand_bit_info(Meta, T, { Expr, [], EArgs }, Types, E);
none ->
handle_unknown_bit_info(Meta, { Expr, ExprMeta, EArgs }, T, Size, Types, E)
end;
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
expand_bit_info(Meta, [{ size, [], [Int] }|T], Size, Types, E);
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
elixir_errors:compile_error(Meta, E#elixir_env.file,
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
expand_bit_info(_Meta, [], Size, Types, _) ->
case Size of
default -> lists:reverse(Types);
_ -> [Size|lists:reverse(Types)]
end.
expand_bit_type_or_size(binary, []) -> type;
expand_bit_type_or_size(integer, []) -> type;
expand_bit_type_or_size(float, []) -> type;
expand_bit_type_or_size(bitstring, []) -> type;
expand_bit_type_or_size(bytes, []) -> type;
expand_bit_type_or_size(bits, []) -> type;
expand_bit_type_or_size(utf8, []) -> type;
expand_bit_type_or_size(utf16, []) -> type;
expand_bit_type_or_size(utf32, []) -> type;
expand_bit_type_or_size(signed, []) -> type;
expand_bit_type_or_size(unsigned, []) -> type;
expand_bit_type_or_size(big, []) -> type;
expand_bit_type_or_size(little, []) -> type;
expand_bit_type_or_size(native, []) -> type;
expand_bit_type_or_size(unit, [_]) -> type;
expand_bit_type_or_size(size, [_]) -> size;
expand_bit_type_or_size(_, _) -> none.
handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:env_to_ex({ ?line(ExprMeta), E })) of
Expr ->
elixir_errors:compile_error(ExprMeta, E#elixir_env.file,
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
Other ->
List = case is_list(Other) of true -> Other; false -> [Other] end,
expand_bit_info(Meta, List ++ T, Size, Types, E)
end.
%% Translation
translate(Meta, Args, S) ->
case S#elixir_scope.context of
match ->
build_bitstr(fun elixir_translator:translate/2, Args, Meta, S);
_ ->
build_bitstr(fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end, Args, Meta, S)
end.
build_bitstr(Fun, Exprs, Meta, S) ->
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{ Acc, S };
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
{ Size, Types } = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
AllowString = types_allow_string(Types),
AllowSplice = types_allow_splice(Types),
AllowAny = (AllowString orelse AllowSplice) andalso (Size == default),
case AllowAny andalso Expr of
{ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] } when AllowString ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), { string, 0, String }, Size, Types }|Acc]);
{ bin, _, Elements } when AllowSplice ->
build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
{ cons, _, _, _ } = Cons ->
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
{ nil, _ } ->
build_bitstr_each(Fun, T, Meta, NS, Acc);
_ ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
end.
rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
types_allow_string([End|T]) when End == little; End == big -> types_allow_string(T);
types_allow_string([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_allow_string(T);
types_allow_string([]) -> true;
types_allow_string(_) -> false.
types_allow_splice(default) -> true;
types_allow_splice([bytes]) -> true;
types_allow_splice([binary]) -> true;
types_allow_splice([bits]) -> true;
types_allow_splice([bitstring]) -> true;
types_allow_splice(_) -> false.
%% Extra bitstring specifiers
extract_bit_info(Meta, [{ size, _, [Arg] }|T], S) ->
case elixir_translator:translate(Arg, S) of
{ { Kind, _, _ } = Size, _ } when Kind == integer; Kind == var ->
{ Size, extract_bit_type(Meta, T, S) };
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
end;
extract_bit_info(Meta, T, S) ->
{ default, extract_bit_type(Meta, T, S) }.
extract_bit_type(Meta, [{ unit, _, [Arg] }|T], S) when is_integer(Arg) ->
[{ unit, Arg }|extract_bit_type(Meta, T, S)];
extract_bit_type(Meta, [{ unit, _, [Arg] }|_], S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
extract_bit_type(Meta, [{ Other, _, [] }|T], S) ->
[Other|extract_bit_type(Meta, T, S)];
extract_bit_type(_Meta, [], _S) ->
[].
+51
View File
@@ -0,0 +1,51 @@
%% An Erlang module that behaves like an Elixir module
%% used for bootstraping.
-module(elixir_bootstrap).
-export(['MACRO-def'/2, 'MACRO-def'/3, 'MACRO-defp'/3, 'MACRO-defmodule'/3,
'MACRO-defmacro'/2, 'MACRO-defmacro'/3, 'MACRO-defmacrop'/3,
'MACRO-@'/2, '__info__'/1]).
-define(kernel, 'Elixir.Kernel').
%% Mock out @ to be a no-op unless Kernel is defined.
'MACRO-@'(Caller, Tree) ->
unless_loaded('MACRO-@', [Caller, Tree], fun() -> nil end).
'MACRO-def'(Caller, Call) -> 'MACRO-def'(Caller, Call, nil).
'MACRO-def'(Caller, Call, Expr) -> define(Caller, def, Call, Expr).
'MACRO-defp'(Caller, Call, Expr) -> define(Caller, defp, Call, Expr).
'MACRO-defmacro'(Caller, Call) -> 'MACRO-defmacro'(Caller, Call, nil).
'MACRO-defmacro'(Caller, Call, Expr) -> define(Caller, defmacro, Call, Expr).
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
'MACRO-defmodule'(_Caller, Alias, [{do,Block}]) ->
{ Escaped, _ } = elixir_quote:escape(Block, false),
Args = [Alias, Escaped, [], env()],
{ { '.', [], [elixir_module, compile] }, [], Args }.
'__info__'(functions) ->
[];
'__info__'(macros) ->
[{ '@', 1 },
{def,1},
{def,2},
{defmacro,1},
{defmacro,2},
{defmacrop,2},
{defmodule,2},
{defp,2}].
define({Line,E}, Kind, Call, Expr) ->
{ EscapedCall, UC } = elixir_quote:escape(Call, true),
{ EscapedExpr, UE } = elixir_quote:escape(Expr, true),
Args = [Line, Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E)],
{ { '.', [], [elixir_def, store_definition] }, [], Args }.
unless_loaded(Fun, Args, Callback) ->
case code:is_loaded(?kernel) of
{ _, _} -> apply(?kernel, Fun, Args);
false -> Callback()
end.
env() ->
{ '__ENV__', [], nil }.
+82 -112
View File
@@ -1,134 +1,113 @@
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
-export([match/3, clause/6, clauses/3, get_pairs/2, get_pairs/3,
extract_splat_guards/1, extract_guards/1]).
-include("elixir.hrl").
%% Get pairs from a clause.
get_pairs(Meta, Key, Clauses, S) ->
get_pairs(Meta, Key, Clauses, S, false).
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
get_pairs(Key, Clauses) ->
get_pairs(Key, Clauses, false).
get_pairs(Key, Clauses, AllowNil) ->
case lists:keyfind(Key, 1, Clauses) of
{ Key, { '->', _, Pairs } } ->
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
{ Key, Pairs } when is_list(Pairs) ->
[{ Key, Meta, Left, Right } || { '->', Meta, [Left, Right] } <- Pairs];
{ Key, nil } when AllowNil ->
[];
{ Key, _ } ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
_ ->
false ->
[]
end.
% Function for translating assigns.
%% Translate matches
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
temp_vars=ordsets:new(), backup_vars=Vars}),
{ Result, NewS } = match(Fun, Args, S#elixir_scope{context=match,
match_vars=ordsets:new(), backup_vars=Vars}),
{ Result, NewS#elixir_scope{context=Context,
temp_vars=TempVars, backup_vars=BackupVars} };
match_vars=MatchVars, backup_vars=BackupVars} };
match(Fun, Args, S) -> Fun(Args, S).
assigns(Fun, Args, S) -> Fun(Args, S).
%% Translate clauses with args, guards and expressions
%% Function for translating a block that is preceeded by an
%% assignment and optional guards. This is used by def* and fn.
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
{ TArgs, SA } = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
{ TExpr, SE } = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
assigns_block(Line, Fun, BareArgs, Exprs, S) ->
{ Args, Guards } = extract_guards(BareArgs),
assigns_block(Line, Fun, Args, Exprs, Guards, S).
SG = SA#elixir_scope{context=guard, extra_guards=nil},
Extra = SA#elixir_scope.extra_guards,
assigns_block(Line, Fun, Args, Exprs, Guards, S) when is_integer(Line) ->
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
FArgs = listify(TArgs),
SG = SA#elixir_scope{context=guard, extra_guards=nil},
Extra = SA#elixir_scope.extra_guards,
FGuards = case Guards of
TGuards = 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
[{ block, _, FExprs }] -> [];
_ -> FExprs = TExprs
end,
{ { clause, Line, FArgs, FGuards, FExprs }, SE }.
{ { clause, Line, TArgs, TGuards, unblock(TExpr) }, SE }.
% Translate/Extract guards from the given expression.
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))|Extra].
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right, []) };
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_guards(Right) };
extract_guards(Else) -> { Else, [] }.
extract_or_clauses({ 'when', _, [Left, Right] }, Acc) -> extract_or_clauses(Right, [Left|Acc]);
extract_or_clauses(Term, Acc) -> [Term|Acc].
% Extract name and args from the given expression.
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args(_) -> error.
extract_or_guards({ 'when', _, [Left, Right] }) -> [Left|extract_or_guards(Right)];
extract_or_guards(Term) -> [Term].
% Extract guards when multiple left side args are allowed.
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
{ Left, Right } = elixir_utils:split_last(Args),
{ Left, extract_or_clauses(Right, []) };
{ Left, extract_or_guards(Right) };
extract_splat_guards(Else) ->
{ Else, [] }.
% Function for translating macros with match style like case and receive.
match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
{ TC, TS } = do_match(Meta, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
C2 = TS#elixir_scope.clause_vars,
{ TC, TS#elixir_scope{clause_vars=elixir_scope:merge_clause_vars(C1, C2)} }.
clauses(Meta, Clauses, #elixir_scope{clause_vars=CV, temp_vars=TV} = S) ->
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{clause_vars=[], temp_vars=[]}),
{ TC, TS#elixir_scope{
clause_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.clause_vars),
temp_vars=elixir_scope:merge_opt_vars(TV, TS#elixir_scope.temp_vars)} }.
do_match(_Meta, [], S) ->
do_clauses(_Meta, [], S) ->
{ [], S };
do_match(_Meta, [DecoupledClause], S) ->
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
{ [TDecoupledClause], TS };
% do_clauses(_Meta, [DecoupledClause], S) ->
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
% { [TDecoupledClause], TS };
do_match(Meta, DecoupledClauses, S) ->
do_clauses(Meta, DecoupledClauses, S) ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {Acc, CV}) ->
{ TX, TAcc } = each_clause(X, Acc),
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
Transformer = fun(X, {SAcc, UAcc, VAcc}) ->
{ TX, TS } = each_clause(Meta, X, SAcc),
{ TX,
{ elixir_scope:mergec(S, TS),
ordsets:union(UAcc, TS#elixir_scope.temp_vars),
[TS#elixir_scope.clause_vars|VAcc] } }
end,
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
{ TClauses, { TS, Unsafe, ReverseCV } } =
lists:mapfoldl(Transformer, {S, S#elixir_scope.unsafe_vars, []}, DecoupledClauses),
% Now get all the variables defined inside each clause
CV = lists:reverse(ReverseCV),
AllVars = lists:foldl(fun(KV, Acc) ->
elixir_scope:merge_clause_vars(Acc, KV)
end, orddict:new(), CV),
AllVars = lists:foldl(fun elixir_scope:merge_vars/2, [], CV),
% Create a new scope that contains a list of all variables
% defined inside all the clauses. It returns this new scope and
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
normalize_vars(Key, Ref, Acc)
end, TS, AllVars),
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Val }, Acc) ->
normalize_vars(Key, Val, Acc)
end, TS#elixir_scope{unsafe_vars=Unsafe}, AllVars),
% Expand all clauses by adding a match operation at the end
% that assigns variables missing in one clause to the others.
% that defines variables missing in one clause to the others.
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
@@ -149,7 +128,8 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
_ ->
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
{ VarName, _, SS } = elixir_scope:build_var('_', S),
StorageVar = { var, Line, VarName },
StorageExpr = { match, Line, StorageVar, Final },
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
end;
@@ -166,22 +146,26 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
% Handle each key/value clause pair and translate them accordingly.
each_clause({ do, Meta, [Condition], Expr }, S) ->
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
translate_do_match(Arg, S) ->
{ TArg, TS } = elixir_translator:translate_many(Arg, S#elixir_scope{extra=do_match}),
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
each_clause({ else, Meta, [Condition], Expr }, S) ->
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
each_clause(PMeta, { do, Meta, [Condition], Expr }, S) ->
Fun = case lists:keyfind(unsafe, 1, PMeta) of
{ unsafe, true } -> fun elixir_translator:translate_many/2;
_ -> fun translate_do_match/2
end,
{ Arg, Guards } = extract_guards(Condition),
clause(?line(Meta), Fun, [Arg], Expr, Guards, S);
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
{ TBody, SB } = elixir_translator:translate([Expr], SC),
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
each_clause(_PMeta, { else, Meta, [Condition], Expr }, S) ->
{ Arg, Guards } = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
each_clause({ Key, Meta, _, _ }, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
each_clause(_PMeta, { 'after', Meta, [Condition], Expr }, S) ->
{ TCondition, SC } = elixir_translator:translate(Condition, S),
{ TExpr, SB } = elixir_translator:translate(Expr, SC),
{ { clause, ?line(Meta), [TCondition], [], unblock(TExpr) }, SB }.
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
@@ -189,25 +173,18 @@ each_clause({ Key, Meta, _, _ }, S) ->
has_match_tuple({'receive', _, _, _, _}) ->
true;
has_match_tuple({'receive', _, _}) ->
true;
has_match_tuple({'case', _, _, _}) ->
true;
has_match_tuple({match, _, _, _}) ->
true;
has_match_tuple({'fun', _, { clauses, _ }}) ->
false;
has_match_tuple(H) when is_tuple(H) ->
has_match_tuple(tuple_to_list(H));
has_match_tuple(H) when is_list(H) ->
lists:any(fun has_match_tuple/1, H);
has_match_tuple(_) -> false.
% Normalize the given var in between clauses
@@ -215,29 +192,32 @@ has_match_tuple(_) -> false.
% its previous value.
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
FS = S#elixir_scope{
VS = S#elixir_scope{
vars=orddict:store(Key, Value, Vars),
clause_vars=orddict:store(Key, Value, ClauseVars)
},
Expr = case orddict:find(Key, Vars) of
{ ok, OldValue } -> { var, 0, OldValue };
{ ok, { PreValue, _ } } -> { var, 0, PreValue };
error -> { atom, 0, nil }
end,
{ { Key, Value, Expr }, FS }.
{ { Key, Value, Expr }, VS }.
% Generate match vars by checking if they were updated
% or not and assigning the previous value.
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
generate_match_vars([{ Key, Value, Expr }|T], ClauseVars, Left, Right) ->
case orddict:find(Key, ClauseVars) of
{ ok, NewValue } ->
{ ok, Value } ->
generate_match_vars(T, ClauseVars, Left, Right);
{ ok, ClauseValue } ->
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
{ ok, Clause } ->
generate_match_vars(T, ClauseVars,
[{ var, 0, element(1, Value) }|Left],
[{ var, 0, element(1, Clause) }|Right]);
error ->
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
generate_match_vars(T, ClauseVars,
[{ var, 0, element(1, Value) }|Left], [Expr|Right])
end;
generate_match_vars([], _ClauseVars, Left, Right) ->
@@ -249,15 +229,5 @@ generate_match(Line, [Left], [Right]) ->
generate_match(Line, LeftVars, RightVars) ->
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
listify(Expr) when not is_list(Expr) -> [Expr];
listify(Expr) -> Expr.
%% We don't use umergec because imports, aliases and
%% what not are not passed from one clause to the other.
merge_clauses_scope(S1, S2) ->
S1#elixir_scope{
counter=S2#elixir_scope.counter,
extra_guards=S2#elixir_scope.extra_guards,
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller
}.
unblock({ 'block', _, Exprs }) -> Exprs;
unblock(Exprs) -> [Exprs].
+65 -28
View File
@@ -3,32 +3,33 @@
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-behavior(gen_server).
-define(timeout, 30000).
-record(elixir_code_server, {
compilation_status=[],
argv=[],
compilation_status=ok,
loaded=[],
at_exit=[],
pool=[],
counter=0,
pool={[],0},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
waiting=[]
erl_compiler_options=nil
}).
call(Args) ->
gen_server:call(?MODULE, Args, get_timeout()).
gen_server:call(?MODULE, Args, ?timeout).
cast(Args) ->
gen_server:cast(?MODULE, Args).
get_timeout() ->
30000.
%% Callbacks
start_link() ->
{ ok, _ } = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
init(_args) ->
init(ok) ->
code:ensure_loaded('Elixir.Macro.Env'),
code:ensure_loaded('Elixir.Module.LocalsTracker'),
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
{ ok, #elixir_code_server{} }.
handle_call({ acquire, Path }, From, Config) ->
@@ -59,20 +60,31 @@ handle_call(argv, _From, Config) ->
handle_call(compiler_options, _From, Config) ->
{ reply, Config#elixir_code_server.compiler_options, Config };
handle_call(compilation_status, _From, Config) ->
{ reply, Config#elixir_code_server.compilation_status, Config };
handle_call({ compilation_status, CompilerPid }, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{ reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew} };
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
[H|T] ->
{ reply, module_tuple(H), Config#elixir_code_server{pool=T} };
[] ->
Counter = Config#elixir_code_server.counter,
{ reply, module_tuple(Counter), Config#elixir_code_server{counter=Counter+1} }
{ [H|T], Counter } ->
{ reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}} };
{ [], Counter } ->
{ reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}} }
end;
handle_call(_Request, _From, Config) ->
{ reply, undef, Config }.
handle_call(erl_compiler_options, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of
nil ->
Opts = erl_compiler_options(),
{ reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts} };
Opts ->
{ reply, Opts, Config }
end;
handle_call(Request, _From, Config) ->
{ stop, { badcall, Request }, Config }.
handle_cast({ at_exit, AtExit }, Config) ->
{ noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
@@ -84,14 +96,18 @@ handle_cast({ compiler_options, Options }, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ noreply, Config#elixir_code_server{compiler_options=Final} };
handle_cast(register_warning, Config) ->
handle_cast({ register_warning, CompilerPid }, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=error} };
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
_ -> { noreply, Config }
end;
handle_cast(reset_warnings, Config) ->
{ noreply, Config#elixir_code_server{compilation_status=ok} };
handle_cast({ reset_warnings, CompilerPid }, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
{ noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
handle_cast({ loaded, Path }, Config) ->
Current = Config#elixir_code_server.loaded,
@@ -112,11 +128,11 @@ handle_cast({ unload_files, Files }, Config) ->
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
handle_cast({ return_module_name, I }, #elixir_code_server{pool=Pool} = Config) ->
{ noreply, Config#elixir_code_server{pool=[I|Pool]} };
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
handle_cast(_Request, Config) ->
{ noreply, Config }.
handle_cast(Request, Config) ->
{ stop, { badcast, Request }, Config }.
handle_info(_Request, Config) ->
{ noreply, Config }.
@@ -127,4 +143,25 @@ terminate(_Reason, _Config) ->
code_change(_Old, Config, _Extra) ->
{ ok, Config }.
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
module_tuple(I) ->
{ list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
erl_compiler_options() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end.
+106 -118
View File
@@ -1,27 +1,21 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/4, format_error/1]).
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
-export([core/0, module/4, eval_forms/3, format_error/1]).
-include("elixir.hrl").
%% Public API
%% Get compilation options.
get_opt(Key) -> get_opt(Key, get_opts()).
get_opt(Key, Dict) ->
get_opt(Key) ->
Dict = elixir_code_server:call(compiler_options),
case lists:keyfind(Key, 1, Dict) of
false -> false;
{ Key, Value } -> Value
end.
get_opts() ->
elixir_code_server:call(compiler_options).
%% Compiles the given string.
%% Compilation entry points.
string(Contents, File) when is_list(Contents), is_binary(File) ->
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
quoted(Forms, File).
quoted(Forms, File) when is_binary(File) ->
@@ -29,71 +23,121 @@ quoted(Forms, File) when is_binary(File) ->
try
put(elixir_compiled, []),
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
elixir_lexical:run(File, fun
(Pid) ->
Env = elixir:env_for_eval([{line,1},{file,File}]),
eval_forms(Forms, [], Env#elixir_env{lexical_tracker=Pid})
end),
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(elixir_utils: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.
%% Evaluation
eval_forms(Forms, Vars, E) ->
case (E#elixir_env.module == nil) andalso allows_fast_compilation(Forms) of
true -> eval_compilation(Forms, Vars, E);
false -> code_loading_compilation(Forms, Vars, E)
end.
eval_compilation(Forms, Vars, E) ->
Binding = [{ Key, Value } || { _Name, _Kind, Key, Value } <- Vars],
{ Result, _Binding, EE, _S } = elixir:eval_forms(Forms, Binding, E),
{ Result, EE }.
code_loading_compilation(Forms, Vars, #elixir_env{line=Line} = E) ->
Dict = [{ { Name, Kind }, { Value, 0 } } || { Name, Kind, Value, _ } <- Vars],
S = elixir_env:env_to_scope_with_vars(E, Dict),
{ Expr, EE, _S } = elixir:quoted_to_erl(Forms, E, S),
eval_forms(Forms, Line, Vars, S) ->
{ Module, I } = retrieve_module_name(),
{ Exprs, FS } = elixir_translator:translate(Forms, S),
Fun = module_form_fun(S#elixir_scope.module),
Form = module_form(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
Args = [X || { _, _, _, X } <- Vars],
Fun = code_fun(E#elixir_env.module),
Form = code_mod(Fun, Expr, Line, E#elixir_env.file, Module, Vars),
Args = list_to_tuple([V || { _, _, _, V } <- Vars]),
%% Pass { native, false } to speed up bootstrap
%% process when native is set to true
{ module(Form, S#elixir_scope.file, [{native,false}], true, fun(_, Binary) ->
Res = Module:Fun(S#elixir_scope.module, Args),
code:delete(Module),
module(Form, E#elixir_env.file, [{native,false}], true, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
case beam_lib:chunks(Binary, [labeled_locals]) of
{ ok, { _, [{ labeled_locals, []}] } } ->
code:purge(Module),
return_module_name(I);
_ ->
ok
end,
Purgeable =
case beam_lib:chunks(Binary, [labeled_locals]) of
{ ok, { _, [{ labeled_locals, []}] } } -> true;
_ -> false
end,
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
end).
Res
end), FS }.
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
Res = Module:Fun(Args),
code:delete(Module),
if Purgeable ->
code:purge(Module),
return_module_name(I);
true ->
ok
end,
{ Res, E }.
%% Internal API
code_fun(nil) -> '__FILE__';
code_fun(_) -> '__MODULE__'.
code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
Tuple = { tuple, Line, [{ var, Line, K } || { _, _, K, _ } <- Vars] },
Relative = elixir_utils:relative_to_cwd(File),
[
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ Fun, 1 }, { '__RELATIVE__', 0 }] },
{ function, Line, Fun, 1, [
{ clause, Line, [Tuple], [], [Expr] }
] },
{ function, Line, '__RELATIVE__', 0, [
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
] }
].
retrieve_module_name() ->
elixir_code_server:call(retrieve_module_name).
return_module_name(I) ->
elixir_code_server:cast({ return_module_name, I }).
allows_fast_compilation({ '__block__', _, Exprs }) ->
lists:all(fun allows_fast_compilation/1, Exprs);
allows_fast_compilation({defmodule,_,_}) -> true;
allows_fast_compilation(_) -> false.
%% 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, File, Callback) ->
Options = case get_opt(debug_info) of
true -> [debug_info];
_ -> []
end,
module(Forms, File, Options, false, Callback).
module(Forms, File, Opts, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info];
false -> []
end,
module(Forms, File, Final, false, Callback).
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
{ Options, SkipNative } = compile_opts(Forms, RawOptions),
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
Listname = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
@@ -110,44 +154,14 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
format_errors(Errors)
end.
%% Compile core files for bootstrap.
%% Invoked from the Makefile.
core() ->
application:start(elixir),
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()].
%% HELPERS
compile_opts(Forms, Options) ->
EnvOptions = env_default_opts(),
compiler_options(Forms, Options) ->
EnvOptions = elixir_code_server:call(erl_compiler_options),
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
case SkipNative or lists:member([{native,false}], Options) of
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
false -> { Options ++ EnvOptions, SkipNative }
end.
env_default_opts() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end.
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
contains_on_load([_|T]) -> contains_on_load(T);
contains_on_load([]) -> false.
@@ -156,46 +170,13 @@ no_auto_import() ->
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
module_form_fun(nil) -> '__FILE__';
module_form_fun(_) -> '__MODULE__'.
module_form(Fun, Exprs, Line, File, Module, Vars) when
is_binary(File), is_integer(Line), is_list(Exprs), is_atom(Module) ->
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
%% CORE HANDLING
Args = [{ var, Line, '_@MODULE'}, Cons],
Relative = elixir_utils:relative_to_cwd(File),
[
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ Fun, 2 }, { '__RELATIVE__', 0 }] },
{ function, Line, Fun, length(Args), [
{ clause, Line, Args, [], Exprs }
] },
{ function, Line, '__RELATIVE__', 0, [
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
] }
].
%% Generate module names from code server.
retrieve_module_name() ->
elixir_code_server:call(retrieve_module_name).
return_module_name(I) ->
elixir_code_server:cast({ return_module_name, I }).
%% 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() ->
application:start(elixir),
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()].
core_file(File) ->
try
@@ -214,13 +195,14 @@ core_main() ->
<<"lib/elixir/lib/keyword.ex">>,
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/record.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
<<"lib/elixir/lib/stream/reducers.ex">>,
<<"lib/elixir/lib/enum.ex">>,
<<"lib/elixir/lib/inspect/algebra.ex">>,
<<"lib/elixir/lib/inspect.ex">>,
@@ -231,18 +213,24 @@ core_main() ->
<<"lib/elixir/lib/io.ex">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/system.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
<<"lib/elixir/lib/module/dispatch_tracker.ex">>
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
].
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
ok = file:write_file(Path, Binary),
Path.
%% ERROR HANDLING
format_error({ skip_native, Module }) ->
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
[elixir_errors:inspect(Module)]).
[elixir_aliases:inspect(Module)]).
format_errors([]) ->
exit({ nocompile, "compilation failed but no error was raised" });
+38
View File
@@ -0,0 +1,38 @@
-module(elixir_counter).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3, next/0]).
-behavior(gen_server).
-define(timeout, 1000). %% 1 second
-define(limit, 4294967296). %% 2^32
next() ->
gen_server:call(?MODULE, next, ?timeout).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
init(Counter) ->
{ ok, Counter }.
handle_call(next, _From, Counter) ->
{ reply, Counter, bump(Counter) };
handle_call(Request, _From, Counter) ->
{ stop, { badcall, Request }, Counter }.
handle_cast(Request, Counter) ->
{ stop, { badcast, Request }, Counter }.
handle_info(_Request, Counter) ->
{ noreply, Counter }.
terminate(_Reason, _Counter) ->
ok.
code_change(_Old, Counter, _Extra) ->
{ ok, Counter }.
bump(Counter) when Counter < ?limit ->
Counter + 1;
bump(_Counter) ->
0.
+101 -120
View File
@@ -1,17 +1,9 @@
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
-module(elixir_def).
-export([table/1,
clauses_table/1,
setup/1,
cleanup/1,
reset_last/1,
lookup_definition/2,
delete_definition/2,
wrap_definition/6,
store_definition/7,
store_each/8,
unwrap_stored_definitions/2,
format_error/1]).
-export([table/1, clauses_table/1, setup/1,
cleanup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/2,
store_each/8, format_error/1]).
-include("elixir.hrl").
-define(attr, '__def_table').
@@ -53,76 +45,59 @@ delete_definition(Module, Tuple) ->
ets:delete(table(Module), Tuple),
ets:delete(clauses_table(Module), Tuple).
%% Wraps the function into a call to store_definition once the function
%% definition is read. The function is compiled into a meta tree to ensure
%% we will receive the full function.
%%
%% We need to wrap functions instead of eagerly defining them to ensure
%% functions inside branches won't propagate, for example:
%%
%% if false do
%% def bar, do: 1
%% else
%% def bar, do: 2
%% end
%%
%% 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, Meta, Call, Expr, CheckClauses, S) ->
Line = ?line(Meta),
?wrap_call(Line, ?MODULE, store_definition, [
{atom, Line, Kind},
{integer, Line, Line},
{atom, Line, CheckClauses},
{var, Line, '_@MODULE'},
Call,
Expr,
elixir_scope:serialize(S)
]).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
S = elixir_scope:deserialize(RawS),
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
E = (elixir_locals:get_cached_env(Pos))#elixir_env{line=Line},
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
error ->
Format = [Kind, 'Elixir.Macro':to_string(NameAndArgs)],
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
Tuple ->
Tuple
{ Name, Args } = case NameAndArgs of
{ N, _, A } when is_atom(N), is_atom(A) -> { N, [] };
{ N, _, A } when is_atom(N), is_list(A) -> { N, A };
_ -> elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE, { invalid_def, Kind, NameAndArgs })
end,
assert_no_aliases_name(Line, Name, Args, S),
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
%% Now that we have verified the call format,
%% extract meta information like file and context.
{ _, Meta, _ } = Call,
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { no_module, Kind });
%% Check if there is a file information in the definition.
%% If so, we assume this come from another source and we need
%% to linify taking into account keep line numbers.
{ File, Key } = case lists:keyfind(file, 1, Meta) of
{ file, Bin } when is_binary(Bin) -> { Bin, keep };
_ -> { nil, line }
end,
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
LinifyArgs = elixir_quote:linify(Line, Key, Args),
LinifyGuards = elixir_quote:linify(Line, Key, Guards),
LinifyBody = elixir_quote:linify(Line, Key, Body),
assert_no_aliases_name(Line, Name, Args, E),
store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, File, E).
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_env{module=Module} = ER) ->
Arity = length(Args),
Tuple = { Name, Arity },
S = DS#elixir_scope{module=Module, function=Tuple},
elixir_tracker:record_definition(Tuple, Kind, Module),
E = ER#elixir_env{function=Tuple},
elixir_locals:record_definition(Tuple, Kind, Module),
CO = elixir_compiler:get_opts(),
Location = retrieve_file(Line, Module, S, CO),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
Location = retrieve_file(Line, MetaFile, Module, E),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, E),
{ Function, Defaults, Super } = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
DefaultsLength = length(Defaults),
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = TS#elixir_scope.file,
Table = table(Module),
File = E#elixir_env.file,
Table = table(Module),
CTable = clauses_table(Module),
compile_super(Module, TS),
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
compile_super(Module, Super, E),
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
store_each(CheckClauses, Kind, File, Location,
Table, CTable, DefaultsLength, Function),
@@ -133,85 +108,83 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
%% @on_definition
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO) ->
case elixir_compiler:get_opt(internal, CO) of
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
case elixir_compiler:get_opt(internal) of
true ->
ok;
_ ->
Env = elixir_scope:to_ex_env({ Line, S }),
Env = elixir_env:env_to_ex({ Line, E }),
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
end.
%% Retrieve @file or fallback to default
%% Retrieve meta file, @file or fallback to default
retrieve_file(Line, Module, S, CO) ->
case elixir_compiler:get_opt(internal, CO) of
true -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
_ ->
case 'Elixir.Module':get_attribute(Module, file) of
nil -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
Else ->
'Elixir.Module':delete_attribute(Module, file),
{ Else, 0 }
end
retrieve_file(Line, File, Module, E) ->
case not(elixir_compiler:get_opt(internal)) andalso
'Elixir.Module':get_attribute(Module, file) of
X when X == nil; X == false ->
{ elixir_utils:characters_to_list(retrieve_file(File, E)), Line };
X ->
'Elixir.Module':delete_attribute(Module, file),
{ X, 0 }
end.
retrieve_file(nil, E) -> E#elixir_env.file;
retrieve_file(File, _E) -> File.
%% Compile super
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
compile_super(Module, true, #elixir_env{function=Function}) ->
elixir_def_overridable:store(Module, Function, true);
compile_super(_Module, _S) -> ok.
compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_integer(Line) ->
Args = elixir_quote:linify(Line, RawArgs),
Guards = elixir_quote:linify(Line, RawGuards),
Body = elixir_quote:linify(Line, RawBody),
Arity = length(Args),
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
Arity = length(Args),
%% 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 is_macro(Kind) of
AllArgs = case is_macro(Kind) of
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
false -> Args
end,
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
{ Clauses, TS } = translate_clause(Line, Kind, Unpacked, Guards, Body, S),
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
AllArgs, Guards, expr_from_body(Line, Body), E),
S = elixir_env:env_to_scope(E),
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
{ Clauses, Super } = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
Function = { function, Line, Name, Arity, Clauses },
{ Function, Defaults, TS }.
{ Function, Defaults, Super }.
translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
{ [], S };
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
Expr = expr_from_body(Line, Body),
{ TClause, TS } = elixir_clauses:assigns_block(Line,
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
{ [], false };
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{ TClause, TS } = elixir_clauses:clause(Line,
fun elixir_translator:translate_many/2, Args, Body, Guards, S),
%% Set __CALLER__ if used
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
true ->
FBody = { 'match', Line,
{ 'var', Line, '__CALLER__' },
?wrap_call(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
?wrap_call(Line, elixir_env, env_to_ex, [{ var, Line, '_@CALLER' }])
},
setelement(5, TClause, [FBody|element(5, TClause)]);
false -> TClause
end,
{ [FClause], TS }.
{ [FClause], TS#elixir_scope.super }.
expr_from_body(_Line, nil) -> nil;
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
@@ -222,20 +195,20 @@ is_macro(_) -> false.
% Unwrap the functions stored in the functions table.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
unwrap_stored_definitions(File, Module) ->
unwrap_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
unwrap_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
unwrap_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
Tuple = element(1, Fun),
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
case Clauses of
[] -> { false, Exports, Private, Def, Defmacro };
_ -> unwrap_stored_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
end,
{ NewFunctions, NewTail } = case NewFun of
@@ -247,26 +220,26 @@ unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defm
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
end,
unwrap_stored_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
unwrap_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_stored_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
unwrap_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
{ All, Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
unwrap_stored_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
unwrap_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
Macro = { ?elixir_macro(Name), Arity + 1 },
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
unwrap_stored_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% { Name, Arity }, Kind, Line, Check, Defaults
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
{ Fun, Exports, [Info|Private], Def, Defmacro };
unwrap_stored_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% { Name, Arity }, Kind, Line, Check, Defaults
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
{ false, Exports, [Info|Private], Def, Defmacro }.
@@ -336,10 +309,10 @@ check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
elixir_errors:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, S) ->
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
[ begin
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE,
elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE,
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
@@ -347,9 +320,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
((Arity >= (A - D)) andalso (Arity < A)) orelse
((A >= (Arity - Defaults)) andalso (A < Arity)).
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
elixir_errors:syntax_error(Line, File, Message, [Atom]);
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_env{file=File}) when is_atom(Atom) ->
elixir_errors:form_error(Line, File, ?MODULE, { no_alias, Atom });
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
ok.
@@ -379,4 +351,13 @@ format_error({ungrouped_clause,{Kind,Name,Arity,OrigLine,OrigFile}}) ->
[Kind, Kind, Name, Arity, OrigFile, OrigLine]);
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]);
format_error({no_alias, Atom}) ->
io_lib:format("function names should start with lowercase characters or underscore, invalid name ~ts", [Atom]);
format_error({invalid_def, Kind, NameAndArgs}) ->
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
format_error({missing_do, Kind}) ->
io_lib:format("missing do keyword in ~ts", [Kind]).
+14 -4
View File
@@ -1,8 +1,18 @@
% Handle default clauses for function definitions.
-module(elixir_def_defaults).
-export([unpack/4]).
-export([expand/2, unpack/4]).
-include("elixir.hrl").
expand(Args, E) ->
lists:mapfoldl(fun
({ '//', Meta, [Left, Right] }, Acc) ->
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
{ { '//', Meta, [ELeft, ERight] }, EL };
(Left, Acc) ->
elixir_exp:expand(Left, Acc)
end, E, Args).
unpack(Kind, Name, Args, S) ->
unpack_each(Kind, Name, Args, [], [], S).
@@ -15,12 +25,12 @@ unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = build_match(Acc, Line, []),
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, S),
{ InvokeArgs, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_many/2, Base ++ Args, S),
{ DefInvoke, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
Call = { call, Line,
{ atom, Line, name_for_kind(Kind, Name) },
InvokeArgs
DefInvoke
},
Clause = { clause, Line, DefArgs, [], [Call] },
-62
View File
@@ -1,62 +0,0 @@
%% Module responsible for local invocation of macros and functions.
-module(elixir_def_local).
-export([macro_for/3, function_for/3]).
-include("elixir.hrl").
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
try elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro, Clauses /= [];
All, Kind == defmacrop, Clauses /= [] ->
fun() -> get_function(Line, Module, Clauses) end;
_ ->
false
catch
error:badarg -> false
end.
function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
%% There is no need to record such calls
%% since they come from funtions that were
%% already analyzed at compilation time.
get_function(Line, Module, Clauses);
_ ->
[_|T] = erlang:get_stacktrace(),
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
end.
get_function(Line, Module, Clauses) ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result.
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
Remote = { remote, Line,
{ atom, Line, ?MODULE },
{ atom, Line, function_for }
},
%% 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) ->
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
rewrite_clause(List, Module) when is_list(List) ->
[rewrite_clause(Item, Module) || Item <- List];
rewrite_clause(Else, _) -> Else.
+12 -26
View File
@@ -1,7 +1,7 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([store_pending/1, is_defined/2, ensure_defined/4,
assign_args/3, name/2, store/3, format_error/1]).
-export([store_pending/1, ensure_defined/4,
name/2, store/3, format_error/1]).
-include("elixir.hrl").
overridable(Module) ->
@@ -12,32 +12,13 @@ overridable(Module, Value) ->
%% Check if an overridable function is defined.
is_defined(Module, Tuple) ->
ensure_defined(Meta, Module, Tuple, S) ->
Overridable = overridable(Module),
case orddict:find(Tuple, Overridable) of
{ ok, { _, _, _, _ } } -> true;
_ -> false
{ ok, { _, _, _, _ } } -> ok;
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
end.
ensure_defined(Meta, Module, Tuple, S) ->
case is_defined(Module, Tuple) of
true -> ok;
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
end.
%% Assign pseudo variables to the given vars.
assign_args(Line, Args, S) when is_integer(Line) ->
{ FArgs, _ } = lists:mapfoldl(fun(X, Acc) -> assign_args(Line, X, Acc, S) end, 1, Args),
FArgs.
assign_args(Line, X, Acc, _) ->
Match = { match, Line, X, { var, Line, super_arg(Acc) } },
{ Match, Acc + 1 }.
super_arg(Counter) ->
?atom_concat(['_@S', Counter]).
%% Gets the name based on the function and stored overridables
name(Module, Function) ->
@@ -62,7 +43,12 @@ store(Module, Function, GenerateName) ->
false -> { Kind, Name }
end,
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours),
case code:is_loaded('Elixir.Module.LocalsTracker') of
{ _, _ } ->
'Elixir.Module.LocalsTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
_ ->
ok
end,
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, File, Location,
@@ -81,7 +67,7 @@ format_error({ no_super, Module, { Name, Arity } }) ->
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
[Name, Arity, elixir_errors:inspect(Module), Joined]).
[Name, Arity, elixir_aliases:inspect(Module), Joined]).
format_fa({ Name, Arity }) ->
A = atom_to_binary(Name, utf8),
+175 -299
View File
@@ -2,32 +2,33 @@
%% This module access the information stored on the scope
%% 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_import/5,
-export([dispatch_import/5, dispatch_require/6,
require_function/5, import_function/4,
expand_import/6, expand_require/6, find_import/4,
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
expand_import/5, expand_require/5,
default_functions/0, default_macros/0, default_requires/0,
find_import/4, format_error/1]).
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
-define(builtin, 'Elixir.Kernel').
-define(kernel, 'Elixir.Kernel').
default_functions() ->
[ { ?builtin, elixir_imported_functions() } ].
[ { ?kernel, elixir_imported_functions() } ].
default_macros() ->
[ { ?builtin, elixir_imported_macros() } ].
[ { ?kernel, elixir_imported_macros() } ].
default_requires() ->
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
find_import(Meta, Name, Arity, S) ->
find_import(Meta, Name, Arity, E) ->
Tuple = { Name, Arity },
case find_dispatch(Meta, Tuple, S) of
case find_dispatch(Meta, Tuple, [], E) of
{ function, Receiver } ->
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
Receiver;
{ macro, Receiver } ->
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
Receiver;
_ ->
false
@@ -35,236 +36,198 @@ find_import(Meta, Name, Arity, S) ->
%% Function retrieval
import_function(Meta, Name, Arity, S) ->
import_function(Meta, Name, Arity, E) ->
Tuple = { Name, Arity },
case find_dispatch(Meta, Tuple, S) of
case find_dispatch(Meta, Tuple, [], E) of
{ function, Receiver } ->
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
remote_function(Meta, Receiver, Name, Arity, S);
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
remote_function(Receiver, Name, Arity, E);
{ macro, _Receiver } ->
false;
{ import, Receiver } ->
require_function(Meta, Receiver, Name, Arity, S);
require_function(Meta, Receiver, Name, Arity, E);
false ->
case elixir_import:special_form(Name, Arity) of
true -> false;
false ->
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
elixir_locals:record_local(Tuple, E#elixir_env.module, E#elixir_env.function),
{ local, Name, Arity }
end
end.
require_function(Meta, Receiver, Name, Arity, S) ->
require_function(_Meta, Receiver, Name, Arity, E) ->
case is_element({ Name, Arity }, get_optional_macros(Receiver)) of
true -> false;
false -> remote_function(Receiver, Name, Arity, E)
end.
remote_function(Receiver, Name, Arity, E) ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
Tuple = { Name, Arity },
Final =
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ remote, Final, Name, Arity }.
%% Dispatches
dispatch_import(Meta, Name, Args, E, Callback) ->
case expand_import(Meta, { Name, length(Args) }, Args, E, []) of
{ ok, Receiver, Quoted } ->
expand_quoted(Meta, Receiver, Quoted, E);
{ ok, Receiver } ->
elixir_exp:expand({ { '.', [], [Receiver, Name] }, Meta, Args }, E);
error ->
Callback()
end.
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
Arity = length(Args),
Tuple = { Name, Arity },
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> false;
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> Callback(erlang);
false ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
remote_function(Meta, Receiver, Name, Arity, S)
end.
%% Function dispatch
dispatch_import(Meta, Name, Args, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
case find_dispatch(Meta, Tuple, S) of
{ function, Receiver } ->
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
{ import, Receiver } ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
Result ->
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
end
end.
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
true ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
false ->
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
end
end.
%% Macros expansion
expand_import(Meta, Tuple, Args, Module, Extra, S) ->
Result = find_dispatch(Meta, Tuple, Extra, S),
do_expand_import(Meta, Tuple, Args, Module, S, Result).
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
Function = S#elixir_scope.function,
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, S),
case Fun of
false ->
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
_ ->
case Result of
{ Kind, Receiver } when Kind == import; Receiver == Module ->
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
{ _, Receiver } ->
Error = { macro_conflict, { Receiver, Name, Arity } },
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
false ->
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
end
end.
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
case Result of
{ macro, ?builtin } ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
end;
{ macro, Receiver } ->
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
{ import, Receiver } ->
expand_require(Meta, Receiver, Tuple, Args, Module, S);
_ ->
{ error, noexpansion }
end.
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
case is_element(Tuple, in_elixir_macros()) of
true ->
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
false ->
{ error, noexpansion }
case expand_require(Meta, Receiver, Tuple, Args, E) of
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Quoted, E);
error -> Callback(Receiver)
end
end;
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
Function = S#elixir_scope.function,
dispatch_require(_Meta, Receiver, _Name, _Args, _E, Callback) ->
Callback(Receiver).
Fun = (Module == Receiver) andalso (Function /= nil) andalso
(Function /= Tuple) andalso elixir_def_local:macro_for(Tuple, false, S),
%% Macros expansion
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true ->
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
end;
expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
Module = E#elixir_env.module,
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
Function = E#elixir_env.function,
Local = (Function /= nil) andalso (Function /= Tuple) andalso
elixir_locals:macro_for(Module, Name, Arity),
case Dispatch of
%% In case it is an import, or the receive is the same as the
%% current module (happens in bootstraping), we dispatch the import.
{ Kind, Receiver } when Kind == import; Receiver == Module ->
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
%% There is a local and an import. This is a conflict.
{ _, Receiver } when Local /= false ->
Error = { macro_conflict, { Receiver, Name, Arity } },
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
%% There is no local. Dispatch the import.
_ when Local == false ->
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
%% Dispatch to the local.
_ ->
elixir_tracker:record_local(Tuple, Module, Function),
{ ok, Receiver, expand_macro_fun(Meta, Fun(), Receiver, Name, Args, Module, S) }
elixir_locals:record_local(Tuple, Module, Function),
{ ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E) }
end.
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
case Result of
{ function, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
Endpoint = case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ ok, Endpoint };
{ macro, Receiver } ->
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
{ import, Receiver } ->
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
{ ok, _, _ } = Response -> Response;
error -> { ok, Receiver }
end;
false ->
error
end.
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, E) ->
Module = E#elixir_env.module,
case is_element(Tuple, get_optional_macros(Receiver)) of
true ->
Requires = E#elixir_env.requires,
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
true ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
false ->
Info = { unrequired_module, { Receiver, Name, length(Args), Requires } },
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Info)
end;
false ->
error
end.
%% Expansion helpers
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
Requires = S#elixir_scope.requires,
case (Receiver == Module) or is_element(Receiver, Requires) or skip_require(Meta) of
true -> ok;
false ->
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
end,
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E) ->
Line = ?line(Meta),
SArg = {Line,S},
EArg = { Line, E },
try
apply(Fun, [SArg|Args])
apply(Fun, [EArg|Args])
catch
Kind:Reason ->
Info = { Receiver, Name, length(Args), location(Line, S) },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
Info = { Receiver, Name, length(Args), location(Line, E) },
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), EArg))
end.
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
expand_macro_named(Meta, Receiver, Name, Arity, Args, E) ->
ProperName = ?elixir_macro(Name),
ProperArity = Arity + 1,
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E).
translate_expansion(Meta, Tree, S) ->
expand_quoted(Meta, Receiver, Quoted, E) ->
Line = ?line(Meta),
Next = elixir_counter:next(),
try
elixir_translator:translate_each(
elixir_quote:linify(Line, Tree),
S
)
elixir_exp:expand(
elixir_quote:linify_with_context_counter(Line, { Receiver, Next }, Quoted),
E)
catch
Kind:Reason ->
erlang:raise(Kind, Reason, munge_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, E), erlang:get_stacktrace(), nil))
end.
mfa(Line, #elixir_scope{module=nil} = S) ->
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
mfa(Line, #elixir_env{module=nil} = E) ->
{ elixir_compiler, '__FILE__', 2, location(Line, E) };
mfa(Line, #elixir_env{module=Module, function=nil} = E) ->
{ Module, '__MODULE__', 0, location(Line, E) };
mfa(Line, #elixir_env{module=Module, function={ Name, Arity }} = E) ->
{ Module, Name, Arity, location(Line, E) }.
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
{ Module, '__MODULE__', 0, location(Line, S) };
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
{ Module, Name, Arity, location(Line, S) }.
location(Line, S) ->
[{ file, S#elixir_scope.file }, { line, Line }].
location(Line, E) ->
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
%% Helpers
skip_require(Meta) ->
lists:keyfind(require, 1, Meta) == { require, false }.
find_dispatch(Meta, Tuple, S) ->
find_dispatch(Meta, Tuple, [], S).
find_dispatch(Meta, Tuple, Extra, S) ->
case is_import(Meta, Tuple, S) of
find_dispatch(Meta, Tuple, Extra, E) ->
case is_import(Meta) of
{ import, _ } = Import ->
Import;
false ->
Functions = S#elixir_scope.functions,
Macros = Extra ++ S#elixir_scope.macros,
File = S#elixir_scope.file,
FunMatch = find_dispatch(Tuple, Functions),
MacMatch = find_dispatch(Tuple, Macros),
Funs = E#elixir_env.functions,
Macs = Extra ++ E#elixir_env.macros,
FunMatch = find_dispatch(Tuple, Funs),
MacMatch = find_dispatch(Tuple, Macs),
case { FunMatch, MacMatch } of
{ [], [Receiver] } -> { macro, Receiver };
@@ -274,21 +237,18 @@ find_dispatch(Meta, Tuple, Extra, S) ->
{ Name, Arity } = Tuple,
[First, Second|_] = FunMatch ++ MacMatch,
Error = { ambiguous_call, { First, Second, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Error)
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error)
end
end.
find_dispatch(Tuple, List) ->
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
is_import(Meta, Tuple, S) ->
is_import(Meta) ->
case lists:keyfind(import, 1, Meta) of
{ import, _ } = Import ->
case lists:keyfind(context, 1, Meta) of
{ context, Context } ->
not_an_import(Tuple, Context, S#elixir_scope.macro_functions)
andalso not_an_import(Tuple, Context, S#elixir_scope.macro_macros)
andalso Import;
{ context, _ } -> Import;
false ->
false
end;
@@ -296,58 +256,30 @@ is_import(Meta, Tuple, S) ->
false
end.
not_an_import(Tuple, Context, Dict) ->
case orddict:find(Context, Dict) of
{ ok, Pairs } ->
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs);
error ->
true
end.
%% We've reached the invoked macro, skip it with the rest
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
prune_stacktrace(Info, [{ _, _, [E|_], _ }|_], E) ->
[Info];
%% We've reached the elixir_dispatch internals, skip it with the rest
munge_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
prune_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
[Info];
munge_stacktrace(Info, [H|T], S) ->
[H|munge_stacktrace(Info, T, S)];
munge_stacktrace(Info, [], _) ->
prune_stacktrace(Info, [H|T], E) ->
[H|prune_stacktrace(Info, T, E)];
prune_stacktrace(Info, [], _) ->
[Info].
remote_function(Meta, Receiver, Name, Arity, S) ->
Line = ?line(Meta),
Final =
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ { 'fun', Line, { function,
{ atom, Line, Final },
{ atom, Line, Name },
{ integer, Line, Arity}
} }, S }.
%% ERROR HANDLING
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], <<", ">>),
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
[elixir_errors:inspect(Receiver), Name, Arity, String]);
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
Module = elixir_aliases:inspect(Receiver),
io_lib:format("you must require ~ts before invoking the macro ~ts.~ts/~B",
[Module, Module, Name, Arity]);
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B",
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B, "
"please rename the local macro or remove the conflicting import",
[Name, Arity, elixir_aliases:inspect(Receiver), Name, Arity]);
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
[Name, Arity, elixir_aliases:inspect(Mod1), elixir_aliases:inspect(Mod2)]).
%% INTROSPECTION
@@ -367,36 +299,27 @@ get_optional_macros(Receiver) ->
elixir_imported_functions() ->
try
?builtin:'__info__'(functions)
?kernel:'__info__'(functions)
catch
error:undef -> in_erlang_functions()
end.
elixir_imported_macros() ->
try
?builtin:'__info__'(macros)
catch
error:undef -> in_erlang_macros()
end.
%% Macros imported from Kernel module. Sorted on compilation.
in_elixir_macros() ->
try
?builtin:'__info__'(macros)
?kernel:'__info__'(macros)
catch
error:undef -> []
end.
%% Functions imported from Erlang module. MUST BE SORTED.
%% TODO: Inline operators here as well once bug in erl_eval is fixed.
in_erlang_functions() ->
[
{ abs, 1 },
{ atom_to_binary, 2 },
{ apply, 2 },
{ apply, 3 },
{ atom_to_list, 1 },
{ binary_part, 3 },
{ binary_to_atom, 2 },
{ binary_to_existing_atom, 2 },
{ binary_to_float, 1 },
{ binary_to_float, 2 },
{ binary_to_integer, 1 },
@@ -410,7 +333,9 @@ in_erlang_functions() ->
{ exit, 1 },
% { float, 1 },
{ float_to_binary, 1 },
% { float_to_binary, 2 },
{ float_to_list, 1 },
% { float_to_list, 2 },
{ hd, 1 },
{ integer_to_binary, 1 },
{ integer_to_binary, 2 },
@@ -440,7 +365,6 @@ in_erlang_functions() ->
{ list_to_float, 1 },
{ list_to_integer, 1 },
{ list_to_integer, 2 },
{ list_to_pid, 1 },
{ list_to_tuple, 1 },
{ make_ref, 0 },
{ max, 2 },
@@ -448,7 +372,6 @@ in_erlang_functions() ->
{ node, 0 },
{ node, 1 },
% { now, 0 },
{ pid_to_list, 1 },
{ round, 1 },
{ self, 0 },
{ size, 1 },
@@ -466,50 +389,3 @@ in_erlang_functions() ->
{ tuple_size, 1 },
{ tuple_to_list, 1 }
].
%% Macros implemented in Erlang. MUST BE SORTED.
in_erlang_macros() ->
[
{'!',1},
{'!=',2},
{'!==',2},
{'*',2},
{'+',1},
{'+',2},
{'++',2},
{'-',1},
{'-',2},
{'--',2},
{'/',2},
{'<',2},
{'<-',2},
{'<=',2},
{'==',2},
{'===',2},
{'>',2},
{'>=',2},
{'@',1},
{'and',2},
{apply,2},
{apply,3},
{'case',2},
{def,1},
{def,2},
{def,4},
{defmacro,1},
{defmacro,2},
{defmacro,4},
{defmacrop,1},
{defmacrop,2},
{defmacrop,4},
{defmodule,2},
{defp,1},
{defp,2},
{defp,4},
{in,2},
{'not',1},
{'or',2},
{'receive',1},
{'try',1},
{'xor',2}
].
+38
View File
@@ -0,0 +1,38 @@
-module(elixir_env).
-include("elixir.hrl").
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2, env_to_ex/1]).
-export([mergea/2, mergev/2]).
%% Conversion in between #elixir_env, #elixir_scope and Macro.Env
env_to_ex({ Line, #elixir_env{} = Env }) ->
erlang:setelement(1, Env#elixir_env{line=Line}, 'Elixir.Macro.Env').
ex_to_env(Env) when element(1, Env) == 'Elixir.Macro.Env' ->
erlang:setelement(1, Env, elixir_env).
env_to_scope(#elixir_env{module=Module,file=File,function=Function,context=Context}) ->
#elixir_scope{module=Module,file=File,function=Function,context=Context}.
env_to_scope_with_vars(#elixir_env{} = Env, Vars) ->
(env_to_scope(Env))#elixir_scope{
vars=orddict:from_list(Vars),
counter=[{'_',length(Vars)}]
}.
%% SCOPE MERGING
%% Receives two scopes and return a new scope based on the second
%% with their variables merged.
mergev(E1, E2) ->
E2#elixir_env{
vars=ordsets:union(E1#elixir_env.vars, E2#elixir_env.vars)
}.
%% Receives two scopes and return the later scope
%% keeping the variables from the first (counters,
%% imports and everything else are passed forward).
mergea(E1, E2) ->
E2#elixir_env{vars=E1#elixir_env.vars}.
+22 -69
View File
@@ -1,53 +1,40 @@
% 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,
compile_error/3, compile_error/4, inspect/1,
form_error/4, parse_error/4, assert_module_scope/3,
assert_no_function_scope/3, assert_function_scope/3,
assert_no_match_scope/3, assert_no_guard_scope/3,
assert_no_match_or_guard_scope/3, warn/1,
-export([compile_error/3, compile_error/4,
form_error/4, parse_error/4, warn/1,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4]).
-include("elixir.hrl").
warn(Warning) ->
elixir_code_server:cast(register_warning),
CompilerPid = get(elixir_compiler_pid),
if
CompilerPid =/= undefined ->
elixir_code_server:cast({ register_warning, CompilerPid });
true -> false
end,
io:put_chars(standard_error, Warning).
%% Handle inspecting for exceptions modules
%% Raised during expansion/translation/compilation.
inspect(Atom) when is_atom(Atom) ->
'Elixir.Inspect.Atom':inspect(Atom).
-spec form_error(list(), binary(), module(), any()) -> no_return().
%% Raised during macros translation.
form_error(Meta, File, Module, Desc) ->
compile_error(Meta, File, format_error(Module, Desc)).
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string()) -> no_return().
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string(), list()) -> no_return().
syntax_error(Meta, File, Message) when is_list(Message) ->
syntax_error(Meta, File, iolist_to_binary(Message));
syntax_error(Meta, File, Message) when is_binary(Message) ->
raise(Meta, File, 'Elixir.SyntaxError', Message).
syntax_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.SyntaxError', elixir_utils:characters_to_binary(Message)).
-spec compile_error(list(), binary(), iolist()) -> no_return().
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
compile_error(Meta, File, Message) when is_list(Message) ->
compile_error(Meta, File, iolist_to_binary(Message));
compile_error(Meta, File, Message) when is_binary(Message) ->
raise(Meta, File, 'Elixir.CompileError', Message).
compile_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
compile_error(Meta, File, Format, Args) when is_list(Format) ->
compile_error(Meta, File, io_lib:format(Format, Args)).
%% Raised on tokenizing/parsing
-spec parse_error(non_neg_integer() | list(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
parse_error(Meta, File, Error, []) ->
Message = case Error of
@@ -73,14 +60,6 @@ parse_error(Meta, File, Error, Token) ->
Message = <<BinError / binary, BinToken / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message).
%% Raised during compilation
-spec form_error(non_neg_integer() | list(), file:filename(), module(), any()) -> no_return().
form_error(Meta, File, Module, Desc) ->
Message = iolist_to_binary(format_error(Module, Desc)),
raise(Meta, File, 'Elixir.CompileError', Message).
%% Shows a deprecation message
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
@@ -126,14 +105,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, inspect(Module)]),
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, elixir_aliases:inspect(Module)]),
warn(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of
true -> [];
false ->
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
warn(file_format(Line, File, Message))
end;
@@ -158,7 +137,7 @@ handle_file_warning(_, File, {Line,Module,Desc}) ->
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
-spec handle_file_error(file:filename(), {non_neg_integer(), module(), any()}) -> no_return().
-spec handle_file_error(file:filename_all(), {non_neg_integer(), module(), any()}) -> no_return().
handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
Translated = case In of
@@ -170,38 +149,12 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [inspect(M), F, A]),
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc).
%% Assertions
assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
assert_no_function_scope(Meta, Kind, S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
assert_no_match_or_guard_scope(Meta, Kind, S) ->
assert_no_match_scope(Meta, Kind, S),
assert_no_guard_scope(Meta, Kind, S).
assert_no_match_scope(Meta, _Kind, #elixir_scope{context=match} = S) ->
compile_error(Meta, S#elixir_scope.file, "invalid pattern in match clause", []);
assert_no_match_scope(_Meta, _Kind, _S) -> [].
assert_no_guard_scope(Meta, _Kind, #elixir_scope{context=guard} = S) ->
compile_error(Meta, S#elixir_scope.file, "invalid pattern in guard", []);
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
%% Helpers
raise(Meta, File, Kind, Message) when is_list(Meta) ->
+556
View File
@@ -0,0 +1,556 @@
-module(elixir_exp).
-export([expand/2, expand_many/2, expand_args/2, expand_arg/2]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
%% =
expand({ '=', Meta, [Left, Right] }, E) ->
assert_no_guard_scope(Meta, '=', E),
{ ERight, ER } = expand(Right, E),
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
{ { '=', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
%% Literal operators
expand({ '{}', Meta, Args }, E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { '{}', Meta, EArgs }, EA };
expand({ '<<>>', Meta, Args }, E) ->
elixir_bitstring:expand(Meta, Args, E);
%% Other operators
expand({ '__op__', Meta, [_, _] = Args }, E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { '__op__', Meta, EArgs }, EA };
expand({ '__op__', Meta, [_, _, _] = Args }, E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { '__op__', Meta, EArgs }, EA };
expand({ '->', Meta, _Args }, E) ->
compile_error(Meta, E#elixir_env.file, "unhandled operator ->");
%% __block__
expand({ '__block__', _Meta, [] }, E) ->
{ nil, E };
expand({ '__block__', _Meta, [Arg] }, E) ->
expand(Arg, E);
expand({ '__block__', Meta, Args }, E) when is_list(Args) ->
{ EArgs, EA } = expand_many(Args, E),
{ { '__block__', Meta, EArgs }, EA };
%% __aliases__
expand({ '__aliases__', _, _ } = Alias, E) ->
case elixir_aliases:expand(Alias, E#elixir_env.aliases,
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
Receiver when is_atom(Receiver) ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
{ Receiver, E };
Aliases ->
{ EAliases, EA } = expand_args(Aliases, E),
case lists:all(fun is_atom/1, EAliases) of
true ->
Receiver = elixir_aliases:concat(EAliases),
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
{ Receiver, EA };
false ->
{ { { '.', [], [elixir_aliases, concat] }, [], [EAliases] }, EA }
end
end;
%% alias
expand({ alias, Meta, [Ref] }, E) ->
expand({ alias, Meta, [Ref,[]] }, E);
expand({ alias, Meta, [Ref, KV] }, E) ->
assert_no_match_or_guard_scope(Meta, alias, E),
{ ERef, ER } = expand(Ref, E),
{ EKV, ET } = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
if
is_atom(ERef) ->
{ { alias, Meta, [ERef, EKV] },
expand_alias(Meta, true, ERef, EKV, ET) };
true ->
compile_error(Meta, E#elixir_env.file,
"invalid arguments for alias, expected a compile time atom or alias as argument")
end;
expand({ require, Meta, [Ref] }, E) ->
expand({ require, Meta, [Ref, []] }, E);
expand({ require, Meta, [Ref, KV] }, E) ->
assert_no_match_or_guard_scope(Meta, require, E),
{ ERef, ER } = expand(Ref, E),
{ EKV, ET } = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
if
is_atom(ERef) ->
elixir_aliases:ensure_loaded(Meta, ERef, ET),
{ { require, Meta, [ERef, EKV] },
expand_require(Meta, ERef, EKV, ET) };
true ->
compile_error(Meta, E#elixir_env.file,
"invalid arguments for require, expected a compile time atom or alias as argument")
end;
expand({ import, Meta, [Left] }, E) ->
expand({ import, Meta, [Left, []]}, E);
expand({ import, Meta, [Ref, KV] }, E) ->
assert_no_match_or_guard_scope(Meta, import, E),
{ ERef, ER } = expand(Ref, E),
{ EKV, ET } = expand_opts(Meta, import, [only, except, warn], KV, ER),
if
is_atom(ERef) ->
elixir_aliases:ensure_loaded(Meta, ERef, ET),
{ Functions, Macros } = elixir_import:import(Meta, ERef, EKV, ET),
{ { import, Meta, [ERef, EKV] },
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
true ->
compile_error(Meta, E#elixir_env.file, "invalid name for import, expected a compile time atom or alias")
end;
%% Pseudo vars
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
{ E#elixir_env.module, E };
expand({ '__FILE__', Meta, Atom }, E) when is_atom(Atom) ->
elixir_errors:deprecation(Meta, E#elixir_env.file, "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead"),
{ E#elixir_env.file, E };
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
{ filename:dirname(E#elixir_env.file), E };
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
{ Caller, E };
expand({ '__ENV__', Meta, Atom }, E) when is_atom(Atom) ->
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
{ { '{}', [], tuple_to_list(Env) }, E };
expand({ { '.', DotMeta, [{ '__ENV__', Meta, Atom }, Field] }, CallMeta, [] }, E) when is_atom(Atom), is_atom(Field) ->
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
case erlang:function_exported('Elixir.Macro.Env', Field, 1) of
true -> { Env:Field(), E };
false -> { { { '.', DotMeta, [{ '{}', [], tuple_to_list(Env) }, Field] }, CallMeta, [] }, E }
end;
%% Quote
expand({ Unquote, Meta, [_] }, E) when Unquote == unquote; Unquote == unquote_splicing ->
compile_error(Meta, E#elixir_env.file, "~p called outside quote", [Unquote]);
expand({ quote, Meta, [Opts] }, E) when is_list(Opts) ->
case lists:keyfind(do, 1, Opts) of
{ do, Do } ->
expand({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, E);
false ->
compile_error(Meta, E#elixir_env.file, "missing do keyword in quote")
end;
expand({ quote, Meta, [_] }, E) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
Exprs =
case lists:keyfind(do, 1, Do) of
{ do, Expr } -> Expr;
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end,
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
{ EKV, ET } = expand_opts(Meta, quote, ValidOpts, KV, E),
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
{ hygiene, List } when is_list(List) ->
List;
false ->
[]
end,
Context = case lists:keyfind(context, 1, EKV) of
{ context, Atom } when is_atom(Atom) ->
Atom;
{ context, _ } ->
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, expected a compile time atom or an alias");
false ->
case E#elixir_env.module of
nil -> 'Elixir';
Mod -> Mod
end
end,
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
Keep = lists:keyfind(location, 1, EKV) == { location, keep },
Line = proplists:get_value(line, EKV, false),
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, EKV) of
{ bind_quoted, BQ } -> { BQ, false };
false -> { nil, true }
end,
Unquote = case lists:keyfind(unquote, 1, EKV) of
{ unquote, Bool } when is_boolean(Bool) -> Bool;
false -> DefaultUnquote
end,
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ET),
expand(Quoted, ET);
expand({ quote, Meta, [_, _] }, E) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
%% Functions
expand({ '&', _, [Arg] } = Original, E) when is_integer(Arg) ->
{ Original, E };
expand({ '&', Meta, [Arg] }, E) ->
assert_no_match_or_guard_scope(Meta, '&', E),
case elixir_fn:capture(Meta, Arg, E) of
{ local, Fun, Arity } ->
{ { '&', Meta, [{ '/', [], [{ Fun, [], nil }, Arity] }] }, E };
{ expanded, Expr, EE } ->
expand(Expr, EE)
end;
expand({ fn, Meta, Pairs }, E) ->
assert_no_match_or_guard_scope(Meta, fn, E),
elixir_fn:expand(Meta, Pairs, E);
%% Case/Receive/Try
expand({'case', Meta, [Expr, KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'case', E),
{ EExpr, EE } = expand(Expr, E),
{ EClauses, EC } = elixir_exp_clauses:'case'(Meta, KV, EE),
{ { 'case', Meta, [EExpr, EClauses] }, EC };
expand({'receive', Meta, [KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'receive', E),
{ EClauses, EC } = elixir_exp_clauses:'receive'(Meta, KV, E),
{ { 'receive', Meta, [EClauses] }, EC };
expand({'try', Meta, [KV]}, E) ->
assert_no_match_or_guard_scope(Meta, 'try', E),
{ EClauses, EC } = elixir_exp_clauses:'try'(Meta, KV, E),
{ { 'try', Meta, [EClauses] }, EC };
%% Comprehensions
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
expand_comprehension(Meta, Kind, Args, E);
%% Super
expand({ super, Meta, Args }, E) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, super, E),
{ EArgs, EA } = expand_args(Args, E),
{ { super, Meta, EArgs }, EA };
%% Vars
expand({ '^', Meta, [Arg] }, #elixir_env{context=match} = E) ->
case expand(Arg, E) of
{ { Name, _, Kind } = EArg, EA } when is_atom(Name), is_atom(Kind) ->
{ { '^', Meta, [EArg] }, EA };
_ ->
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
compile_error(Meta, E#elixir_env.file, Msg, ['Elixir.Macro':to_string(Arg)])
end;
expand({ '^', Meta, [Arg] }, E) ->
compile_error(Meta, E#elixir_env.file,
"cannot use ^~ts outside of match clauses", ['Elixir.Macro':to_string(Arg)]);
expand({ '_', _, Kind } = Var, E) when is_atom(Kind) ->
{ Var, E };
expand({ Name, Meta, Kind } = Var, #elixir_env{context=match,vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
{ Var, E#elixir_env{vars=ordsets:add_element({ Name, var_kind(Meta, Kind) }, Vars)} };
expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
case lists:member({ Name, var_kind(Meta, Kind) }, Vars) of
true ->
{ Var, E };
false ->
VarMeta = lists:keyfind(var, 1, Meta),
if
VarMeta == { var, true } ->
Extra = case Kind of
nil -> "";
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
end,
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
"variable or be a part of a match", [Name, Extra]);
E#elixir_env.context == guard ->
compile_error(Meta, E#elixir_env.file, "unknown variable ~ts or cannot invoke "
"function ~ts/0 inside guard", [Name, Name]);
true ->
expand({ Name, Meta, [] }, E)
end
end;
%% Local calls
expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
assert_no_ambiguous_op(Atom, Meta, Args, E),
elixir_dispatch:dispatch_import(Meta, Atom, Args, E, fun() ->
expand_local(Meta, Atom, Args, E)
end);
%% Remote calls
expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
{ ELeft, EL } = expand(Left, E),
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(Receiver) ->
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL)
end);
%% Anonymous calls
expand({ { '.', DotMeta, [Expr] }, Meta, Args }, E) when is_list(Args) ->
{ EExpr, EE } = expand(Expr, E),
if
is_atom(EExpr) ->
compile_error(Meta, E#elixir_env.file, "invalid function call :~ts.()", [EExpr]);
true ->
{ EArgs, EA } = expand_args(Args, elixir_env:mergea(E, EE)),
{ { { '.', DotMeta, [EExpr] }, Meta, EArgs }, elixir_env:mergev(EE, EA) }
end;
%% Invalid calls
expand({ _, Meta, Args } = Invalid, E) when is_list(Meta) and is_list(Args) ->
compile_error(Meta, E#elixir_env.file, "invalid call ~ts",
['Elixir.Macro':to_string(Invalid)]);
expand({ _, _, _ } = Tuple, E) ->
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple, [{raw,true}])]);
%% Literals
expand({ Left, Right }, E) ->
{ [ELeft, ERight], EE } = expand_args([Left, Right], E),
{ { ELeft, ERight }, EE };
expand(List, #elixir_env{context=match} = E) when is_list(List) ->
expand_list(List, fun expand/2, E, []);
expand(List, E) when is_list(List) ->
{ EArgs, { EC, EV } } = expand_list(List, fun expand_arg/2, {E, E}, []),
{ EArgs, elixir_env:mergea(EV, EC) };
expand(Function, E) when is_function(Function) ->
case (erlang:fun_info(Function, type) == { type, external }) andalso
(erlang:fun_info(Function, env) == { env, [] }) of
true ->
{ Function, E };
false ->
compile_error([{line,0}], E#elixir_env.file,
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Function)])
end;
expand(Other, E) when is_number(Other); is_atom(Other); is_binary(Other); is_pid(Other) ->
{ Other, E };
expand(Other, E) ->
compile_error([{line,0}], E#elixir_env.file,
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Other)]).
%% Helpers
expand_list([{ '|', Meta, [_, _] = Args }], Fun, Acc, List) ->
{ EArgs, EAcc } = lists:mapfoldl(Fun, Acc, Args),
expand_list([], Fun, EAcc, [{ '|', Meta, EArgs }|List]);
expand_list([H|T], Fun, Acc, List) ->
{ EArg, EAcc } = Fun(H, Acc),
expand_list(T, Fun, EAcc, [EArg|List]);
expand_list([], _Fun, Acc, List) ->
{ lists:reverse(List), Acc }.
expand_many(Args, E) ->
lists:mapfoldl(fun expand/2, E, Args).
%% Variables in arguments are not propagated from one
%% argument to the other. For instance:
%%
%% x = 1
%% foo(x = x + 2, x)
%% x
%%
%% Should be the same as:
%%
%% foo(3, 1)
%% 3
%%
%% However, lexical information is.
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
{ Arg, Acc };
expand_arg(Arg, { Acc1, Acc2 }) ->
{ EArg, EAcc } = expand(Arg, Acc1),
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
expand_args(Args, #elixir_env{context=match} = E) ->
lists:mapfoldl(fun expand/2, E, Args);
expand_args(Args, E) ->
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
{ EArgs, elixir_env:mergea(EV, EC) }.
%% Match/var helpers
var_kind(Meta, Kind) ->
case lists:keyfind(counter, 1, Meta) of
{ counter, Counter } -> Counter;
false -> Kind
end.
%% Locals
assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
case lists:keyfind(ambiguous_op, 1, Meta) of
{ ambiguous_op, Kind } ->
case lists:member({ Name, Kind }, E#elixir_env.vars) of
true ->
compile_error(Meta, E#elixir_env.file, "\"~ts ~ts\" looks like a function call but "
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
[Name, 'Elixir.Macro':to_string(Arg), Name]);
false ->
ok
end;
_ ->
ok
end;
assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
ok.
expand_local(Meta, Name, Args, #elixir_env{local=nil, function=nil} = E) ->
{ EArgs, EA } = expand_args(Args, E),
{ { Name, Meta, EArgs }, EA };
expand_local(Meta, Name, Args, #elixir_env{local=nil, module=Module, function=Function} = E) ->
elixir_locals:record_local({ Name, length(Args) }, Module, Function),
{ EArgs, EA } = expand_args(Args, E),
{ { Name, Meta, EArgs }, EA };
expand_local(Meta, Name, Args, E) ->
expand({ { '.', Meta, [E#elixir_env.local, Name] }, Meta, Args }, E).
%% Remote
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
if
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker);
true -> ok
end,
{ EArgs, EA } = expand_args(Args, E),
{ { { '.', DotMeta, [Receiver, Right] }, Meta, EArgs }, elixir_env:mergev(EL, EA) }.
%% Lexical helpers
expand_opts(Meta, Kind, Allowed, Opts, E) ->
{ EOpts, EE } = expand(Opts, E),
validate_opts(Meta, Kind, Allowed, EOpts, EE),
{ EOpts, EE }.
validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
[begin
compile_error(Meta, E#elixir_env.file,
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
false -> KV
end;
no_alias_opts(KV) -> KV.
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
{ '__aliases__', Meta, ['Elixir',H|T] };
no_alias_expansion(Other) ->
Other.
expand_require(Meta, Ref, KV, E) ->
RE = E#elixir_env{requires=ordsets:add_element(Ref, E#elixir_env.requires)},
expand_alias(Meta, false, Ref, KV, RE).
expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Context} = E) ->
New = expand_as(lists:keyfind(as, 1, KV), Meta, IncludeByDefault, Ref, E),
%% Add the alias to context_modules if defined is true.
%% This is used by defmodule in order to store the defined
%% module in context modules.
NewContext =
case lists:keyfind(defined, 1, Meta) of
{ defined, Mod } when is_atom(Mod) -> [Mod|Context];
false -> Context
end,
{ Aliases, MacroAliases } = elixir_aliases:store(Meta, New, Ref, KV, E#elixir_env.aliases,
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker),
E#elixir_env{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext}.
expand_as({ as, true }, _Meta, _IncludeByDefault, Ref, _E) ->
elixir_aliases:last(Ref);
expand_as({ as, false }, _Meta, _IncludeByDefault, Ref, _E) ->
Ref;
expand_as({ as, Atom }, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
case length(string:tokens(atom_to_list(Atom), ".")) of
1 -> compile_error(Meta, E#elixir_env.file,
"invalid value for keyword :as, expected an alias, got atom: ~ts", [elixir_aliases:inspect(Atom)]);
2 -> Atom;
_ -> compile_error(Meta, E#elixir_env.file,
"invalid value for keyword :as, expected an alias, got nested alias: ~ts", [elixir_aliases:inspect(Atom)])
end;
expand_as(false, _Meta, IncludeByDefault, Ref, _E) ->
if IncludeByDefault -> elixir_aliases:last(Ref);
true -> Ref
end;
expand_as({ as, Other }, Meta, _IncludeByDefault, _Ref, E) ->
compile_error(Meta, E#elixir_env.file,
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
%% Comprehensions
expand_comprehension(Meta, Kind, Args, E) ->
case elixir_utils:split_last(Args) of
{ Cases, [{do,Expr}] } ->
{ ECases, EC } = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
{ EExpr, _ } = expand(Expr, EC),
{ { Kind, Meta, ECases ++ [[{do,EExpr}]] }, E };
_ ->
compile_error(Meta, E#elixir_env.file, "missing do keyword in comprehension ~ts", [Kind])
end.
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
{ ERight, ER } = expand(Right, E),
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
{ { Gen, Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
expand_comprehension_clause(X, E) ->
expand(X, E).
%% Assertions
assert_no_match_or_guard_scope(Meta, Kind, E) ->
assert_no_match_scope(Meta, Kind, E),
assert_no_guard_scope(Meta, Kind, E).
assert_no_match_scope(Meta, _Kind, #elixir_env{context=match,file=File}) ->
compile_error(Meta, File, "invalid pattern in match clause");
assert_no_match_scope(_Meta, _Kind, _E) -> [].
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
compile_error(Meta, File, "invalid pattern in guard");
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
+180
View File
@@ -0,0 +1,180 @@
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_exp_clauses).
-export([match/3, clause/5, 'case'/3, 'receive'/3, 'try'/3, def/5]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
match(Fun, Expr, #elixir_env{context=Context} = E) ->
{ EExpr, EE } = Fun(Expr, E#elixir_env{context=match}),
{ EExpr, EE#elixir_env{context=Context} }.
def(Fun, Args, Guards, Body, E) ->
{ EArgs, EA } = match(Fun, Args, E),
{ EGuards, EG } = guard(Guards, EA#elixir_env{context=guard}),
{ EBody, EB } = elixir_exp:expand(Body, EG#elixir_env{context=E#elixir_env.context}),
{ EArgs, EGuards, EBody, EB }.
clause(Meta, Kind, Fun, { '->', ClauseMeta, [_, _] } = Clause, E) when is_function(Fun, 3) ->
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
clause(_Meta, _Kind, Fun, { '->', Meta, [Left, Right] }, E) ->
{ ELeft, EL } = head(Fun, Left, E),
{ ERight, ER } = elixir_exp:expand(Right, EL),
{ { '->', Meta, [ELeft, ERight] }, ER };
clause(Meta, Kind, _Fun, _, E) ->
compile_error(Meta, E#elixir_env.file, "expected -> clauses in ~ts", [Kind]).
head(Fun, [{ 'when', Meta, [_,_|_] = All }], E) ->
{ Args, Guard } = elixir_utils:split_last(All),
{ EArgs, EA } = match(Fun, Args, E),
{ EGuard, EG } = guard(Guard, EA#elixir_env{context=guard}),
{ [{ 'when', Meta, EArgs ++ [EGuard] }], EG#elixir_env{context=E#elixir_env.context} };
head(Fun, Args, E) ->
match(Fun, Args, E).
guard({ 'when', Meta, [Left, Right] }, E) ->
{ ELeft, EL } = guard(Left, E),
{ ERight, ER } = guard(Right, EL),
{ { 'when', Meta, [ELeft, ERight] }, ER };
guard(Other, E) ->
elixir_exp:expand(Other, E).
%% Case
'case'(Meta, [], E) ->
compile_error(Meta, E#elixir_env.file, "missing do keyword in case");
'case'(Meta, KV, E) when not is_list(KV) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for case");
'case'(Meta, KV, E) ->
lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, E) end, E, KV).
do_case(Meta, { 'do', _ } = Do, Acc, E) ->
expand_with_vars(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
do_case(Meta, { Key, _ }, _Acc, E) ->
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in case", [Key]).
%% Receive
'receive'(Meta, [], E) ->
compile_error(Meta, E#elixir_env.file, "missing do or after keyword in receive");
'receive'(Meta, KV, E) when not is_list(KV) ->
compile_error(Meta, E#elixir_env.file, "invalid arguments for receive");
'receive'(Meta, KV, E) ->
lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, E) end, E, KV).
do_receive(_Meta, { 'do', nil } = Do, Acc, _E) ->
{ Do, Acc };
do_receive(Meta, { 'do', _ } = Do, Acc, E) ->
expand_with_vars(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
do_receive(_Meta, { 'after', [{ '->', Meta, [[Left], Right] }] }, Acc, E) ->
{ ELeft, EL } = elixir_exp:expand(Left, E),
{ ERight, ER } = elixir_exp:expand(Right, EL),
EClause = { 'after', [{ '->', Meta, [[ELeft], ERight] }] },
{ EClause, elixir_env:mergev(ER, Acc) };
do_receive(Meta, { 'after', _ }, _Acc, E) ->
compile_error(Meta, E#elixir_env.file, "expected a single -> clause for after in receive");
do_receive(Meta, { Key, _ }, _Acc, E) ->
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in receive", [Key]).
%% Try
'try'(Meta, [], E) ->
compile_error(Meta, E#elixir_env.file, "missing do keywords in try");
'try'(Meta, KV, E) when not is_list(KV) ->
elixir_errors:compile_error(Meta, E#elixir_env.file, "invalid arguments for try");
'try'(Meta, KV, E) ->
{ lists:map(fun(X) -> do_try(Meta, X, E) end, KV), E }.
do_try(_Meta, { 'do', Expr }, E) ->
{ EExpr, _ } = elixir_exp:expand(Expr, E),
{ 'do', EExpr };
do_try(_Meta, { 'after', Expr }, E) ->
{ EExpr, _ } = elixir_exp:expand(Expr, E),
{ 'after', EExpr };
do_try(Meta, { 'else', _ } = Else, E) ->
expand_without_vars(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
do_try(Meta, { 'catch', _ } = Catch, E) ->
expand_without_vars(Meta, 'try', fun elixir_exp:expand_many/2, Catch, E);
do_try(Meta, { 'rescue', _ } = Rescue, E) ->
expand_without_vars(Meta, 'try', fun expand_rescue/3, Rescue, E);
do_try(Meta, { Key, _ }, E) ->
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in try", [Key]).
expand_rescue(Meta, [Arg], E) ->
case expand_rescue(Arg, E) of
{ EArg, EA } ->
{ [EArg], EA };
false ->
compile_error(Meta, E#elixir_env.file, "invalid rescue clause. The clause should "
"match on an alias, a variable or be in the `var in [alias]` format")
end;
expand_rescue(Meta, _, E) ->
compile_error(Meta, E#elixir_env.file, "expected one arg for rescue clauses (->) in try").
%% rescue var => var in _
expand_rescue({ Name, _, Atom } = Var, E) when is_atom(Name), is_atom(Atom) ->
expand_rescue({ in, [], [Var, { '_', [], E#elixir_env.module }] }, E);
%% rescue var in [Exprs]
expand_rescue({ in, Meta, [Left, Right] }, E) ->
{ ERight, ER } = elixir_exp:expand(Right, E#elixir_env{context=nil}),
{ ELeft, EL } = elixir_exp:expand(Left, ER#elixir_env{context=match}),
case ELeft of
{ Name, _, Atom } when is_atom(Name), is_atom(Atom) ->
case normalize_rescue(ERight) of
false -> false;
Other -> { { in, Meta, [ELeft, Other] }, EL }
end;
_ ->
false
end;
%% rescue Error => _ in [Error]
expand_rescue(Arg, E) ->
expand_rescue({ in, [], [{ '_', [], E#elixir_env.module }, Arg] }, E).
normalize_rescue({ '_', _, Atom } = N) when is_atom(Atom) -> N;
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
normalize_rescue(Other) ->
is_list(Other)
andalso lists:all(fun is_var_or_atom/1, Other)
andalso Other.
is_var_or_atom({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
is_var_or_atom(Atom) when is_atom(Atom) -> true;
is_var_or_atom(_) -> false.
%% Expansion helpers
%% Returns a function that expands arguments
%% considering we have at maximum one entry.
expand_arg(Meta, Kind, Key) ->
fun
([Arg], E) ->
{ EArg, EA } = elixir_exp:expand(Arg, E),
{ [EArg], EA };
(_, E) ->
compile_error(Meta, E#elixir_env.file, "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
end.
%% Expands all -> pairs in a given key keeping the overall vars.
expand_with_vars(Meta, Kind, Fun, { Key, Clauses }, Acc, E) when is_list(Clauses) ->
Transformer = fun(Clause, FunAcc) ->
{ EClause, EC } = clause(Meta, Kind, Fun, Clause, E),
{ EClause, elixir_env:mergev(EC, FunAcc) }
end,
{ EClauses, EAcc } = lists:mapfoldl(Transformer, Acc, Clauses),
{ { Key, EClauses }, EAcc };
expand_with_vars(Meta, Kind, _Fun, { Key, _ }, _Acc, E) ->
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
%% Expands all -> pairs in a given key but do not keep the overall vars.
expand_without_vars(Meta, Kind, Fun, { Key, Clauses }, E) when is_list(Clauses) ->
Transformer = fun(Clause) ->
{ EClause, _ } = clause(Meta, Kind, Fun, Clause, E),
EClause
end,
{ Key, lists:map(Transformer, Clauses) };
expand_without_vars(Meta, Kind, _Fun, { Key, _ }, E) ->
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
+93 -95
View File
@@ -1,13 +1,13 @@
-module(elixir_fn).
-export([fn/3, capture/3]).
-import(elixir_scope, [umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4, compile_error/4]).
-export([translate/3, capture/3, expand/3]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
fn(Meta, Clauses, S) ->
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
translate(Meta, Clauses, S) ->
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
elixir_clauses:assigns_block(?line(CMeta), fun translate_fn_match/2, Args, [Expr], Guards, umergec(S, Acc))
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2, Args, Expr, Guards, Acc),
{ TClause, elixir_scope:mergef(S, TS) }
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
@@ -15,21 +15,32 @@ fn(Meta, Clauses, S) ->
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
{ { 'fun', ?line(Meta), { clauses, TClauses } }, NS };
_ ->
syntax_error(Meta, S#elixir_scope.file,
"cannot mix clauses with different arities in function definition")
compile_error(Meta, S#elixir_scope.file,
"cannot mix clauses with different arities in function definition")
end.
translate_fn_match(Arg, S) ->
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
{ TArg, TS } = elixir_translator:translate_many(Arg, S#elixir_scope{extra=fn_match}),
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, S) when is_atom(F), is_integer(A) ->
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_require(Meta, { Dot, RequireMeta, Args }, S, true);
%% Expansion
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
expand(Meta, Clauses, E) when is_list(Clauses) ->
Transformer = fun(Clause) ->
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_many/2, Clause, E),
EClause
end,
{ { fn, Meta, lists:map(Transformer, Clauses) }, E }.
%% Capture
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, E) when is_atom(F), is_integer(A) ->
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_require(Meta, { Dot, RequireMeta, Args }, E, true);
capture(Meta, { '/', _, [{ F, _, C }, A] }, E) when is_atom(F), is_integer(A), is_atom(C) ->
ImportMeta =
case lists:keyfind(import_fa, 1, Meta) of
{ import_fa, { Receiver, Context } } ->
@@ -40,115 +51,102 @@ capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), i
false -> Meta
end,
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_import(Meta, { F, ImportMeta, Args }, S, true);
capture_import(Meta, { F, ImportMeta, Args }, E, true);
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is_list(Args) ->
capture_require(Meta, Expr, S, is_sequential_and_not_empty(Args));
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, E) when is_atom(Fun), is_list(Args) ->
capture_require(Meta, Expr, E, is_sequential_and_not_empty(Args));
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, S) when is_list(Args) ->
do_capture(Meta, Expr, S, false);
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, E) when is_list(Args) ->
do_capture(Meta, Expr, E, false);
capture(Meta, { '__block__', _, [Expr] }, S) ->
capture(Meta, Expr, S);
capture(Meta, { '__block__', _, [Expr] }, E) ->
capture(Meta, Expr, E);
capture(Meta, { '__block__', _, _ } = Expr, S) ->
capture(Meta, { '__block__', _, _ } = Expr, E) ->
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Expr)]);
capture(Meta, { Atom, _, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
capture_import(Meta, Expr, S, is_sequential_and_not_empty(Args));
capture(Meta, { Atom, _, Args } = Expr, E) when is_atom(Atom), is_list(Args) ->
capture_import(Meta, Expr, E, is_sequential_and_not_empty(Args));
capture(Meta, { Left, Right }, S) ->
capture(Meta, { '{}', Meta, [Left, Right] }, S);
capture(Meta, { Left, Right }, E) ->
capture(Meta, { '{}', Meta, [Left, Right] }, E);
capture(Meta, List, S) when is_list(List) ->
capture(Meta, { '[]', Meta, List }, S);
capture(Meta, List, E) when is_list(List) ->
do_capture(Meta, List, E, is_sequential_and_not_empty(List));
capture(Meta, Arg, S) when is_integer(Arg) ->
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
capture(Meta, Arg, E) ->
invalid_capture(Meta, Arg, E).
capture(Meta, Arg, S) ->
invalid_capture(Meta, Arg, S).
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, E, Sequential) ->
Res = Sequential andalso
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), E),
handle_capture(Res, Meta, Expr, E, Sequential).
%% Helpers
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, E, Sequential) ->
{ Mod, EE } = elixir_exp:expand(Left, E),
Res = Sequential andalso is_atom(Mod) andalso
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), EE),
handle_capture(Res, Meta, Expr, EE, Sequential).
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, S, Sequential) ->
case Sequential andalso
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
false -> do_capture(Meta, Expr, S, Sequential);
Else -> Else
handle_capture({ local, Fun, Arity }, _Meta, _Expr, _E, _Sequential) ->
{ local, Fun, Arity };
handle_capture({ remote, Receiver, Fun, Arity }, Meta, _Expr, E, _Sequential) ->
Tree = { { '.', [], [erlang, make_fun] }, Meta, [Receiver, Fun, Arity] },
{ expanded, Tree, E };
handle_capture(false, Meta, Expr, E, Sequential) ->
do_capture(Meta, Expr, E, Sequential).
do_capture(Meta, Expr, E, Sequential) ->
case do_escape(Expr, elixir_counter:next(), E, []) of
{ _, [] } when not Sequential ->
invalid_capture(Meta, Expr, E);
{ EExpr, EDict } ->
EVars = validate(Meta, EDict, 1, E),
Fn = { fn, Meta, [{ '->', Meta, [EVars, EExpr]}]},
{ expanded, Fn, E }
end.
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, S, Sequential) ->
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
case Sequential andalso is_atom(Mod) andalso
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), SE) of
false -> do_capture(Meta, Expr, S, Sequential);
Else -> Else
end.
do_capture(Meta, Expr, S, Sequential) ->
case do_escape(Expr, S, []) of
{ _, _, [] } when not Sequential ->
invalid_capture(Meta, Expr, S);
{ TExpr, TS, TDict } ->
TVars = validate(Meta, TDict, 1, S),
fn(Meta, [{ TVars, Meta, TExpr }], TS)
end.
invalid_capture(Meta, Arg, S) ->
invalid_capture(Meta, Arg, E) ->
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]).
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]).
validate(Meta, [{ Pos, Var }|T], Pos, S) ->
[Var|validate(Meta, T, Pos + 1, S)];
validate(Meta, [{ Pos, Var }|T], Pos, E) ->
[Var|validate(Meta, T, Pos + 1, E)];
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
compile_error(Meta, S#elixir_scope.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
validate(Meta, [{ Pos, _ }|_], Expected, E) ->
compile_error(Meta, E#elixir_env.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
validate(_Meta, [], _Pos, _S) ->
validate(_Meta, [], _Pos, _E) ->
[].
do_escape({ '&', Meta, [Pos] }, S, Dict) when is_integer(Pos), Pos > 0 ->
case orddict:find(Pos, Dict) of
{ ok, Var } ->
{ Var, S, Dict };
error ->
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
{ Var, SC, orddict:store(Pos, Var, Dict) }
end;
do_escape({ '&', _, [Pos] }, Counter, _E, Dict) when is_integer(Pos), Pos > 0 ->
Var = { list_to_atom([$x, $@+Pos]), [{ counter, Counter }], elixir_fn },
{ Var, orddict:store(Pos, Var, Dict) };
do_escape({ '&', Meta, [Pos] }, S, _Dict) when is_integer(Pos) ->
compile_error(Meta, S#elixir_scope.file, "capture &~B is not allowed", [Pos]);
do_escape({ '&', Meta, [Pos] }, _Counter, E, _Dict) when is_integer(Pos) ->
compile_error(Meta, E#elixir_env.file, "capture &~B is not allowed", [Pos]);
do_escape({ '&', Meta, _ } = Arg, S, _Dict) ->
do_escape({ '&', Meta, _ } = Arg, _Counter, E, _Dict) ->
Message = "nested captures via & are not allowed: ~ts",
compile_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]);
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]);
do_escape({ Left, Meta, Right }, S0, Dict0) ->
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
{ { TLeft, Meta, TRight }, S2, Dict2 };
do_escape({ Left, Meta, Right }, Counter, E, Dict0) ->
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
{ { TLeft, Meta, TRight }, Dict2 };
do_escape({ Left, Right }, S0, Dict0) ->
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
{ { TLeft, TRight }, S2, Dict2 };
do_escape({ Left, Right }, Counter, E, Dict0) ->
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
{ { TLeft, TRight }, Dict2 };
do_escape(List, S, Dict) when is_list(List) ->
do_escape_list(List, S, Dict, []);
do_escape(List, Counter, E, Dict) when is_list(List) ->
lists:mapfoldl(fun(X, Acc) -> do_escape(X, Counter, E, Acc) end, Dict, List);
do_escape(Other, S, Dict) ->
{ Other, S, Dict }.
do_escape_list([H|T], S, Dict, Acc) ->
{ TH, TS, TDict } = do_escape(H, S, Dict),
do_escape_list(T, TS, TDict, [TH|Acc]);
do_escape_list([], S, Dict, Acc) ->
{ lists:reverse(Acc), S, Dict }.
do_escape(Other, _Counter, _E, Dict) ->
{ Other, Dict }.
is_sequential_and_not_empty([]) -> false;
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
+34 -54
View File
@@ -7,56 +7,55 @@
%% IMPORT
import(Meta, Ref, Opts, S) ->
SI =
import(Meta, Ref, Opts, E) ->
Res =
case keyfind(only, Opts) of
{ only, functions } ->
import_functions(Meta, Ref, Opts, S);
{ import_functions(Meta, Ref, Opts, E),
E#elixir_env.macros };
{ only, macros } ->
import_macros(true, Meta, Ref, Opts, S);
{ E#elixir_env.functions,
import_macros(true, Meta, Ref, Opts, E) };
{ only, List } when is_list(List) ->
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S));
{ import_functions(Meta, Ref, Opts, E),
import_macros(false, Meta, Ref, Opts, E) };
false ->
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S))
{ import_functions(Meta, Ref, Opts, E),
import_macros(false, Meta, Ref, Opts, E) }
end,
record_warn(Meta, Ref, Opts, SI),
SI.
import_functions(Meta, Ref, Opts, S) ->
{ Functions, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
S#elixir_scope.macro_functions, fun() -> get_functions(Ref) end, S),
S#elixir_scope{functions=Functions, macro_functions=Temp}.
record_warn(Meta, Ref, Opts, E),
Res.
import_macros(Force, Meta, Ref, Opts, S) ->
Existing = fun() ->
import_functions(Meta, Ref, Opts, E) ->
calculate(Meta, Ref, Opts, E#elixir_env.functions, E, fun() -> get_functions(Ref) end).
import_macros(Force, Meta, Ref, Opts, E) ->
calculate(Meta, Ref, Opts, E#elixir_env.macros, E, fun() ->
case Force of
true -> get_macros(Meta, Ref, S);
true -> get_macros(Meta, Ref, E);
false -> get_optional_macros(Ref)
end
end,
{ Macros, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.macros,
S#elixir_scope.macro_macros, Existing, S),
S#elixir_scope{macros=Macros, macro_macros=Temp}.
end).
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
record_warn(Meta, Ref, Opts, E) ->
Warn =
case keyfind(warn, Opts) of
{ warn, false } -> false;
{ warn, true } -> true;
false -> not lists:keymember(context, 1, Meta)
end,
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
elixir_lexical:record_import(Ref, ?line(Meta), Warn, E#elixir_env.lexical_tracker).
%% Calculates the imports based on only and except
calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
calculate(Meta, Key, Opts, Old, E, Existing) ->
New = case keyfind(only, Opts) of
{ only, Only } when is_list(Only) ->
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple);
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple);
_ ->
intersection(Only, Existing())
end;
@@ -77,25 +76,10 @@ calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
Final = remove_internals(Set),
case Final of
[] ->
{ keydelete(Key, Old),
if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
[] -> keydelete(Key, Old);
_ ->
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
{ [{ Key, Final }|keydelete(Key, Old)],
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
end.
if_quoted(Meta, Temp, Callback) ->
case lists:keyfind(context, 1, Meta) of
{ context, Context } ->
Current = case orddict:find(Context, Temp) of
{ ok, Value } -> Value;
error -> []
end,
orddict:store(Context, Callback(Current), Temp);
_ ->
Temp
ensure_no_special_form_conflict(Meta, E#elixir_env.file, Key, Final),
[{ Key, Final }|keydelete(Key, Old)]
end.
%% Retrieve functions and macros from modules
@@ -114,13 +98,13 @@ get_functions(Module) ->
error:undef -> Module:module_info(exports)
end.
get_macros(Meta, Module, S) ->
get_macros(Meta, Module, E) ->
try
Module:'__info__'(macros)
catch
error:undef ->
Tuple = { no_macros, Module },
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple)
end.
get_optional_macros(Module) ->
@@ -151,14 +135,14 @@ ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
format_error({invalid_import,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
[elixir_errors:inspect(Receiver), Name, Arity]);
[elixir_aliases:inspect(Receiver), Name, Arity]);
format_error({special_form_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
[elixir_errors:inspect(Receiver), Name, Arity]);
[elixir_aliases:inspect(Receiver), Name, Arity]);
format_error({ no_macros, Module }) ->
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]).
%% LIST HELPERS
@@ -197,12 +181,10 @@ special_form('^',1) -> true;
special_form('=',2) -> true;
special_form('__op__',2) -> true;
special_form('__op__',3) -> true;
special_form('__scope__',2) -> true;
special_form('__block__',_) -> true;
special_form('->',_) -> true;
special_form('<<>>',_) -> true;
special_form('{}',_) -> true;
special_form('[]',_) -> true;
special_form('alias',1) -> true;
special_form('alias',2) -> true;
special_form('require',1) -> true;
@@ -212,7 +194,6 @@ special_form('import',2) -> true;
special_form('__ENV__',0) -> true;
special_form('__CALLER__',0) -> true;
special_form('__MODULE__',0) -> true;
special_form('__FILE__',0) -> true;
special_form('__DIR__',0) -> true;
special_form('__aliases__',_) -> true;
special_form('quote',1) -> true;
@@ -221,10 +202,9 @@ special_form('unquote',1) -> true;
special_form('unquote_splicing',1) -> true;
special_form('fn',_) -> true;
special_form('super',_) -> true;
special_form('super?',0) -> true;
special_form('bc',_) -> true;
special_form('lc',_) -> true;
special_form('var!',1) -> true;
special_form('var!',2) -> true;
special_form('alias!',1) -> true;
special_form('case',2) -> true;
special_form('try',2) -> true;
special_form('receive',1) -> true;
special_form(_, _) -> false.
+60 -60
View File
@@ -6,62 +6,70 @@ unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
%% Extract string interpolations
extract(Line, File, Interpol, String, Last) ->
extract(Line, File, Interpol, String, [], [], [], Last).
extract(Line, Raw, Interpol, String, Last) ->
%% Ignore whatever is in the scope and enable terminator checking.
Scope = Raw#elixir_tokenizer{terminators=[], check_terminators=true},
extract(Line, Scope, Interpol, String, [], 0, [], Last).
extract(Line, File, _Interpol, [], Buffer, [], Output, []) ->
finish_extraction(Line, File, Buffer, Output, []);
%% Terminators
extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
finish_extraction(Line, Buffer, Output, []);
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
finish_extraction(Line, File, Buffer, Output, Remaining);
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, _File, _Interpol, End, _Buffer, _Search, _Output, Last) when End == [Last]; End == [] ->
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, Output, Last) ->
finish_extraction(Line, Buffer, Output, Remaining);
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, File, Interpol, [$\\, $#, ${|Rest], Buffer, [], Output, Last) ->
extract(Line, File, Interpol, Rest, [${,$#|Buffer], [], Output, Last);
%% Going through the string
extract(Line, File, Interpol, [$\\,Char|Rest], Buffer, [], Output, Last) ->
extract(Line, File, Interpol, Rest, [Char,$\\|Buffer], [], Output, Last);
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
extract(Line, File, true, [$#, ${|Rest], Buffer, [], Output, Last) ->
NewOutput = build_interpol(s, Line, File, Buffer, Output),
extract(Line, File, true, Rest, [], [$}], NewOutput, Last);
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Search, Output, Last);
extract(Line, File, true, [$}|Rest], Buffer, [$}], Output, Last) ->
NewOutput = build_interpol(i, Line, File, Buffer, Output),
extract(Line, File, true, Rest, [], [], NewOutput, Last);
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Search, Output, Last);
%% Check for available separators inside interpolation
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
Output1 = build_string(Line, Buffer, Output),
extract(Line, File, Interpol, [C|Rest], Buffer, [C|Search], Output, Last) when C == $); C == $]; C == $}; C == $>; C == $"; C == $' ->
extract(Line, File, Interpol, Rest, [C|Buffer], Search, Output, Last);
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
{ error, { EndLine, _, "}" }, [$}|NewRest], Tokens } ->
Output2 = build_interpol(Line, Tokens, Output1),
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
{ error, Reason, _, _ } ->
{ error, Reason };
{ ok, _EndLine, _ } ->
{ error, { string, Line, "missing interpolation terminator: }", [] } }
end;
extract(Line, File, Interpol, [C|Rest], Buffer, [_|_] = Search, Output, Last) when C == $"; C == $' ->
extract(Line, File, Interpol, Rest, [C|Buffer], [C|Search], Output, Last);
%% Matching () [] {} <> inside sigils
extract(Line, File, Interpol, [$<|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [$<|Buffer], [$>|Search], Output, Last);
extract(Line, Scope, Interpol, [$(|Rest], Buffer, Search, Output, $)) ->
extract(Line, Scope, Interpol, Rest, [$(|Buffer], Search + 1, Output, $));
extract(Line, File, Interpol, [${|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [${|Buffer], [$}|Search], Output, Last);
extract(Line, Scope, Interpol, [$[|Rest], Buffer, Search, Output, $]) ->
extract(Line, Scope, Interpol, Rest, [$[|Buffer], Search + 1, Output, $]);
extract(Line, File, Interpol, [$[|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [$[|Buffer], [$]|Search], Output, Last);
extract(Line, Scope, Interpol, [${|Rest], Buffer, Search, Output, $}) ->
extract(Line, Scope, Interpol, Rest, [${|Buffer], Search + 1, Output, $});
extract(Line, File, Interpol, [$(|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [$(|Buffer], [$)|Search], Output, Last);
extract(Line, Scope, Interpol, [$<|Rest], Buffer, Search, Output, $>) ->
extract(Line, Scope, Interpol, Rest, [$<|Buffer], Search + 1, Output, $>);
%% Else
extract(Line, Scope, Interpol, [Last|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Last|Buffer], Search - 1, Output, Last);
extract(Line, File, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [Char|Buffer], Search, Output, Last).
%% Catch all clause
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Search, Output, Last).
%% Unescape a series of tokens as returned by extract.
@@ -130,7 +138,13 @@ unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
Codepoint = list_to_integer(List, Base),
unescape_chars(Rest, Map, Octal, Hex, <<Acc/binary, Codepoint/utf8>>).
try <<Acc/binary, Codepoint/utf8>> of
Binary -> unescape_chars(Rest, Map, Octal, Hex, Binary)
catch
error:badarg ->
Msg = <<"invalid or reserved unicode codepoint ", (integer_to_binary(Codepoint))/binary>>,
error('Elixir.ArgumentError':exception([{message,Msg}]))
end.
% Unescape Helpers
@@ -148,30 +162,16 @@ unescape_map(E) -> E.
% Extract Helpers
finish_extraction(Line, File, Buffer, Output, Remaining) ->
case build_interpol(s, Line, File, Buffer, Output) of
finish_extraction(Line, Buffer, Output, Remaining) ->
case build_string(Line, Buffer, Output) of
[] -> Final = [<<>>];
Final -> []
end,
{ Line, lists:reverse(Final), Remaining }.
build_interpol(_Kind, _Line, _File, [], Output) ->
Output;
build_string(_Line, [], Output) -> Output;
build_string(_Line, Buffer, Output) ->
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output].
build_interpol(s, _Line, _File, Buffer, Output) ->
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output];
build_interpol(i, Line, File, Buffer, Output) ->
[wrap_interpol(Line, forms(lists:reverse(Buffer), Line, File))| Output].
wrap_interpol(Line, Form) ->
Meta = [{line,Line}],
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
forms(String, StartLine, File) ->
case elixir_translator:forms(String, StartLine, File, []) of
{ ok, [] } -> nil;
{ ok, [Forms] } when not is_list(Forms) -> Forms;
{ ok, Forms } -> { '__block__', [{line,StartLine}], Forms };
{ error, Tuple } -> throw({ interpolation_error, Tuple })
end.
build_interpol(Line, Buffer, Output) ->
[{ Line, lists:reverse(Buffer) }|Output].
+79
View File
@@ -0,0 +1,79 @@
%% Module responsible for tracking lexical information.
-module(elixir_lexical).
-export([run/2,
record_alias/4, record_alias/2,
record_import/4, record_import/2,
record_remote/2, format_error/1
]).
-include("elixir.hrl").
-define(tracker, 'Elixir.Kernel.LexicalTracker').
run(File, Callback) ->
case code:is_loaded(?tracker) of
{ file, _ } ->
Pid = ?tracker:start_link(),
try
Callback(Pid)
after
warn_unused_aliases(File, Pid),
warn_unused_imports(File, Pid),
unlink(Pid), ?tracker:stop(Pid)
end;
false ->
Callback(nil)
end.
%% RECORD
record_alias(Module, Line, Warn, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:add_alias(Pid, Module, Line, Warn),
true
end).
record_import(Module, Line, Warn, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:add_import(Pid, Module, Line, Warn),
true
end).
record_alias(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:alias_dispatch(Pid, Module),
true
end).
record_import(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:import_dispatch(Pid, Module),
true
end).
record_remote(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:remote_dispatch(Pid, Module),
true
end).
%% HELPERS
if_tracker(nil, _Callback) -> false;
if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid).
%% ERROR HANDLING
warn_unused_imports(File, Pid) ->
[ begin
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
end || { M, L } <- ?tracker:collect_unused_imports(Pid)].
warn_unused_aliases(File, Pid) ->
[ begin
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_alias, M } })
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
format_error({unused_alias, Module}) ->
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
format_error({unused_import, Module}) ->
io_lib:format("unused import ~ts", [elixir_aliases:inspect(Module)]).
-214
View File
@@ -1,214 +0,0 @@
%% Handle translation of Elixir literals to Erlang AST.
-module(elixir_literal).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
case S#elixir_scope.context of
match ->
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
_ ->
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
{ TArgs, umergec(SV, SC) }
end;
Else -> Else
end;
translate({ '{}', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S) of
error ->
{ TArgs, SE } = translate_args(Args, S),
{ { tuple, ?line(Meta), TArgs }, SE };
Else -> Else
end;
translate({ '[]', _Meta, [] }, S) ->
{ { nil, 0 }, S };
translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
[RTail|RArgs] = lists:reverse(Args),
case RTail of
{'|',_,[Left,Right]} ->
RExprs = [Left|RArgs],
TailFun = fun(ST) -> translate_each(Right, ST) end;
_ ->
RExprs = [RTail|RArgs],
TailFun = fun(ST) -> { { nil, ?line(Meta) }, ST } end
end,
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
{ Tail, ST } = TailFun(SE),
{ elixir_utils:list_to_cons(?line(Meta), Exprs, Tail), ST };
Else -> Else
end;
translate({ Left, Right }, S) ->
translate({ '{}', [], [Left, Right]}, S);
translate(Args, S) when is_list(Args) ->
translate({ '[]', [], Args }, S);
translate(Tuple, S) when is_tuple(Tuple) ->
elixir_errors:compile_error(0, S#elixir_scope.file,
"tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple)]);
translate(Other, S) ->
{ elixir_utils:elixir_to_erl(0, Other, S), S }.
%% Helpers
% Build a bitstring taking into accounts the following types:
%
% * If a bitstring or a list is given, we just append its items
% * If '::' is given, extract the bitstring information
% * All the other types are simply translated and handled with Erlang's default
%
build_bitstr(Fun, Exprs, Meta, S) ->
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{ Acc, S };
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
%% Variables defined outside the binary can be accounted
%% on subparts, however we can't assign new variables.
case S of
{ ES, _ } -> []; %% translate_arg, no assigns
_ -> ES = S#elixir_scope{context=nil} %% translate_each, revert assigns
end,
{ Size, Types } = extract_bit_values(Meta, V, ES),
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
AllowString = types_allow_string(Types),
AllowSplice = types_allow_splice(Types),
AllowAny = (AllowString orelse AllowSplice) andalso (Size == default),
case AllowAny andalso Expr of
{ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] } when AllowString ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), { string, 0, String }, Size, Types }|Acc]);
{ bin, _, Elements } when AllowSplice ->
build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
{ cons, _, _, _ } = Cons ->
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
{ nil, _ } ->
build_bitstr_each(Fun, T, Meta, NS, Acc);
_ ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
end.
rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
types_allow_string([End|T]) when End == little; End == big -> types_allow_string(T);
types_allow_string([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_allow_string(T);
types_allow_string([]) -> true;
types_allow_string(_) -> false.
types_allow_splice(default) -> true;
types_allow_splice([bytes]) -> true;
types_allow_splice([binary]) -> true;
types_allow_splice([bits]) -> true;
types_allow_splice([bitstring]) -> true;
types_allow_splice(_) -> false.
%% Extra bitstring specifiers
extract_bit_values(Meta, V, S) when is_list(V) ->
extract_bit_values(Meta, V, default, [], S);
extract_bit_values(Meta, V, S) ->
extract_bit_values(Meta, [V], S).
extract_bit_values(Meta, [{ Value, _Meta, Atom }|T] = All, Size, Types, S)
when is_atom(Value) andalso (is_atom(Atom) orelse Atom == []) ->
case extract_bit_type(Value, Types) of
{ error, unknown } ->
handle_unknown_specifier(Meta, All, Size, Types, S);
NewTypes when is_list(NewTypes) ->
extract_bit_values(Meta, T, Size, NewTypes, S)
end;
extract_bit_values(Meta, [{ Value, CallMeta, [_] = Args }|T] = All, Size, Types, S) when is_atom(Value) ->
{ TArgs, _ } = elixir_translator:translate_args(Args, S),
case extract_bit_type_or_size(Value, TArgs, Size, Types) of
{ error, unknown } ->
handle_unknown_specifier(Meta, All, Size, Types, S);
{ error, Msg } ->
elixir_errors:syntax_error(CallMeta, S#elixir_scope.file, Msg);
{ NewSize, NewTypes } ->
extract_bit_values(Meta, T, NewSize, NewTypes, S)
end;
extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) andalso Size >= 0 ->
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
extract_bit_values(Meta, [{ '|', _, [Left, Right] }], Size, Types, S) ->
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, ES);
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
handle_unknown_specifier(Meta, All, Size, Types, S);
extract_bit_values(_Meta, [], Size, [], _S) ->
{ Size, default };
extract_bit_values(_Meta, [], Size, Types, _S) ->
{ Size, lists:reverse(Types) }.
extract_bit_type(Value, Types) when
Value == binary; Value == integer; Value == float; Value == bitstring;
Value == bytes; Value == bits;
Value == utf8; Value == utf16; Value == utf32;
Value == signed; Value == unsigned;
Value == big; Value == little; Value == native ->
[Value|Types];
extract_bit_type(_Value, _Types) ->
{ error, unknown }.
extract_bit_type_or_size(size, [{ Kind, _, _ } = Arg], default, Types) when Kind == var; Kind == integer ->
{ Arg, Types };
extract_bit_type_or_size(size, _Args, default, _Types) ->
{ error, "size in bitstring expects an integer or a variable as argument" };
extract_bit_type_or_size(size, _Args, _Other, _Types) ->
{ error, "duplicated size definition for bitstring" };
extract_bit_type_or_size(unit, [{ integer, _, Arg }], Size, Types) ->
{ Size, [{ unit, Arg }|Types] };
extract_bit_type_or_size(unit, _Args, _Other, _Types) ->
{ error, "unit in bitstring expects an integer as argument" };
extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
{ error, unknown }.
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
case 'Elixir.Macro':expand_all(H, elixir_scope:to_ex_env({ ?line(Meta), S }), S) of
{ H, ES } ->
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(H)]);
{ E, ES } ->
extract_bit_values(Meta, join_expansion(E,T), Size, Types, ES)
end.
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
join_expansion(Expanded, Tail) -> [Expanded|Tail].
+184
View File
@@ -0,0 +1,184 @@
%% Module responsible for tracking invocations of module calls.
-module(elixir_locals).
-export([
setup/1, cleanup/1, cache_env/1, get_cached_env/1,
record_local/2, record_local/3, record_import/4,
record_definition/3, record_defaults/4,
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1
]).
-export([macro_for/3, function_for/3]).
-include("elixir.hrl").
-define(attr, '__locals_tracker').
-define(tracker, 'Elixir.Module.LocalsTracker').
macro_for(nil, _Name, _Arity) -> false;
macro_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
try elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, Kind, Line, _, _, _, _ }, [_|_] = Clauses }
when Kind == defmacro; Kind == defmacrop ->
fun() -> get_function(Line, Module, Clauses) end;
_ ->
false
catch
error:badarg -> false
end.
function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
%% There is no need to record such calls
%% since they come from funtions that were
%% already analyzed at compilation time.
get_function(Line, Module, Clauses);
_ ->
[_|T] = erlang:get_stacktrace(),
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
end.
get_function(Line, Module, Clauses) ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result.
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
Remote = { remote, Line,
{ atom, Line, ?MODULE },
{ atom, Line, function_for }
},
%% 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) ->
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
rewrite_clause(List, Module) when is_list(List) ->
[rewrite_clause(Item, Module) || Item <- List];
rewrite_clause(Else, _) -> Else.
%% TRACKING
setup(Module) ->
case code:is_loaded(?tracker) of
{ file, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
false -> ok
end.
cleanup(Module) ->
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid) end).
record_local(Tuple, Module) when is_atom(Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_local(Pid, Tuple),
true
end).
record_local(Tuple, _Module, Function)
when Function == nil; Function == Tuple -> false;
record_local(Tuple, Module, Function) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_local(Pid, Function, Tuple),
true
end).
record_import(_Tuple, Receiver, Module, _Function)
when Module == nil; Module == Receiver -> false;
record_import(Tuple, Receiver, Module, Function) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_import(Pid, Function, Receiver, Tuple),
true
end).
record_definition(Tuple, Kind, Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_definition(Pid, Kind, Tuple),
true
end).
record_defaults(_Tuple, _Kind, _Module, 0) ->
true;
record_defaults(Tuple, Kind, Module, Defaults) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
true
end).
if_tracker(Module, Callback) ->
try ets:lookup_element(Module, ?attr, 2) of
Pid -> Callback(Pid)
catch
error:badarg -> false
end.
%% CACHING
cache_env(#elixir_env{module=Module} = RE) ->
E = RE#elixir_env{line=nil,vars=[]},
try ets:lookup_element(Module, ?attr, 2) of
Pid ->
{ Pid, ?tracker:cache_env(Pid, E) }
catch
error:badarg ->
{ Escaped, _ } = elixir_quote:escape(E, false),
Escaped
end;
cache_env(ExEnv) ->
cache_env(elixir_env:ex_to_env(ExEnv)).
get_cached_env({Pid,Ref}) -> ?tracker:get_cached_env(Pid, Ref);
get_cached_env(Env) -> Env.
%% ERROR HANDLING
ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
if_tracker(Module, fun(Pid) ->
[ begin
elixir_errors:form_error(Meta, File, ?MODULE, { function_conflict, Error })
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
end),
ok.
warn_unused_local(File, Module, Private) ->
if_tracker(Module, fun(Pid) ->
Args = [ { Fun, Kind, Defaults } ||
{ Fun, Kind, _Line, true, Defaults } <- Private],
Unused = ?tracker:collect_unused_locals(Pid, Args),
[ begin
{ _, _, Line, _, _ } = lists:keyfind(element(2, Error), 1, Private),
elixir_errors:handle_file_warning(File, { Line, ?MODULE, Error })
end || Error <- Unused ]
end).
format_error({function_conflict,{Receivers, Name, Arity}}) ->
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
[elixir_aliases:inspect(hd(Receivers)), Name, Arity]);
format_error({unused_args,{Name, Arity}}) ->
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
format_error({unused_args,{Name, Arity},1}) ->
io_lib:format("the first default argument in ~ts/~B is never used", [Name, Arity]);
format_error({unused_args,{Name, Arity},Count}) ->
io_lib:format("the first ~B default arguments in ~ts/~B are never used", [Count, Name, Arity]);
format_error({unused_def,{Name, Arity},defp}) ->
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
format_error({unused_def,{Name, Arity},defmacrop}) ->
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
-324
View File
@@ -1,324 +0,0 @@
%% Those macros behave like they belong to Kernel,
%% but do not since they need to be implemented in Erlang.
-module(elixir_macros).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
-import(elixir_scope, [umergec/2, umergea/2]).
-import(elixir_errors, [compile_error/3, syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
-include("elixir.hrl").
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
%% Operators
translate({ '+', _Meta, [Expr] }, S) when is_number(Expr) ->
translate_each(Expr, S);
translate({ '-', _Meta, [Expr] }, S) when is_number(Expr) ->
translate_each(-1 * Expr, S);
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
Op == '<-' orelse Op == '--' ->
assert_no_match_or_guard_scope(Meta, Op, S),
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
Op == '+' orelse Op == '-' orelse Op == '*' orelse Op == '/' orelse
Op == '++' orelse Op == 'not' orelse Op == 'and' orelse Op == 'or' orelse
Op == 'xor' orelse Op == '<' orelse Op == '>' orelse Op == '<=' orelse
Op == '>=' orelse Op == '==' orelse Op == '!=' orelse Op == '===' orelse
Op == '!==' ->
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
translate({ '!', Meta, [{ '!', _, [Expr] }] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
elixir_utils:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
translate({ '!', Meta, [Expr] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
elixir_utils:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
{ _, TExpr, TS } = translate_in(Meta, Left, Right, S),
{ TExpr, TS };
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
{ TVar, TExpr, TS } = translate_in(Meta, Left, Right, S),
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
%% @
translate({'@', Meta, [{ Name, _, [Arg] }]}, S) when Name == typep; Name == type; Name == spec; Name == callback; Name == opaque ->
case elixir_compiler:get_opt(internal) of
true -> { { nil, ?line(Meta) }, S };
false ->
Call = { { '.', Meta, ['Elixir.Kernel.Typespec', spec_to_macro(Name)] }, Meta, [Arg] },
translate_each(Call, S)
end;
translate({'@', Meta, [{ Name, _, Args }]}, S) ->
assert_module_scope(Meta, '@', S),
case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
true ->
{ { nil, ?line(Meta) }, S };
_ ->
case Args of
[Arg] ->
case S#elixir_scope.function of
nil ->
translate_each({
{ '.', Meta, ['Elixir.Module', put_attribute] },
Meta,
[ { '__MODULE__', Meta, false }, Name, Arg ]
}, S);
_ ->
syntax_error(Meta, S#elixir_scope.file,
"cannot dynamically set attribute @~ts inside a function", [Name])
end;
_ when is_atom(Args) or (Args == []) ->
case S#elixir_scope.function of
nil ->
translate_each({
{ '.', Meta, ['Elixir.Module', get_attribute] },
Meta,
[ { '__MODULE__', Meta, false }, Name, true ]
}, S);
_ ->
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
{ elixir_utils:elixir_to_erl(?line(Meta), Contents, S), S }
end;
_ ->
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
end
end;
%% Case
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
assert_no_match_or_guard_scope(Meta, 'case', S),
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
{ TExpr, NS } = translate_each(Expr, S),
RClauses = case elixir_utils:returns_boolean(TExpr) of
true -> rewrite_case_clauses(Clauses);
false -> Clauses
end,
{ TClauses, TS } = elixir_clauses:match(Meta, RClauses, NS),
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
%% Try
translate({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
assert_no_match_or_guard_scope(Meta, 'try', S),
Do = proplists:get_value('do', Clauses, nil),
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
After = proplists:get_value('after', Clauses, nil),
{ TAfter, SA } = elixir_translator:translate_each(After, umergea(S, SC)),
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
%% Receive
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
assert_no_match_or_guard_scope(Meta, 'receive', S),
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
case lists:keyfind('after', 1, KV) of
false ->
{ TClauses, SC } = elixir_clauses:match(Meta, Do, S),
{ { 'receive', ?line(Meta), TClauses }, SC };
_ ->
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
{ _, _, [FExpr], _, FAfter } = TAfter,
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
end;
%% Definitions
translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
{ TRef, _ } = translate_each(Ref, S),
Block = case lists:keyfind(do, 1, KV) of
{ do, DoValue } -> DoValue;
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
end,
{ FRef, FS } = case TRef of
{ atom, _, Module } ->
FullModule = expand_module(Ref, Module, S),
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
false -> S
end,
{
{ atom, Meta, FullModule },
RS#elixir_scope{context_modules=[FullModule|S#elixir_scope.context_modules]}
};
_ ->
{ TRef, S }
end,
MS = FS#elixir_scope{local=nil},
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
translate({Kind, Meta, [Call]}, S) when ?defs(Kind) ->
translate({Kind, Meta, [Call, nil]}, S);
translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TCall, QC, SC } = elixir_quote:erl_escape(Call, true, S),
{ TExpr, QE, SE } = elixir_quote:erl_escape(Expr, true, SC),
CheckClauses = (not lists:keymember(context, 1, Meta)) andalso
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
{ TLeft, SL } = translate_each(Left, S),
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR);
translate({ apply, Meta, Args }, S) ->
{ TArgs, NS } = translate_args(Args, S),
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
translate({ Name, Meta, Args }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
%% Helpers
translate_in(Meta, Left, Right, S) ->
Line = ?line(Meta),
{ TLeft, SL } = case Left of
{ '_', _, Atom } when is_atom(Atom) ->
elixir_scope:build_erl_var(Line, S);
_ ->
translate_each(Left, S)
end,
{ TRight, SR } = translate_each(Right, SL),
Cache = (S#elixir_scope.context == nil),
{ Var, SV } = case Cache of
true -> elixir_scope:build_erl_var(Line, SR);
false -> { TLeft, SR }
end,
{ TCache, TExpr } = case TRight of
{ nil, _ } ->
Expr = { atom, Line, false },
{ Cache, Expr };
{ cons, _, _, _ } ->
[H|T] = elixir_utils:cons_to_list(TRight),
Expr = lists:foldr(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
end, { op, Line, '==', Var, H }, T),
{ Cache, Expr };
{ string, _, [H|T] } ->
Expr = lists:foldl(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
end, { op, Line, '==', Var, { integer, Line, H } }, T),
{ Cache, Expr };
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
Expr = case { Start, End } of
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?opt_in_types(K1), ?opt_in_types(K2), StartInt =< EndInt ->
increasing_compare(Line, Var, Start, End);
{ { K1, _, _ }, { K2, _, _ } } when ?opt_in_types(K1), ?opt_in_types(K2) ->
decreasing_compare(Line, Var, Start, End);
_ ->
{ op, Line, 'orelse',
{ op, Line, 'andalso',
{ op, Line, '=<', Start, End},
increasing_compare(Line, Var, Start, End) },
{ op, Line, 'andalso',
{ op, Line, '<', End, Start},
decreasing_compare(Line, Var, Start, End) } }
end,
{ Cache, Expr };
_ ->
case Cache of
true ->
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
false ->
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
"compile time list or range on the right side when used in guard expressions")
end
end,
case TCache of
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
false -> { Var, TExpr, SV }
end.
increasing_compare(Line, Var, Start, End) ->
{ op, Line, 'andalso',
{ op, Line, '>=', Var, Start },
{ op, Line, '=<', Var, End } }.
decreasing_compare(Line, Var, Start, End) ->
{ op, Line, 'andalso',
{ op, Line, '=<', Var, Start },
{ op, Line, '>=', Var, End } }.
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
rewrite_case_clauses(Clauses) ->
Clauses.
%% defmodule :foo
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
Raw;
%% defmodule Hello
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
elixir_aliases:concat([S#elixir_scope.module, H]);
%% defmodule Hello.World
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Atom when is_atom(Atom) ->
Module;
Aliases when is_list(Aliases) ->
elixir_aliases:concat([S#elixir_scope.module, Module])
end;
%% defmodule Elixir.Hello.World
expand_module(_Raw, Module, S) ->
elixir_aliases:concat([S#elixir_scope.module, Module]).
is_reserved_data(moduledoc) -> true;
is_reserved_data(typedoc) -> true;
is_reserved_data(doc) -> true;
is_reserved_data(_) -> false.
spec_to_macro(type) -> deftype;
spec_to_macro(typep) -> deftypep;
spec_to_macro(opaque) -> defopaque;
spec_to_macro(spec) -> defspec;
spec_to_macro(callback) -> defcallback.
% Pack a list of expressions from a block.
pack({ 'block', _, Exprs }) -> Exprs;
pack(Expr) -> [Expr].

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