Compare commits

...
459 Commits
Author SHA1 Message Date
José Valim ddc6b48a49 Release v0.12.2 2014-01-15 16:01:57 +01:00
José Valim 5a2e838def Deprecate <-/2 in favor of send/2 2014-01-14 18:27:41 +01:00
José Valim ebc6d76034 Update CHANGELOG 2014-01-14 16:32:11 +01:00
José Valim 42fc70a0eb Deprecate Enum.first/1 in favor of Enum.at/2 and List.first/1
The main reason for this change is because `Enum.first/1` does
not communicate that enumeration will happen, in contrast to
functions like Enum.at/2 and Enum.fetch/2 which are meant to
be used with non-indexed collections and the need for enumeration
is made a bit more obvious.

Closes #1983
2014-01-14 16:26:32 +01:00
José Valim 87fee37711 Change String.next_grapheme/1 and String.next_codepoint/1 to return nil on string end
This is more composable in the sense can use in in if/cond constructs
and also makes it easy to build streams. Closes #1721.
2014-01-14 16:15:54 +01:00
José Valim 4870351d2f Deprecate continuable heredocs, closes #1986 2014-01-14 16:03:14 +01:00
José Valim aba387c526 Ensure git scm does not take lock into account when comparing deps, closes #1992 2014-01-14 11:55:23 +01:00
José Valim 36d8e1ceee Merge pull request #1995 from jwarwick/module_doc
Cleaning up Module docs
2014-01-14 01:35:36 -08:00
John Warwick 68c9a28d06 Cleaning up Module docs 2014-01-14 17:31:28 +08:00
José Valim 30c9af3631 Merge pull request #1994 from devinus/eex-assigns-dict
Allow EEx.AssignsEngine to accept any Dict.t
2014-01-13 13:30:55 -08:00
Devin Torres f42055be57 Allow EEx.AssignsEngine to accept any Dict.t 2014-01-13 15:21:26 -06:00
José Valim 17a5c37d06 Merge pull request #1993 from chrismccord/fix_module_definitions_in_typo
Fix Module.definitions_in documentation typo.
2014-01-13 10:21:20 -08:00
Chris McCord 8c45982cc6 Fix Module.definitions_in documentation typo. 2014-01-13 11:52:07 -05:00
José Valim 6697100f95 Merge pull request #1989 from chaslemley/fix_float_round_error
fix issue with Float.round/3 (midpoint_rounding)
2014-01-12 12:22:55 -08:00
Chas Lemley 94812b4242 fix error with float midpoint rounding
- remove midpoint rounding option from Float.round/2
- always midpoint round away from 0
2014-01-12 12:17:50 -05:00
José Valim 4fae777e89 Merge pull request #1988 from jwarwick/behaviour_doc
Dict.Behaviour doc cleanup
2014-01-12 03:50:41 -08:00
John Warwick 7ea4b02460 Dict.Behaviour doc cleanup 2014-01-12 07:46:23 +08:00
José Valim 11423af777 Add missing fixture file for previous commit 2014-01-11 19:11:08 +01:00
José Valim 23c67ac47c Add a test to ensure no hanging in ParallelCompiler 2014-01-11 19:10:43 +01:00
José Valim 3ae00072ae Remove unecessary protocol in IEx.Autocomplete 2014-01-11 17:08:31 +01:00
José Valim b4b9ecccb4 Remove :[] as a valid atom and do not allow remote container calls 2014-01-11 17:02:45 +01:00
José Valim 53b68887b6 Invert the precedence of | and when
Although this adds some drawbacks when it comes to typespecs,
it prepares us for when we have named patterns.
2014-01-11 16:52:46 +01:00
José Valim ea8e555f53 Add Eric to the list of .travis.yml recipients 2014-01-11 10:34:56 +01:00
José Valim 407427f627 Take into get_rev_info command may fail when not in a git repository 2014-01-11 10:33:18 +01:00
José Valim 2b2d9dc2de Delete .git source when running the build
This will help us catch bugs that only show up when
we run tests outside of a git checkout.
2014-01-11 10:28:44 +01:00
Eric Meadows-Jönsson b4ad1ff5bc Fix bootstrapping issue in previous commit 2014-01-11 00:36:31 +01:00
Eric Meadows-Jönsson ab538670d3 Add :records, :binaries and :char_lists options to inspect/2
Deprecate :raw in favor of :records
2014-01-11 00:25:23 +01:00
José Valim 691a9b8b2b Update CHANGELOG 2014-01-10 22:56:43 +01:00
José Valim ee3fcdb96a Ensure recursive copy takes into account the proper target directory 2014-01-10 22:48:52 +01:00
José Valim ba88ee7c92 Improve binary handling to properly consider chars and bitstring with specifiers
Closes #1963
2014-01-10 22:25:08 +01:00
José Valim 9cd5a47bbe Improve docs for Dict.Behaviour 2014-01-10 14:29:47 +01:00
Eric Meadows-Jönsson be3ab7d13d Fix test race condition 2014-01-10 02:48:58 +01:00
José Valim cdbd480f4d Path dependencies can only be used with mix projects, closes #1967 2014-01-09 20:56:02 +01:00
José Valim 0538e46023 Compile unicode modules in parallel 2014-01-09 20:12:20 +01:00
José Valim ebf84037cc Kernel.inspect/2 with Inspect.Opts[] is deprecated
In favor of `Inspect.Algebra.to_doc/2`. Closes #1965.
2014-01-09 20:12:20 +01:00
José Valim 1b3cdbc7fb Merge pull request #1981 from christhekeele/patch-1
Add Keyword.delete/3 for deleting explicit values.
2014-01-09 09:37:09 -08:00
José Valim 1d13d189c1 Document that ^ always points to the previous value 2014-01-09 16:22:33 +01:00
José Valim 7fcdecde95 Turn off dependency tracking for compilation on release, closes #1982 2014-01-09 15:50:48 +01:00
José Valim bc852a6fba Add Enum.flat_map_reduce/3 2014-01-09 15:38:27 +01:00
José Valim 28ff6b3532 Update CHANGELOG 2014-01-09 13:56:56 +01:00
José Valim b0e1c09579 Add Stream.transform/3
Note: the function name may be modified before the next release.
2014-01-09 13:54:45 +01:00
Christopher Keele d3a5cd22a6 Add Keyword.delete/3 for deleting explicit values. 2014-01-09 00:59:38 -06:00
José Valim 96a1628033 Unify precedence of ++, --, **, .., <> ops 2014-01-09 01:06:34 +01:00
José Valim d6de0979fb Ensure Elixir own .app files include all lib modules 2014-01-08 15:04:19 +01:00
José Valim 19301b9005 Small improvements to records and others 2014-01-08 14:55:28 +01:00
José Valim 02c76da51a Add a test for multiple vars in IEx for the previous commit 2014-01-08 11:30:56 +01:00
Eric Meadows-Jonsson 435597286f Give erl_eval bindings as an orddict
Fixes error where in IEx when referencing the ~10th defined variable.
2014-01-08 05:55:31 +01:00
Eric Meadows-Jönsson 8e3b5e38e4 Merge pull request #1980 from jwarwick/doc_typos
Fixing doc typos in ExUnit and Kernel.Typespec
2014-01-07 20:36:28 -08:00
John Warwick 9fcab6e881 Fixing doc typos in Kernel.Typespec 2014-01-07 23:13:56 -05:00
John Warwick afd5ebb563 Fixing doc typos in ExUnit 2014-01-07 23:03:54 -05:00
Eric Meadows-Jonsson e169c0f49b Create a signature for callbacks 2014-01-07 23:00:07 +01:00
Eric Meadows-Jonsson 8c97056b0e Use :application.get_application/1 in stracktraces 2014-01-07 22:48:46 +01:00
Eric Meadows-Jonsson 4be1501f57 Annotated types should not be in guard vars 2014-01-07 22:10:47 +01:00
José Valim 59402569ed Use relative paths when compiling elixir itself 2014-01-07 12:00:57 +01:00
José Valim 0aff2a3139 Compile with relative paths and include apps in stacktraces
Closes #1964, closes #1861
2014-01-07 11:30:44 +01:00
José Valim 9c4271891a Deprecate is_alive in favor of Node.alive? 2014-01-07 10:12:31 +01:00
José Valim 7c240199f3 Improve stacktraces and error messages during expansion 2014-01-06 19:34:38 +01:00
José Valim 1e9ab79cad Format lock opts with the lock
This is a simpler solution to the problem outlines on issue #1879
2014-01-06 12:04:09 +01:00
José Valim d37b7b3702 Ensure teardown test case is returned 2014-01-05 18:33:53 +01:00
José Valim ac2c79a7ce Update CHANGELOG 2014-01-05 17:57:46 +01:00
José Valim 84cc7d5eb0 Only run setup_all if there are tests in the case 2014-01-05 17:38:21 +01:00
José Valim 27467bdb67 Fix spelling of overridden 2014-01-05 12:28:11 +01:00
José Valim 1ee1a3f687 Support @moduletag in ExUnit 2014-01-05 12:24:37 +01:00
José Valim 05c6f9f822 include in ExUnit only has effect after an exclude 2014-01-05 12:08:08 +01:00
José Valim 952052869f Ensure bitstring modifiers args are expanded 2014-01-04 22:44:55 +01:00
José Valim df8b216357 Reenable case clause rewriting, closes #1972 2014-01-04 10:45:18 +01:00
José Valim 79faa1f706 Do not export case and receive vars 2014-01-04 10:45:13 +01:00
José Valim 31cec42b23 Start v0.12.2-dev 2014-01-04 10:40:49 +01:00
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
Vincent Siliakus 5cf7f22b70 Renamed Macro.split_call/1 back to Macro.decompose_call/1 2013-11-05 10:16:16 +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
Vincent Siliakus 97bf67d8db Add Macro.decompose_call/1 2013-11-01 12:45:59 +01:00
280 changed files with 14535 additions and 10074 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
+4 -2
View File
@@ -1,9 +1,11 @@
language: erlang
script: "make compile && make test"
script: "make compile && rm -rf .git && make test"
notifications:
irc: "irc.freenode.org#elixir-lang"
recipients:
- jose.valim@plataformatec.com.br
- yrashk@gmail.com
- eric.meadows.jonsson@gmail.com
otp_release:
- R16B02
- R16B01
- R16B
+101 -2
View File
@@ -1,3 +1,102 @@
# v0.12.2 (2014-01-15)
* Enhancements
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
* [Enum] Add `Enum.flat_map_reduce/3`
* [ExUnit] Support @moduletag in ExUnit cases
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
* [Stream] Add `Stream.transform/3`
* Bug fixes
* [ExUnit] `:include` in ExUnit only has effect if a test was previously excluded with `:exclude`
* [ExUnit] Only run `setup_all` and `teardown_all` if there are tests in the case
* [Kernel] Ensure bitstring modifier arguments are expanded
* [Kernel] Ensure compiler does not block on missing modules
* [Kernel] Ensure `<>/2` works only with binaries
* [Kernel] Fix usage of string literals inside `<<>>` when `utf8`/`utf16`/`utf32` is used as specifier
* [Mix] Ensure mix properly copies _build dependencies on Windows
* Deprecations
* [Enum] Deprecate `Enum.first/1` in favor of `Enum.at/2` and `List.first/1`
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behavior has been deprecated
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
* [Kernel] Deprecate `<-/2` in favor of `send/2`
* Backwards incompatible changes
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
# 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 overridden 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
@@ -103,7 +202,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
@@ -289,7 +388,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`
+21 -15
View File
@@ -1,4 +1,4 @@
REBAR := "$(shell echo `pwd`/rebar)"
REBAR := rebar
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -20,12 +20,16 @@ define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
$(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
$(Q) cd lib/$(1) && ../../$$(ELIXIRC) "lib/**/*.ex" -o ebin
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
@@ -48,7 +52,7 @@ lib/elixir/src/elixir.app.src: src/elixir.app.src
$(Q) cat src/elixir.app.src >>lib/elixir/src/elixir.app.src
erlang:
$(Q) cd lib/elixir && $(REBAR) compile
$(Q) cd lib/elixir && ../../$(REBAR) compile
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
@@ -61,17 +65,18 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@ echo "==> kernel (compile)";
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
@ echo "==> elixir (compile)";
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/kernel.ex" -o ebin;
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin;
$(Q) $(MAKE) unicode
$(Q) rm -rf lib/elixir/ebin/elixir.app
$(Q) cd lib/elixir && $(REBAR) compile
$(Q) cd lib/elixir && ../../$(REBAR) compile
unicode: $(UNICODE)
$(UNICODE): lib/elixir/unicode/unicode.ex lib/elixir/unicode/UnicodeData.txt lib/elixir/unicode/GraphemeBreakProperty.txt
$(UNICODE): lib/elixir/unicode/*
@ 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/unicode/unicode.ex -o lib/elixir/ebin --no-debug-info;
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/graphemes.ex unicode/unicode.ex -o ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
@@ -92,9 +97,10 @@ install: compile
done
clean:
cd lib/elixir && $(REBAR) 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
@@ -112,7 +118,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:
@@ -124,9 +130,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
@@ -150,7 +156,7 @@ test_doc_test: compile
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_kernel: compile
@ echo "==> kernel (exunit)"
@ echo "==> elixir (exunit)"
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
.dialyzer.base_plt:
+2 -2
View File
@@ -14,13 +14,13 @@ This document simply outlines the release process:
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.11.1
0.12.2
+1 -1
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 (*)
+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 (*)
+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
@@ -88,7 +88,7 @@ defmodule EEx.AssignsEngine do
defmacro __using__(_) do
quote unquote: false do
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
quote do: Keyword.get(var!(assigns), unquote(name))
quote do: Dict.get(var!(assigns), unquote(name))
end
defp transform(arg) do
+7 -2
View File
@@ -7,16 +7,21 @@ defmodule EEx.SmartEngineTest do
assert_eval "foo bar", "foo bar"
end
test "evaluates with assigns" do
test "evaluates with assigns as a ListDict" do
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
end
test "evaluates with assigns as a HashDict" do
assigns = HashDict.new [foo: 1]
assert_eval "1", "<%= @foo %>", assigns: assigns
end
test "evaluates with loops" do
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
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(Path.relative_to_cwd(__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(Path.relative_to_cwd(__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
+24 -19
View File
@@ -9,8 +9,8 @@
-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
@@ -18,26 +18,34 @@
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
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
clause_vars=nil, %% a dict of all variables defined in a particular clause
match_vars=nil, %% a set of all variables defined in a particular match
export_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
macro_counter=0, %% macros expansions counter
context_modules=[], %% modules defined in the current context
vars=[], %% a set of defined variables
export_vars=nil, %% a set of variables to be exported in certain constructs, may be nil
lexical_tracker=nil, %% holds the lexical tracker pid
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
local=nil %% the module to delegate local functions to
}).
-record(elixir_quote, {
line=nil,
line=false,
keep=false,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
@@ -54,9 +62,6 @@
existing_atoms_only=false
}).
%% Introspection
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
%% Used in tokenization and interpolation
%% Numbers
+88 -21
View File
@@ -36,7 +36,7 @@ defmodule Behaviour do
`@callback` attributes. `defcallback` is a simple
mechanism that defines the `@callback` attribute
according to the given type specification. `defcallback` allows
documentsion to be created for the callback and defines
documentation to be created for the callback and defines
a custom function signature.
The callbacks and their documentation can be retrieved
@@ -46,44 +46,60 @@ defmodule Behaviour do
@doc """
Define a function callback according to the given type specification.
"""
defmacro defcallback({ :::, _, [fun, return] }) do
do_defcallback(fun, return, __CALLER__)
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
{ return, guard } = split_return_and_guard(return_and_guard)
do_defcallback(fun, return, guard, __CALLER__)
end
defmacro defcallback(fun) do
do_defcallback(fun, quote(do: term), __CALLER__)
do_defcallback(fun, quote(do: term), [], __CALLER__)
end
@doc """
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return] }) do
do_defmacrocallback(fun, return, __CALLER__)
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
{ return, guard } = split_return_and_guard(return_and_guard)
do_defmacrocallback(fun, return, guard, __CALLER__)
end
defmacro defmacrocallback(fun) do
do_defmacrocallback(fun, quote(do: Macro.t), __CALLER__)
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
end
defp do_defcallback(fun, spec, caller) do
case Macro.extract_args(fun) do
defp split_return_and_guard({ :when, _, [return, guard] }) do
{ return, guard }
end
defp split_return_and_guard({ :|, meta, [left, right] }) do
{ return, guard } = split_return_and_guard(right)
{ { :|, meta, [left, return] }, guard }
end
defp split_return_and_guard(other) do
{ other, [] }
end
defp do_defcallback(fun, return, guards, caller) do
case Macro.decompose_call(fun) do
{ name, args } ->
do_callback(:def, name, args, name, length(args), spec, caller)
:error ->
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
_ ->
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
defp do_defmacrocallback(fun, return, guards, caller) 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 ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
end
end
defp do_callback(kind, name, args, docs_name, docs_arity, return, caller) do
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
Enum.each args, fn
{ :::, _, [left, right] } ->
ensure_not_default(left)
@@ -95,24 +111,75 @@ defmodule Behaviour do
end
quote do
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(kind), unquote(docs_name), unquote(docs_arity)
docs_args = unquote(Macro.escape(docs_args))
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
unquote(docs_name), docs_args, unquote(docs_arity))
end
end
defp ensure_not_default({ ://, _, [_, _] }) do
raise ArgumentError, message: "default arguments // not supported in defcallback"
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
end
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, kind, name, arity) do
def store_docs(module, line, kind, name, args, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
{ simplify_signature(arg, ix + 1), [line: line], nil }
end)
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
end
defp simplify_signature({ :::, _, [var, _] }, i) do
simplify_signature(var, i)
end
defp simplify_signature({ :|, _, [first, _] }, i) do
simplify_signature(first, i)
end
defp simplify_signature({ :., _, [_, name] }, _i) do
name
end
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
:"fun#{i}"
end
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
:"range#{i}"
end
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
:"bitstring#{i}"
end
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
:"tuple#{i}"
end
defp simplify_signature({ name, _, args }, _i)
when is_atom(name) and (is_atom(args) or is_list(args)) do
name
end
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
simplify_signature(ast, i)
end
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
defp simplify_signature(_, i), do: :"arg#{i}"
@doc false
defmacro __using__(_) do
quote do
+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
+11 -17
View File
@@ -1,7 +1,8 @@
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
@@ -10,7 +11,6 @@ defmodule Code do
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
to add behavior which is specific to Elixir.
"""
@doc """
@@ -127,7 +127,7 @@ 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
@@ -162,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
@@ -229,10 +228,10 @@ 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)
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
@@ -250,14 +249,9 @@ 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 """
Load the given file.
@@ -353,7 +347,7 @@ defmodule Code do
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 """
@@ -364,7 +358,7 @@ defmodule Code do
binary.
"""
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
:elixir_compiler.quoted [quoted], file
:elixir_compiler.quoted quoted, file
end
@doc """
+1 -2
View File
@@ -70,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
@@ -102,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
+61 -33
View File
@@ -1,9 +1,11 @@
defmodule Dict.Behaviour do
@moduledoc """
Default implementations for some required functions in the `Dict` module.
This module makes it easier to create your own `Dict` compliant
module, by providing default implementations for some required functions.
Usage:
## Example
defmodule MyDict do
use Dict.Behaviour
@@ -13,17 +15,39 @@ defmodule Dict.Behaviour do
# override default implementations if needed
end
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.
The client module must contain the following functions:
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
* `update/4`
All of which are part of the `Dict` behaviour, so no extra functions are
actually required.
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.
for the following functions:
If you implement `new/0` and `new/1` functions, you can also test your custom
module via `Dict` doctests:
* `drop/2`
* `equal?/2`
* `fetch!/2`
* `get/2`
* `get/3`
* `has_key?/2`
* `keys/1`
* `merge/2`
* `merge/3`
* `put_new/3`
* `take/2`
* `to_list/1`
* `values/1`
All of these functions are defined as overridable, so you can provide your own
implementation if needed.
Note you can also test your custom module via `Dict`'s doctests:
defmodule MyDict do
def new(keywords // []) do
@@ -48,37 +72,31 @@ defmodule Dict.Behaviour do
: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
@@ -88,38 +106,48 @@ defmodule Dict.Behaviour do
end
end
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
defoverridable to_list: 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, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
defoverridable keys: 1
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, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
defoverridable values: 1
def values(dict) do
reduce(dict, { :cont, [] }, fn
{_, v}, acc -> { :cont, [v|acc] }
end) |> elem(1) |> :lists.reverse
end
def equal?(dict1, 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
+948 -798
View File
File diff suppressed because it is too large Load Diff
+80 -42
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"
@@ -5,19 +9,22 @@ defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
exception.description
end
end
@@ -47,7 +54,8 @@ 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
@@ -100,25 +108,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
@@ -176,41 +215,45 @@ defmodule Exception do
@doc """
Receives a tuple representing a stacktrace entry and formats it.
The current working directory may be given as an argument,
otherwise one is automatically retrieved.
"""
def format_stacktrace_entry(entry, cwd // nil)
def format_stacktrace_entry(entry)
# From Macro.Env.stacktrace
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
"#{format_location(file_line, cwd)}#{inspect module} (module)"
def format_stacktrace_entry({ module, :__MODULE__, 0, location }) do
format_location(location) <> inspect(module) <> " (module)"
end
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
"#{format_location(file_line, cwd)}(module)"
def format_stacktrace_entry({ _module, :__MODULE__, 1, location }) do
format_location(location) <> "(module)"
end
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
"#{format_location(file_line, cwd)}(file)"
def format_stacktrace_entry({ _module, :__FILE__, 1, location }) do
format_location(location) <> "(file)"
end
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
"#{format_location(file_line, cwd)}#{format_mfa(module, fun, arity)}"
def format_stacktrace_entry({module, fun, arity, location}) do
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
end
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
"#{format_location(file_line, cwd)}#{format_fa(fun, arity)}"
def format_stacktrace_entry({fun, arity, location}) do
format_location(location) <> format_fa(fun, arity)
end
defp format_application(module) do
case :application.get_application(module) do
{ :ok, app } -> "(" <> atom_to_binary(app) <> ") "
:undefined -> ""
end
end
@doc """
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,16 +262,19 @@ defmodule Exception do
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
cwd = System.cwd
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1, cwd)) <> "\n"
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
end
end
@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.
"""
@@ -289,21 +335,16 @@ defmodule Exception do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
else
"#{format_module module}#{fun}/#{arity}"
"#{inspect 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.
The current working directory may be given as an argument,
otherwise one is automatically retrieved.
## Examples
iex> Exception.format_file_line("foo", 1)
@@ -316,11 +357,8 @@ defmodule Exception do
""
"""
def format_file_line(file, line, cwd // nil) do
def format_file_line(file, line) do
if file do
file = to_string(file)
file = if cwd, do: Path.relative_to(file, cwd), else: Path.relative_to_cwd(file)
if line && line != 0 do
"#{file}:#{line}: "
else
@@ -331,8 +369,8 @@ defmodule Exception do
end
end
defp format_location(opts, cwd) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
defp format_location(opts) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line)
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
+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
@@ -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)
@@ -663,7 +649,7 @@ defmodule File do
do_rm_rf(path, { :ok, [] })
end
defp do_rm_rf(path, { :ok, acc } = entry) do
defp do_rm_rf(path, { :ok, _ } = entry) do
case safe_list_dir(path) do
{ :ok, files } when is_list(files) ->
res =
@@ -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 and every time 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 and every time 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)
+90 -11
View File
@@ -4,8 +4,8 @@ defmodule Float do
"""
@doc """
Parses a binary into a float.
Parses a binary into a float.
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
Otherwise `:error`.
@@ -22,28 +22,40 @@ defmodule Float do
"""
@spec parse(binary) :: { float, binary } | :error
def parse(binary) when is_binary(binary) do
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 after_integer, integer_part
{ 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(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
parse(rest, char - ?0, 1, int)
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
parse_unsign(rest, char - ?0, 1, int)
end
defp parse(rest, int) do
defp parse_unsign(rest, int) do
{ :erlang.float(int), rest }
end
# Handle decimal points
defp parse(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
parse rest, 10 * float + (char - ?0), decimal + 1, int
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(<< ?e, after_e :: binary >>, float, decimal, int) do
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
@@ -56,7 +68,7 @@ defmodule Float do
end
end
defp parse(bitstring, float, decimal, int) do
defp parse_unsign(bitstring, float, decimal, int) do
{ floatify(int, float, decimal), bitstring }
end
@@ -80,4 +92,71 @@ defmodule Float do
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).
## Examples
iex> Float.round(5.5674, 3)
5.567
iex> Float.round(5.5675, 3)
5.568
iex> Float.round(-5.5674, 3)
-5.567
iex> Float.round(-5.5675, 3)
-5.568
"""
@spec round(float, integer) :: float
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
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, :ok, 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 -25
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
@@ -480,6 +505,6 @@ 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
+77 -39
View File
@@ -1,7 +1,13 @@
import Kernel, except: [inspect: 1]
import Inspect.Algebra
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
defrecord Inspect.Opts,
records: true,
binaries: :infer,
char_lists: :infer,
limit: 50,
pretty: false,
width: 80
defprotocol Inspect do
@moduledoc """
@@ -13,11 +19,9 @@ defprotocol Inspect do
followed by the inspecting options, represented by the record
`Inspect.Opts`.
Inspection is done using the functions available in
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
receives an `Inspect.Opts` record as the second argument, it returns
the underlying algebra document instead of the formatted string.
Inspection is done using the functions available in `Inspect.Algebra`.
## Examples
Many times, inspecting a structure can be implemented in function
of existing entities. For example, here is `HashSet`'s `inspect`
@@ -27,7 +31,7 @@ defprotocol Inspect do
import Inspect.Algebra
def inspect(dict, opts) do
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
concat ["#HashSet<", to_doc(HashSet.to_list(dict), opts), ">"]
end
end
@@ -35,14 +39,24 @@ defprotocol Inspect do
concatenates algebra documents together. In the example above,
it is concatenating the string `"HashSet<"` (all strings are
valid algebra documents that keep their formatting when pretty
printed), the document returned by `Kernel.inspect/2` and the
printed), the document returned by `Inspect.Algebra.to_doc/2` and the
other string `">"`.
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 fall back to tuple inspection for records.
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
@@ -86,7 +100,7 @@ defimpl Inspect, for: Atom do
valid_ref_identifier?(binary) ->
"Elixir." <> rest = binary
rest
atom in [:{}, :[], :<<>>] ->
atom in [:{}, :<<>>] ->
":" <> binary
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
@@ -150,8 +164,8 @@ defimpl Inspect, for: BitString do
"<<0, 1, 2>>"
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
def inspect(thing, Inspect.Opts[binaries: bins] = opts) when is_binary(thing) do
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
<< ?", escape(thing, ?") :: binary, ?" >>
else
inspect_bitstring(thing, opts)
@@ -205,11 +219,29 @@ defimpl Inspect, for: BitString do
defp escape(<<?\v, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp escape(<<h :: utf8, t :: binary>>, char, binary) do
head = << h :: utf8 >>
if String.printable?(head) do
escape(t, char, append(head, binary))
else
<< byte :: size(8), h :: binary >> = head
t = << h :: binary, t :: binary >>
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
end
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, h >>)
escape(t, char, << binary :: binary, octify(h) :: binary >>)
end
defp escape(<<>>, _char, binary), do: binary
defp octify(byte) do
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
end
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
defp append(<<>>, binary), do: binary
## Bitstrings
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
@@ -262,21 +294,21 @@ defimpl Inspect, for: List do
def inspect([], _opts), do: "[]"
def inspect(thing, Inspect.Opts[] = opts) do
def inspect(thing, Inspect.Opts[char_lists: lists] = opts) do
cond do
:io_lib.printable_list(thing) ->
lists == :as_char_lists or (lists == :infer and :io_lib.printable_list(thing)) ->
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
keyword?(thing) && not opts.raw ->
keyword?(thing) ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
true ->
surround_many("[", thing, "]", opts.limit, &Kernel.inspect(&1, opts))
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
end
end
defp keyword({key, value}, opts) do
concat(
key_to_binary(key) <> ": ",
Kernel.inspect(value, opts)
to_doc(value, opts)
)
end
@@ -314,24 +346,24 @@ defimpl Inspect, for: Tuple do
def inspect({}, _opts), do: "{}"
def inspect(tuple, opts) do
unless opts.raw do
def inspect(tuple, Inspect.Opts[] = opts) do
if opts.records do
record_inspect(tuple, opts)
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &Kernel.inspect(&1, opts))
else
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
end
end
## Helpers
defp record_inspect(record, opts) do
defp record_inspect(record, Inspect.Opts[] = opts) 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
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
surround_record(name, fields, tail, opts)
else
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
end
end
defp record_fields(name) do
@@ -346,20 +378,26 @@ defimpl Inspect, for: Tuple do
end
end
defp surround_record(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
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) <> ": ",
Kernel.inspect(v, opts)
)
concat(k <> ": ", to_doc(v, opts))
end
end
@@ -408,11 +446,11 @@ defimpl Inspect, for: Regex do
"""
def inspect(regex, opts) when size(regex) == 5 do
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
end
def inspect(other, opts) do
Kernel.inspect(other, opts.raw(true))
def inspect(other, Inspect.Opts[] = opts) do
to_doc(other, opts.records(false))
end
end
+50 -41
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,14 +93,34 @@ 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
@doc """
Converts an Elixir structure to an algebra document
according to the inspect protocol.
"""
@spec to_doc(any, Inspect.Opts.t) :: t
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
if is_tuple(arg) do
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
try do
Inspect.inspect(arg, opts)
catch
_, _ -> Inspect.Tuple.inspect(arg, opts)
end
else
Inspect.Tuple.inspect(arg, opts)
end
else
Inspect.inspect(arg, opts)
end
end
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
@@ -296,7 +313,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 +331,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,58 +390,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 | :infinity, 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 w == :infinity or fits?(w - k, t) do
s_text(str: s, sdoc: format(w, k, t))
[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
+13 -12
View File
@@ -1,7 +1,8 @@
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
@@ -243,7 +244,7 @@ defmodule IO do
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
fn(acc, f) -> stream(map_dev(device), line_or_codepoints, acc, f) end
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
end
@doc """
@@ -256,30 +257,30 @@ defmodule IO do
"""
@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
+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
{ _, rest } -> length_without_escape(rest, count + 1)
nil -> count
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
+1873 -1752
View File
File diff suppressed because it is too large Load Diff
+34 -10
View File
@@ -221,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
@@ -339,18 +339,42 @@ 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)
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
if files != [] do
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)
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
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
{ :missing, Enum.uniq(missing_patterns) }
end
end
+12 -2
View File
@@ -18,9 +18,19 @@ defmodule Kernel.ErrorHandler do
{ :module, _ } -> []
{ :error, _ } ->
parent = :erlang.get(:elixir_compiler_pid)
parent <- { :waiting, self(), module }
ref = :erlang.make_ref
send parent, { :waiting, self(), ref, module }
:erlang.garbage_collect(self)
receive do
{ :release, ^parent } -> :ok
{ ^ref, :ready } ->
:ok
{ ^ref, :release } ->
# On release, get rid of the compiler pid,
# no further allow elixir_ensure_compiled directives
# and revert to the original error handler.
:erlang.erase(:elixir_compiler_pid)
:erlang.erase(:elixir_ensure_compiled)
:erlang.process_flag(:error_handler, :error_handler)
end
end
end
+5 -5
View File
@@ -8,7 +8,7 @@
# any of the `GenServer.Behaviour` conveniences.
defmodule Kernel.LexicalTracker do
@timeout 30_000
@behavior :gen_server
@behaviour :gen_server
@import 2
@alias 3
@@ -104,8 +104,8 @@ defmodule Kernel.LexicalTracker do
{ :reply, d, d }
end
def handle_call(_request, _from, d) do
{ :noreply, d }
def handle_call(request, _from, d) do
{ :stop, { :bad_call, request }, d }
end
def handle_cast({ :remote_dispatch, module }, d) do
@@ -137,8 +137,8 @@ defmodule Kernel.LexicalTracker do
{ :stop, :normal, d }
end
def handle_cast(_msg, d) do
{ :noreply, d }
def handle_cast(msg, d) do
{ :stop, { :bad_cast, msg }, d }
end
def handle_info(_msg, d) do
+79 -51
View File
@@ -62,29 +62,42 @@ 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)
send h, { ref, :ready }
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 ->
# Notify Code.ensure_compiled/2 that we should
# attempt to compile the module by doing a dispatch.
:erlang.put(:elixir_ensure_compiled, true)
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 }
# Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
:erlang.put(:elixir_compiler_pid, parent)
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
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
@@ -92,7 +105,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)
send child, { ref, :release }
end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
@@ -104,38 +120,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, 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 <- { ref, :ack }
send 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 } ->
new_waiting = :orddict.store(child, on, waiting)
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
{ :failure, child, kind, reason, stacktrace } ->
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
send 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
# 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
@@ -143,29 +182,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
+2 -2
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 }
@@ -128,7 +128,7 @@ defmodule Kernel.RecordRewriter do
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
end
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args } = call, module, dict) do
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
{ left, dict, res } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ args, dict } = optimize_args(args, module, dict)
+392 -97
View File
@@ -1,22 +1,22 @@
defmodule Kernel.SpecialForms do
@moduledoc """
In this module we define Elixir special forms. Special forms
cannot be overriden by the developer and are the basic
cannot be overridden 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__`, `__DIR__` and `__CALLER__`). Pseudo variables
return information about Elixir's compilation environment and can only
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 """
@@ -34,7 +34,7 @@ defmodule Kernel.SpecialForms do
{ :{}, [], [1,2,3] }
"""
defmacro :{}.(args)
defmacro unquote(:{})(args)
@doc """
Defines a new bitstring.
@@ -171,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.
@@ -227,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
@@ -284,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.
@@ -471,19 +471,45 @@ 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 """
Accesses 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 that `^` always refers to the value of x prior to the match. The
following example will match:
iex> x = 0
iex> { x, ^x } = { 1, 0 }
iex> x
1
"""
defmacro ^(var)
@doc %S"""
Gets the representation of any expression.
@@ -532,14 +558,14 @@ defmodule Kernel.SpecialForms do
2.0 #=> Floats
[1, 2] #=> Lists
"strings" #=> Strings
{key, value} #=> Tuple with two elements
{key, value} #=> Tuples with two elements
## Quote and macros
`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 allows us to focus
seen as a bad practice), but it is simple enough that it allows us to focus
on the important aspects of quotes and macros:
defmodule Math do
@@ -557,8 +583,9 @@ defmodule Kernel.SpecialForms do
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`. We can see this behaviour in practice though
because our macro actually has a bug:
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:
import Math
my_number = fn ->
@@ -613,11 +640,11 @@ defmodule Kernel.SpecialForms do
end
end
`:bind_quoted` will translate to the same example as 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, as unquote fragments discussed
in some sections below.
`: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:
@@ -637,7 +664,7 @@ defmodule Kernel.SpecialForms do
because Elixir macros are hygienic, a topic we will discuss at length
in the next sections as well.
### Hygiene in variables
## Hygiene in variables
Consider the following example:
@@ -655,8 +682,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
@@ -671,7 +698,7 @@ defmodule Kernel.SpecialForms do
NoHygiene.interference
a #=> 1
Note that you cannot even access variables defined by the same
Note that you cannot even access variables defined in the same
module unless you explicitly give it a context:
defmodule Hygiene do
@@ -717,7 +744,7 @@ defmodule Kernel.SpecialForms do
quote hygiene: [vars: false], do: x
### Hygiene in aliases
## Hygiene in aliases
Aliases inside quote are hygienic by default.
Consider the following example:
@@ -737,10 +764,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
@@ -789,7 +829,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,
@@ -842,12 +891,8 @@ defmodule Kernel.SpecialForms do
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 that `location: :keep` evaluates the code as if it was defined
inside the `Adder` file, in particular, the macro `__FILE__` and
exceptions happening inside the quote will always point to the file
where `Added` was defined.
reported to where `defadd` was invoked. Note `location: :keep` affects
only definitions inside the quote.
## Binding and unquote fragments
@@ -860,12 +905,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
@@ -882,13 +927,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
@@ -900,12 +945,12 @@ 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 (again!). By using `:bind_quoted`, we
can automatically disable unquoting while still injecting the
@@ -924,27 +969,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
unquoted or an atom representing the variable name.
Check `quote/2` for more information.
"""
defmacro var!(var)
@doc """
Defines a variable in the given context.
Check `quote/2` for more information.
"""
defmacro var!(var, context)
@doc """
When used inside quoting, marks that the alias should not
be hygienezed. This means the alias will be expanded when
the macro is expanded.
"""
defmacro alias!(alias)
@doc """
Unquotes the given expression from inside a macro.
@@ -968,8 +992,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
@@ -982,8 +1005,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:
@@ -1067,8 +1089,7 @@ defmodule Kernel.SpecialForms do
3
"""
name = :fn
defmacro unquote(name)(clauses)
defmacro unquote(:fn)(clauses)
@doc """
Internal special form for block expressions.
@@ -1158,21 +1179,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.
@@ -1218,4 +1225,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
+307 -132
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
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
`false`). All other types are built 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 # 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, ...] # shorthand for nonempty_list(Type)
Tuple :: tuple # 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,27 +100,98 @@ 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`
`fun` | `(... -> any)`
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 on by functions in
the `String` module), use `String.t` type instead.
"""
@doc """
Defines a type.
This macro is the one responsible for handling the attribute `@type`.
This macro is responsible for handling the attribute `@type`.
## Examples
@@ -90,13 +200,13 @@ 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
@doc """
Defines an opaque type.
This macro is the one responsible for handling the attribute `@opaque`.
This macro is responsible for handling the attribute `@opaque`.
## Examples
@@ -105,13 +215,13 @@ 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
@doc """
Defines a private type.
This macro is the one responsible for handling the attribute `@typep`.
This macro is responsible for handling the attribute `@typep`.
## Examples
@@ -120,13 +230,13 @@ 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
@doc """
Defines a spec.
This macro is the one responsible for handling the attribute `@spec`.
This macro is responsible for handling the attribute `@spec`.
## Examples
@@ -135,13 +245,13 @@ 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
@doc """
Defines a callback.
This macro is the one responsible for handling the attribute `@callback`.
This macro is responsible for handling the attribute `@callback`.
## Examples
@@ -150,7 +260,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 +327,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 +337,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 +346,28 @@ 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)] }
meta = [line: line]
body = { name, meta, Enum.map(args, &typespec_to_ast/1) }
vars = args ++ [result]
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Enum.map(&{ &1, { :var, meta, nil } })
result = if vars == [] do
typespec_to_ast(result)
else
{ :when, meta, [typespec_to_ast(result), vars] }
end
{ :::, meta, [body, result] }
end
def spec_to_ast(name, { :type, line, :fun, [] }) do
@@ -252,15 +375,25 @@ 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
meta = [line: line]
{ :::, [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
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{ &1, { :var, meta, nil } })
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :::, meta, [
{ name, [line: line], args },
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
] }
end
@doc """
@@ -281,10 +414,10 @@ defmodule Kernel.Typespec do
@doc """
Returns all type docs available from the module's beam code.
It is returned as a list of tuples where the first element is the pair of type
The result is returned as a list of tuples where the first element is the pair of type
name and arity and the second element is the documentation.
The module has to have a corresponding beam file on the disk which can be
The module must have a corresponding beam file which can be
located by the runtime system.
"""
@spec beam_typedocs(module | binary) :: [tuple] | nil
@@ -301,10 +434,10 @@ defmodule Kernel.Typespec do
@doc """
Returns all types available from the module's beam code.
It is returned as a list of tuples where the first
The result is returned as a list of tuples where the first
element is the type (`:typep`, `:type` and `:opaque`).
The module has to have a corresponding beam file on the disk which can be
The module must have a corresponding beam file which can be
located by the runtime system.
"""
@spec beam_types(module | binary) :: [tuple] | nil
@@ -317,7 +450,7 @@ 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
@@ -329,10 +462,10 @@ defmodule Kernel.Typespec do
@doc """
Returns all specs available from the module's beam code.
It is returned as a list of tuples where the first
The result 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
The module must have a corresponding beam file which can be
located by the runtime system.
"""
@spec beam_specs(module | binary) :: [tuple] | nil
@@ -343,10 +476,10 @@ defmodule Kernel.Typespec do
@doc """
Returns all callbacks available from the module's beam code.
It is returned as a list of tuples where the first
The result 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
The module must have a corresponding beam file
which can be located by the runtime system.
"""
@spec beam_callbacks(module | binary) :: [tuple] | nil
@@ -397,7 +530,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
@@ -418,19 +551,23 @@ defmodule Kernel.Typespec do
end
@doc false
def defspec(type, {:::, _, [{ :when, _, [{ name, meta, args }, constraints_guard] }, return] }, caller) do
def defspec(type, { :::, meta, [{ name, _, args }, return_and_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)] }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
{ return, guard } = split_return_and_guard(return_and_guard)
unless Keyword.keyword?(guard) do
guard = Macro.to_string(guard)
compile_error caller, "expected keywords as guard in function type specification, got: #{guard}"
end
vars = Keyword.keys(guard)
constraints = guard_to_constraints(guard, vars, meta, 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
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 }
Module.compile_typespec(caller.module, type, code)
code
@@ -438,23 +575,67 @@ 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 split_return_and_guard({ :when, _, [return, guard] }) do
{ return, guard }
end
defp split_return_and_guard({ :|, meta, [left, right] }) do
{ return, guard } = split_return_and_guard(right)
{ { :|, meta, [left, return] }, guard }
end
defp split_return_and_guard(other) do
{ other, [] }
end
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
[]
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
@@ -462,8 +643,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
@@ -487,17 +668,16 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({ :type, line, :union, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
Enum.reduce tl(args), hd(args),
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
end
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :->, [line: 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
@@ -514,14 +694,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)
@@ -563,6 +740,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
@@ -574,7 +760,7 @@ defmodule Kernel.Typespec do
# Handle unions
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
exprs = Enum.reverse(collect_union(exprs))
exprs = collect_union(exprs)
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line(meta), :union, union }
end
@@ -608,11 +794,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
@@ -646,11 +828,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
@@ -659,6 +837,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)
@@ -666,7 +848,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)
@@ -675,7 +857,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 }
@@ -715,18 +897,14 @@ defmodule Kernel.Typespec do
typespec({ :nonempty_list, [], [spec] }, vars, caller)
end
defp typespec([h|t] = l, vars, caller) do
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
{ :|, [], [acc, validate_kw(x, l, caller)] }
defp typespec(list, vars, caller) do
[h|t] = Enum.reverse(list)
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
{ :|, [], [validate_kw(x, list, caller), acc] }
end)
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
@@ -738,12 +916,12 @@ defmodule Kernel.Typespec do
{ :remote_type, line(meta), [ remote, name, arguments ] }
end
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
defp collect_union({ :|, _, [a, b] }), do: [a|collect_union(b)]
defp collect_union(v), do: [v]
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 in typespec: #{Macro.to_string original}")
end
defp fn_args(meta, args, return, vars, caller) do
@@ -766,15 +944,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
+24 -1
View File
@@ -25,7 +25,9 @@ defmodule Keyword do
@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`
@@ -209,6 +211,27 @@ defmodule Keyword do
lc { _, value } inlist keywords, do: value
end
@doc """
Deletes the entry in the keyword list for a `key` with `value`.
If no `key` with `value` exists, returns the keyword list unchanged.
## Examples
iex> Keyword.delete([a: 1, b: 2], :a, 1)
[b: 2]
iex> Keyword.delete([a: 1, b: 2, a: 3], :a, 3)
[a: 1, b: 2]
iex> Keyword.delete([b: 2], :a, 5)
[b: 2]
"""
@spec delete(t, key, value) :: t
def delete(keywords, key, value) when is_atom(key) do
lc { k, v } = tuple inlist keywords, key != k or value != v, do: tuple
end
@doc """
Deletes all entries in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
@@ -254,7 +277,7 @@ defmodule Keyword do
Puts the given `value` under `key`.
If a previous value is already stored, all entries are
removed and the value is overriden.
removed and the value is overridden.
## Examples
+148 -18
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,10 +111,28 @@ 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
@doc """
Returns the first element in `list` or `nil` if `list` is empty.
## Examples
iex> List.first([])
nil
iex> List.first([1])
1
iex> List.first([1, 2, 3])
1
"""
@spec first([elem]) :: nil | elem when elem: var
def first([]), do: nil
def first([h|_]), do: h
@doc """
Returns the last element in `list` or `nil` if `list` is empty.
@@ -117,11 +146,10 @@ defmodule List do
3
"""
def last([]), do: nil
def last(list) do
:lists.last(list)
end
@spec last([elem]) :: nil | elem when elem: var
def last([]), do: nil
def last([h]), do: h
def last([_|t]), do: last(t)
@doc """
Receives a list of tuples and returns the first tuple
@@ -140,6 +168,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 +190,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 +205,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 +223,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 +242,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 +255,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 +286,7 @@ defmodule List do
[]
"""
@spec wrap(list | any) :: list
def wrap(list) when is_list(list) do
list
end
@@ -276,6 +311,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 +330,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 +355,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 +365,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 +384,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 +393,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 +482,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
+255 -184
View File
@@ -2,54 +2,55 @@ 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
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
::: -> {:right, 30}
:when -> {:right, 40}
o when o in [:inlist, :inbits] -> {:left, 50}
:// -> {:right, 60}
:| -> {:left, 70}
::: -> {:right, 40}
o when o in [:inlist, :inbits, ://, :|] -> {:right, 50}
:when -> {:right, 70}
:= -> {:right, 80}
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
:in -> {:left, 170}
:.. -> {:left, 200}
o when o in [:++, :--, :**, :.., :<>] -> {:right, 200}
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
o when o in [:<>] -> {:right, 230}
:^^^ -> {:left, 250}
:. -> {:left, 310}
end
end
@doc """
Breaks a pipeline expression into a list. Raises if
the pipeline is ill-formed.
Breaks a pipeline expression into a list.
Raises if the pipeline is ill-formed.
"""
@spec unpipe(Macro.t) :: [Macro.t]
def unpipe({ :|> , _, [left, right] }) do
@@ -61,7 +62,7 @@ defmodule Macro do
end
@doc """
Pipes the given `expr` in to the `call_expr` as the
Pipes `expr` into the `call_expr` as the
argument in the given `position`.
"""
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
@@ -81,33 +82,83 @@ defmodule Macro do
end
@doc """
Receives an expression 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.
Recurs the quoted expression applying the given function to
each metadata node.
This is useful for macros that want to provide the same
argument syntax available in def/defp/defmacro and friends.
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
@@ -129,7 +180,9 @@ defmodule Macro do
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.
@@ -139,7 +192,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
@@ -148,7 +201,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
@@ -157,13 +210,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
@@ -184,16 +238,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)
@@ -205,12 +259,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]
@@ -220,6 +275,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]
@@ -270,31 +326,43 @@ defmodule Macro do
# Tuple containers
def to_string({ :{}, _, args } = ast, fun) do
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
fun.(ast, tuple)
end
# Fn keyword
def to_string({ :fn, _, [{ :->, _, [{_, _, 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, _, [{ :->, _, [_] } = 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, _, [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
@@ -309,7 +377,7 @@ defmodule Macro do
fun.(ast, "not " <> to_string(arg, fun))
end
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
def to_string({ op, _, [arg] } = ast, fun) when op in @unary_ops do
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
end
@@ -321,7 +389,7 @@ defmodule Macro do
# 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)
@@ -334,25 +402,30 @@ defmodule Macro do
# Lists
def to_string(list, fun) when is_list(list) do
if Keyword.keyword?(list) do
fun.(list, "[" <> kw_list_to_string(list, fun) <> "]")
else
fun.(list, "[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]")
end
fun.(list, cond do
list == [] ->
"[]"
:io_lib.printable_list(list) ->
"'" <> Inspect.BitString.escape(String.from_char_list!(list), ?') <> "'"
Keyword.keyword?(list) ->
"[" <> kw_list_to_string(list, fun) <> "]"
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))
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
# 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(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
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)
@@ -361,23 +434,25 @@ defmodule Macro do
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
@@ -390,8 +465,8 @@ defmodule Macro do
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)
@@ -413,7 +488,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
@@ -431,8 +506,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)
@@ -455,18 +530,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
@@ -511,7 +587,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:
@@ -530,153 +606,148 @@ defmodule Macro do
"""
def expand_once(ast, env) do
elem(expand_once(ast, env, nil), 0)
elem(do_expand_once(ast, env), 0)
end
defp expand_once({ :__aliases__, _, _ } = original, env, cache) 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_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true, cache }
{ receiver, true }
aliases ->
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 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_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true, cache }
false -> { original, false, cache }
{ 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
elem(expand(tree, env, nil), 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 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
+21 -13
View File
@@ -12,37 +12,45 @@ defmodule Macro.Env do
* `function` - a tuple as `{ atom, integer` }, where the first
element is the function name and the seconds its arity. Returns
`nil` if not inside a function
* `aliases` - a list of two item tuples, where the first
item is the aliased name and the second the actual name
* `context` - the context of the environment. It can be nil
(default context), inside a guard or inside an assign
* `aliases` - a list of two item tuples, where the first
item is the aliased name and the second the actual name
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
* `context_modules` - a list of modules defined in the current context
* `macro_aliases` - a list of aliases defined inside the current macro
* `vars` - a list keeping all defined varaibles as { var, context }
* `context_modules` - a list of modules defined in the current context
* `vars` - a list keeping all defined variables as { var, context }
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
* `local` - the module to expand local functions to
"""
@type name_arity :: { atom, non_neg_integer }
@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 export_vars :: vars | nil
@type lexical_tracker :: pid
@type local :: module | nil
fields = [:module, :file, :line, :function, :aliases, :context, :requires, :functions,
:macros, :context_modules, :macro_aliases, :vars, :lexical_tracker]
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
:macros, :macro_aliases, :context_modules, :vars, :export_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, lexical_tracker: lexical_tracker]
function: name_arity, context: context, requires: requires, aliases: aliases,
functions: functions, macros: macros, macro_aliases: aliases,
context_modules: context_modules, vars: vars, export_vars: export_vars,
lexical_tracker: lexical_tracker, local: local]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
@@ -56,13 +64,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
@@ -73,7 +81,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 ->
+64 -43
View File
@@ -51,11 +51,13 @@ defmodule Module do
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
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).
### Example
defmodule M do
@before_compile __MODULE__
defmodule A do
defmacro __before_compile__(_env) do
quote do
def hello, do: "world"
@@ -63,7 +65,11 @@ defmodule Module do
end
end
* `@behaviour` (notice the british spelling)
defmodule B do
@before_compile A
end
* `@behaviour` (notice the British spelling)
Specify an OTP or user-defined behaviour.
@@ -177,7 +183,7 @@ defmodule Module do
When just a module is provided, the function is assumed to be
`__on_definition__/6`.
Note that you can\'t provide the current module to `@on_definition`
Note that you can't provide the current module to `@on_definition`
because the hook function will not be defined in time. Finally, since
the `on_definition` hook is executed inside the context of the defined
function (i.e. `env.function` returns the current function), the hook
@@ -292,7 +298,7 @@ defmodule Module do
In addition to the above, you may also pass to `__info__/1` any atom supported
by Erlang's `module_info` function which also gets defined for each compiled
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
module. See http://erlang.org/doc/reference_manual/modules.html#id69430 for
more information.
"""
def __info__(kind)
@@ -346,13 +352,16 @@ 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 """
Creates a module with the given name and given by
Creates a module with the given name and defined by
the given quoted expressions. The line where the module
is defined and its file can be given as options.
is defined and its file can be passed as options.
## Examples
@@ -365,7 +374,7 @@ defmodule Module do
Hello.world #=> true
## Differences with `defmodule`
## Differences from `defmodule`
`Module.create` works similarly to `defmodule` and
return the same results. While one could also use
@@ -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.
Concatenates a list of aliases and returns a new alias.
## 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.
Concatenates two aliases and returns a new alias.
## Examples
@@ -417,18 +422,17 @@ 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
@doc """
Concatenates the list aliases and returns a new alias only
Concatenates a list of aliases and returns a new alias only
if the alias was already referenced. If the alias was not
referenced yet, fails with ArgumentError.
referenced yet, fails with `ArgumentError`.
It handles char lists, binaries and atoms.
## Examples
@@ -440,14 +444,15 @@ 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
@doc """
Concatenates the two aliases and returns a new alias only
Concatenates two aliases and returns a new alias only
if the alias was already referenced. If the alias was not
referenced yet, fails with ArgumentError.
referenced yet, fails with `ArgumentError`.
It handles char lists, binaries and atoms.
## Examples
@@ -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
@@ -479,7 +485,7 @@ defmodule Module do
@doc """
Attaches documentation to a given function or type. It expects
the module the function/type belongs to, the line (a non negative
integer), the kind (def or defmacro), a tuple representing
integer), the kind (`def` or `defmacro`), a tuple representing
the function and its arity, the function signature (the signature
should be omitted for types) and the documentation, which should
be either a binary or a boolean.
@@ -578,7 +584,7 @@ defmodule Module do
@doc """
Checks if the module defines the given function or macro.
Use `defines?/3` to assert for an specific type.
Use `defines?/3` to assert for a specific type.
## Examples
@@ -596,8 +602,8 @@ defmodule Module do
end
@doc """
Checks if the module defines a function or macro with the
given `kind`. `kind` can be either `:def`, `:defp`,
Checks if the module defines a function or macro of the
given `kind`. `kind` can be any of `:def`, `:defp`,
`:defmacro` or `:defmacrop`.
## Examples
@@ -619,7 +625,7 @@ defmodule Module do
end
@doc """
Return all functions defined in the given module.
Return all functions defined in `module`.
## Examples
@@ -636,7 +642,7 @@ defmodule Module do
end
@doc """
Returns all functions defined in te given module according
Returns all functions defined in `module`, according
to its kind.
## Examples
@@ -655,7 +661,7 @@ defmodule Module do
end
@doc """
Makes the given functions in the given module overridable.
Makes the given functions in `module` overridable.
An overridable function is lazily defined, allowing a
developer to customize it. See `Kernel.defoverridable` for
more information and documentation.
@@ -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, _, _, _ }) ->
@@ -683,10 +694,10 @@ defmodule Module do
end
@doc """
Returns true if the given tuple in module is marked as overridable.
Returns `true` if `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,18 +774,22 @@ 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
@doc """
Deletes all attributes that matches the given key.
Deletes all attributes that match the given key.
## Examples
@@ -802,7 +821,7 @@ defmodule Module do
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are false.
By default, both options are `false`.
## Examples
@@ -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
@@ -11,32 +11,27 @@
#
# 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, name, arity }` and `{ :remote, name arity }`
# * 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
@@ -48,44 +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 imported modules that had the given
`{ name, arity }` invoked.
@@ -96,16 +56,6 @@ defmodule Module.DispatchTracker do
:digraph.out_neighbours(d, { :import, name, arity })
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.
@@ -130,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.
@@ -173,16 +119,10 @@ defmodule Module.DispatchTracker do
:gen_server.cast(pid, { :add_local, from, to })
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 })
:gen_server.cast(pid, { :add_import, function, module, target })
end
# Yanks a local node. Returns its in and out vertices in a tuple.
@@ -246,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
@@ -257,77 +207,92 @@ defmodule Module.DispatchTracker do
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
{ :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_external, kind, function, module, { name, arity } }, d) do
handle_import_or_remote(d, kind, function, module, name, arity)
{ :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_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)
tuple = { kind, name, arity }
tuple = { :import, name, arity }
:digraph.add_vertex(d, tuple)
replace_edge!(d, tuple, module)
+1 -1
View File
@@ -33,7 +33,7 @@ defmodule OptionParser do
Extra information about switches can be given as arguments, too.
This is useful when a switch must behave as a boolean
or if duplicated switches should be kept, overriden or accumulated.
or if duplicated switches should be kept, overridden or accumulated.
The following types are supported:
+2 -2
View File
@@ -264,8 +264,8 @@ defmodule Process do
@doc """
Associates the name with a pid or a port identifier. name, which must
be an atom, can be used instead of the pid / port identifier in the
send operator (name <- message).
be an atom, can be used instead of the pid / port identifier with the
`Kernel.send/2` function.
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
"""
+17 -15
View File
@@ -22,10 +22,6 @@ defmodule Protocol do
@functions []
@fallback_to_any false
# Deprecated
@only nil
@except nil
# Invoke the user given block
unquote(block)
@@ -71,7 +67,8 @@ defmodule Protocol do
# Inline both helpers
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
@@ -207,7 +204,7 @@ defmodule Protocol do
# Defines an implementation with fallback to the given module.
defp impl_with_fallback(mod, guard, current, arg) do
quote do
{ unquote(guard)(unquote(arg)),
{ :erlang.unquote(guard)(unquote(arg)),
unquote(with_fallback(Module.concat(current, mod))) }
end
end
@@ -261,7 +258,7 @@ defmodule Protocol.DSL do
# 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
@@ -271,15 +268,20 @@ defmodule Protocol.DSL do
end
@doc false
def callback_from_spec(module, name, arity) do
tuple = { name, arity }
specs = Module.get_attribute(module, :spec)
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
found = lc { k, v } inlist specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
found != []
{ :error, _ } ->
true
end
found != []
end
end
+42 -33
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,56 +17,65 @@ defprotocol Range.Iterator do
end
defimpl Enumerable, for: Range do
def reduce(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
defp reduce(_x, _y, { :halt, acc }, _fun, _next, _up) do
{ :halted, acc }
end
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
first <= value and value <= last
if first <= last do
{ :ok, first <= value and value <= last }
else
{ :ok, last <= value and value <= first }
end
end
def count(first .. _ = range) do
Range.Iterator.count(first, range)
{ :ok, Range.Iterator.count(first, range) }
end
end
defimpl Range.Iterator, for: Integer do
def reduce(first, Range[last: last], acc, fun) when is_integer(last) do
reducer = if last >= first do
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
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(last) and last >= first do
last - first + 1
end
def count(first, Range[last: last]) when is_integer(last) do
first - last + 1
if last >= first do
last - first + 1
else
first - last + 1
end
end
end
defimpl Inspect, for: Range do
import Inspect.Algebra
def inspect(Range[first: first, last: last], opts) do
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
concat [to_doc(first, opts), "..", to_doc(last, opts)]
end
end
+64 -48
View File
@@ -96,9 +96,9 @@ defmodule Record do
Notice that now since the record definition is accessible, Elixir
shows the record nicely formatted, no longer as a simple tuple. We
can get the raw formatting by passing `raw: true` to `inspect`:
can get the raw formatting by passing `records: false` to `inspect`:
inspect user(), raw: true
inspect user(), records: false
{ User, "José", 25 }
Since working with external records is common, Elixir allows
@@ -150,7 +150,7 @@ defmodule Record do
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`
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
@@ -188,16 +188,16 @@ defmodule Record do
@type t :: tuple
@doc """
Extract record information from an Erlang file.
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,
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")
@@ -268,30 +268,52 @@ 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 """
@@ -347,16 +369,20 @@ 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 """
Define macros for manipulating records.
Define macros for manipulating records.
This is called
directly by `Kernel.defrecordp/3`. It expects the macro name, the
record values and the environment.
@@ -368,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) }
@@ -408,7 +436,7 @@ defmodule Record do
end
# Store fields that can be optimized and that cannot be
# optimized as they are overriden
# optimized as they are overridden
@doc false
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = { name, length(args) }
@@ -459,10 +487,10 @@ defmodule Record do
case remaining do
[] ->
quote do: { unquote_splicing([atom|match]) }
{ :{}, [], [atom|match] }
_ ->
keys = lc { key, _ } inlist remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
end
end
@@ -685,7 +713,7 @@ defmodule Record do
quote do
@doc false
def to_keywords(record) do
unquote(:orddict.from_list(sorted))
unquote(sorted)
end
end
end
@@ -780,11 +808,11 @@ defmodule Record do
end
defp updater_lookup(k, key, values) do
v = find_index(values, key, 1)
index = find_index(values, key, 0)
quote do
case :lists.keyfind(unquote(k), 1, keywords) do
false -> elem(record, unquote(v))
false -> :erlang.element(unquote(index + 2), record)
{_, value} -> value
end
end
@@ -807,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
@@ -816,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
@@ -829,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
+5 -4
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
+2 -2
View File
@@ -4,8 +4,8 @@ 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/
+851 -286
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
+19 -16
View File
@@ -149,7 +149,7 @@ defmodule String do
def printable?(<<?\a, t :: binary>>), do: printable?(t)
def printable?(<<>>), do: true
def printable?(_), do: false
def printable?(b) when is_binary(b), do: false
@doc """
Divides a string into substrings at each Unicode whitespace
@@ -497,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"
@@ -562,7 +562,7 @@ defmodule String do
do_reverse(next_grapheme(rest), [grapheme|acc])
end
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
defp do_reverse(nil, acc), do: iolist_to_binary(acc)
@doc """
Returns a binary `subject` duplicated `n` times.
@@ -602,7 +602,7 @@ defmodule String do
Returns the next codepoint in a String.
The result is a tuple with the codepoint and the
remaining of the string or `:no_codepoint` in case
remaining of the string or `nil` in case
the string reached its end.
As with other functions in the String module, this
@@ -617,7 +617,7 @@ defmodule String do
"""
@compile { :inline, next_codepoint: 1 }
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
@spec next_codepoint(t) :: {codepoint, t} | nil
defdelegate next_codepoint(string), to: String.Unicode
@doc %S"""
@@ -693,7 +693,7 @@ defmodule String do
Returns the next grapheme in a String.
The result is a tuple with the grapheme and the
remaining of the string or `:no_grapheme` in case
remaining of the string or `nil` in case
the String reached its end.
## Examples
@@ -703,7 +703,7 @@ defmodule String do
"""
@compile { :inline, next_grapheme: 1 }
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
@spec next_grapheme(t) :: { grapheme, t } | nil
defdelegate next_grapheme(string), to: String.Graphemes
@doc """
@@ -722,7 +722,7 @@ defmodule String do
def first(string) do
case next_grapheme(string) do
{ char, _ } -> char
:no_grapheme -> nil
nil -> nil
end
end
@@ -747,7 +747,7 @@ defmodule String do
do_last(next_grapheme(rest), char)
end
defp do_last(:no_grapheme, last_char), do: last_char
defp do_last(nil, last_char), do: last_char
@doc """
Returns the number of unicode graphemes in an utf8 string.
@@ -769,7 +769,7 @@ defmodule String do
1 + do_length(next_grapheme(rest))
end
defp do_length(:no_grapheme), do: 0
defp do_length(nil), do: 0
@doc """
Returns the grapheme in the `position` of the given utf8 `string`.
@@ -811,7 +811,7 @@ defmodule String do
char
end
defp do_at(:no_grapheme, _, _), do: nil
defp do_at(nil, _, _), do: nil
@doc """
Returns a substring starting at the offset given by the first, and
@@ -934,11 +934,11 @@ defmodule String do
acc <> char
end
defp do_slice(:no_grapheme, start_pos, _, current_pos, acc) when start_pos == current_pos do
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
acc
end
defp do_slice(:no_grapheme, _, _, _, acc) do
defp do_slice(nil, _, _, _, acc) do
case acc do
"" -> nil
_ -> acc
@@ -1056,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
+3 -3
View File
@@ -28,8 +28,8 @@ defmodule System do
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
case read_stripped("VERSION") do
"" -> raise CompileError, message: "could not read the version number from VERSION"
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
data -> data
end
end
@@ -38,7 +38,7 @@ defmodule System do
# the most recent tag. If that is not available, tries to read the commit hash
# from .git/HEAD. If that fails, returns an empty string.
defmacrop get_describe do
dirpath = ".git"
dirpath = :filename.join(__DIR__, "../../../.git")
case :file.read_file_info(dirpath) do
{ :ok, _ } ->
if :os.find_executable('git') do
+79 -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)
@@ -146,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
+19 -16
View File
@@ -2,7 +2,7 @@ defmodule Version do
@moduledoc %S"""
Functions for parsing and matching versions against requirements.
A version is a string in a specific format or a `Version.Schema`
A version is a string in a specific format or a `Version.Schema`
generated after parsing via `Version.parse/1`.
`Version` parsing and requirements follow
@@ -31,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
operators like `>=`, `<=`, `>`, `==` and friends that
work as one would expect:
# Only version 2.0.0
@@ -65,14 +65,17 @@ 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
@@ -80,7 +83,7 @@ defmodule Version do
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.
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
## Examples
@@ -137,7 +140,7 @@ defmodule Version do
## Examples
> Version.parse("2.0.1-alpha1")
iex> Version.parse("2.0.1-alpha1")
#Version.Schema<2.0.1-alpha1>
"""
@@ -190,7 +193,7 @@ defmodule Version do
## Examples
> Version.from_matchable({2, 0, 1, ["alpha", 1]})
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
#Version.Schema<2.0.1-alpha.1>
"""
@@ -401,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
+88 -52
View File
@@ -1,9 +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_forms/3,
eval_quoted/2, eval_quoted/3, eval_quoted/4]).
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
@@ -11,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]).
@@ -22,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.
@@ -44,95 +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)).
eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
eval_forms(elixir_quote:linify(Line, Tree), Binding, S).
%% Handle forms evaluation internally, it is an
%% internal API not meant for external usage.
%% Handle forms evaluation. The main difference to
%% to eval_quoted is that it does not linefy the given
%% args.
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, scope_for_eval(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),
eval_forms(Tree, Binding, Scope) ->
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope, nil),
{ Exprs, NewScope } = elixir_translator:translate(Tree, ParsedScope),
case Exprs of
[] ->
{ nil, Binding, NewScope };
case Erl of
{ atom, _, Atom } ->
{ Atom, Binding, NewEnv, NewScope };
_ ->
{ value, Value, NewBinding } = erl_eval:exprs(Exprs, ParsedBinding),
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), 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.
+75 -109
View File
@@ -1,98 +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/4, store/5]).
-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, _TKV, S) -> S;
store(Meta, New, Old, TKV, S) ->
record_warn(Meta, New, TKV, 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.
record_warn(Meta, Ref, Opts, S) ->
%% 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, S#elixir_scope.lexical_tracker).
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, ['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, LexicalTracker);
{ alias, Atom } when is_atom(Atom) ->
case expand(Alias, MacroAliases, LexicalTracker) of
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
OtherAliases when is_list(OtherAliases) -> Atom
end;
Atom;
false ->
expand(Alias, Aliases, LexicalTracker)
end.
expand({ '__aliases__', _Meta, [H] }, Aliases, LexicalTracker) when H /= 'Elixir' ->
case expand_one(H, Aliases, LexicalTracker) of
false -> [H];
Atom -> Atom
end;
expand({ '__aliases__', _Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
case H of
'Elixir' ->
concat(T);
_ ->
case expand_one(H, Aliases, LexicalTracker) 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, _LexicalTracker) ->
List.
expand_one(H, Aliases, LexicalTracker) ->
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
case lookup(Lookup, Aliases) of
Lookup -> false;
Else ->
elixir_lexical:record_alias(Lookup, LexicalTracker),
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.
@@ -105,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)]).
+202
View File
@@ -0,0 +1,202 @@
%% 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) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
case expand_bit_type_or_size(Expr, ListArgs) of
type ->
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, Size, [{ Expr, [], EArgs }|Types], EE);
size ->
case Size of
default -> ok;
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file, "duplicated size definition in bitstring")
end,
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, { Expr, [], EArgs }, Types, EE);
none ->
handle_unknown_bit_info(Meta, { Expr, ExprMeta, ListArgs }, 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, default, Types) when is_binary(H) ->
Element =
case types_allow_splice(Types, []) of
true ->
%% See explanation in elixir_utils:elixir_to_erl/1 to know
%% why we can simply convert the binary to a list.
{ bin_element, ?line(Meta), { string, 0, binary_to_list(H) }, default, default };
false ->
case types_require_conversion(Types) of
true ->
{ bin_element, ?line(Meta), { string, 0, elixir_utils:characters_to_list(H) }, default, Types };
false ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid types for literal string in <<>>. "
"Accepted types are: little, big, utf8, utf16, utf32, bits, bytes, binary, bitstring")
end
end,
build_bitstr_each(Fun, T, Meta, S, [Element|Acc]);
build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_binary(H) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "size is not supported for literal string in <<>>");
build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_list(H); is_atom(H) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid literal ~ts in <<>>",
['Elixir.Macro':to_string(H)]);
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
case Expr of
{ bin, _, Elements } ->
case (Size == default) andalso types_allow_splice(Types, Elements) of
true -> build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
false -> build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
end;
_ ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
end.
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_require_conversion(T);
types_require_conversion([]) -> true;
types_require_conversion(_) -> false.
types_allow_splice([bytes], Elements) -> lists:all(fun has_default_size/1, Elements);
types_allow_splice([binary], Elements) -> lists:all(fun has_default_size/1, Elements);
types_allow_splice([bits], _) -> true;
types_allow_splice([bitstring], _) -> true;
types_allow_splice(default, _) -> true;
types_allow_splice(_, _) -> false.
has_default_size({ bin_element, _, _, default, _ }) -> true;
has_default_size({ bin_element, _, _, _, _ }) -> 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 }.
+69 -111
View File
@@ -1,134 +1,109 @@
%% 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{export_vars=CV} = S) ->
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_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, VAcc}) ->
{ TX, TS } = each_clause(X, SAcc),
{ TX,
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
end,
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
{ TClauses, { TS, ReverseCV } } =
lists:mapfoldl(Transformer, {S, []}, 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)
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Val }, Acc) ->
normalize_vars(Key, Val, Acc)
end, TS, 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 +124,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;
@@ -167,21 +143,17 @@ 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);
{ Arg, Guards } = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
each_clause({ else, Meta, [Condition], Expr }, S) ->
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
{ Arg, Guards } = extract_guards(Condition),
clause(?line(Meta), fun elixir_translator:translate_many/2, [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({ 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]).
{ 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,55 +161,51 @@ 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
% by picking one value as reference and retriving
% its previous value.
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
FS = S#elixir_scope{
normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S) ->
VS = S#elixir_scope{
vars=orddict:store(Key, Value, Vars),
clause_vars=orddict:store(Key, Value, ClauseVars)
export_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 +217,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].
+13 -12
View File
@@ -3,6 +3,8 @@
-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=[],
@@ -14,21 +16,19 @@
}).
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) ->
code:ensure_loaded('Elixir.Module.DispatchTracker'),
init(ok) ->
code:ensure_loaded('Elixir.Macro.Env'),
code:ensure_loaded('Elixir.Module.LocalsTracker'),
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
{ ok, #elixir_code_server{} }.
@@ -83,8 +83,8 @@ handle_call(erl_compiler_options, _From, Config) ->
{ reply, Opts, Config }
end;
handle_call(_Request, _From, Config) ->
{ reply, undef, Config }.
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]} };
@@ -131,8 +131,8 @@ handle_cast({ unload_files, Files }, Config) ->
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 }.
@@ -143,7 +143,8 @@ 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",
+66 -50
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/4, 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).
%% 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) ->
@@ -31,8 +25,8 @@ quoted(Forms, File) when is_binary(File) ->
put(elixir_compiled, []),
elixir_lexical:run(File, fun
(Pid) ->
Scope = elixir:scope_for_eval([{file,File}]),
eval_forms(Forms, 1, [], Scope#elixir_scope{lexical_tracker=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
@@ -51,51 +45,65 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
%% Evaluation
eval_forms(Forms, Line, Vars, S) ->
{ Module, I } = retrieve_module_name(),
{ Exprs, FS } = elixir_translator:translate(Forms, S),
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.
Fun = eval_fun(S#elixir_scope.module),
Form = eval_mod(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
Args = [X || { _, _, _, X } <- Vars],
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),
{ Module, I } = retrieve_module_name(),
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 }.
eval_fun(nil) -> '__FILE__';
eval_fun(_) -> '__MODULE__'.
code_fun(nil) -> '__FILE__';
code_fun(_) -> '__MODULE__'.
eval_mod(Fun, Exprs, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
Args = [{ var, Line, '_@MODULE'}, Cons],
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, file, { elixir_utils:characters_to_list(Relative), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ Fun, 2 }, { '__RELATIVE__', 0 }] },
{ function, Line, Fun, length(Args), [
{ clause, Line, Args, [], Exprs }
{ 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)] }
@@ -108,6 +116,11 @@ 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
@@ -182,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">>,
@@ -199,12 +213,12 @@ 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/kernel/lexical_tracker.ex">>,
<<"lib/elixir/lib/module/dispatch_tracker.ex">>
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
].
binary_to_path({ModuleName, Binary}, CompilePath) ->
@@ -216,17 +230,19 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
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" });
format_errors(Errors) ->
lists:foreach(fun ({File, Each}) ->
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
end, Errors).
format_warnings(Bootstrap, Warnings) ->
lists:foreach(fun ({File, Each}) ->
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
end, Warnings).
+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.
+106 -127
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/1,
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_location(Line, MetaFile, Module),
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,85 @@ 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 location from meta file or @file, otherwise nil
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_location(Line, File, Module) ->
case not(elixir_compiler:get_opt(internal)) andalso
'Elixir.Module':get_attribute(Module, file) of
X when X == nil; X == false ->
if
is_binary(File) ->
{ elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line };
true ->
nil
end;
X ->
'Elixir.Module':delete_attribute(Module, file),
{ X, 0 }
end.
%% 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 +197,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(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, [], [], [], [], [], [], []).
unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
unwrap_definition([Fun|T], CTable, 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
@@ -244,39 +219,35 @@ unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defm
{ Functions, Tail };
_ ->
NewAll = [Tuple|All],
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
end,
unwrap_stored_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_stored_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
unwrap_definition([], _CTable, 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 }.
%% Helpers
function_for_stored_definition({{Name,Arity}, _, Line, _, _, {File, _}, _}, Clauses, File, Functions, Tail) ->
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, _File, Functions, Tail) ->
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
{ Functions, [
{ function, Line, Name, Arity, Clauses },
{ attribute, Line, file, Location } | Tail
@@ -336,10 +307,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 +318,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 +349,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.
+11 -12
View File
@@ -1,6 +1,6 @@
% 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,
-export([store_pending/1, ensure_defined/4,
name/2, store/3, format_error/1]).
-include("elixir.hrl").
@@ -12,17 +12,11 @@ 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
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 })
{ ok, { _, _, _, _ } } -> ok;
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
end.
%% Gets the name based on the function and stored overridables
@@ -49,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,
@@ -68,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),
+176 -299
View File
@@ -2,34 +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_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
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_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
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
@@ -37,239 +36,200 @@ 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_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
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) ->
Arity = length(Args),
case expand_import(Meta, { Name, Arity }, Args, E, []) of
{ ok, Receiver, Quoted } ->
expand_quoted(Meta, Receiver, Name, Arity, 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_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
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_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
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_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
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_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, Receiver, Tree } ->
translate_expansion(Meta, Receiver, 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_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
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_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
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, Receiver, 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_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
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_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
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_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
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, Name, Arity, 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).
%% Macros expansion
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_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 ->
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
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, prune_stacktrace(Info, erlang:get_stacktrace(), SArg))
Info = [{ Receiver, Name, length(Args), location(Line, E) }, mfa(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, Receiver, Tree, S) ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
Line = ?line(Meta),
New = S#elixir_scope.macro_counter + 1,
Next = elixir_counter:next(),
try
elixir_translator:translate_each(
elixir_quote:linify(Line, { Receiver, New }, Tree),
S#elixir_scope{macro_counter=New}
)
elixir_exp:expand(
elixir_quote:linify_with_context_counter(Line, { Receiver, Next }, Quoted),
E)
catch
Kind:Reason ->
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
Info = [{ Receiver, Name, Arity, location(Line, E) }, mfa(Line, E)],
erlang:raise(Kind, Reason, prune_stacktrace(Info, 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 };
@@ -279,21 +239,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;
@@ -301,59 +258,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
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
prune_stacktrace(Info, [{ _, _, [E|_], _ }|_], E) ->
Info;
%% We've reached the elixir_dispatch internals, skip it with the rest
prune_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
[Info];
prune_stacktrace(Info, [H|T], S) ->
[H|prune_stacktrace(Info, T, S)];
prune_stacktrace(Info, [{ Mod, _, _, _ }|_], _) when Mod == elixir_dispatch; Mod == elixir_exp ->
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 }.
Info.
%% ERROR HANDLING
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
Module = elixir_errors:inspect(Receiver),
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, "
"please rename the local macro or remove the conflicting import",
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
[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
@@ -373,31 +301,25 @@ 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 },
{ apply, 2 },
{ apply, 3 },
{ atom_to_list, 1 },
{ binary_part, 3 },
{ binary_to_float, 1 },
@@ -413,7 +335,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 },
@@ -421,7 +345,6 @@ in_erlang_functions() ->
{ integer_to_list, 2 },
{ iolist_size, 1 },
{ iolist_to_binary, 1 },
{ is_alive, 0 },
{ is_atom, 1 },
{ is_binary, 1 },
{ is_bitstring, 1 },
@@ -452,6 +375,7 @@ in_erlang_functions() ->
% { now, 0 },
{ round, 1 },
{ self, 0 },
{ send, 2 },
{ size, 1 },
{ spawn, 1 },
{ spawn, 3 },
@@ -467,50 +391,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}
].
+49
View File
@@ -0,0 +1,49 @@
-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, merge_vars/2, merge_opt_vars/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) when is_list(E1) ->
E2#elixir_env{
vars=merge_vars(E1, E2#elixir_env.vars),
export_vars=merge_opt_vars(E1, E2#elixir_env.export_vars)
};
mergev(E1, E2) ->
E2#elixir_env{
vars=merge_vars(E1#elixir_env.vars, E2#elixir_env.vars),
export_vars=merge_opt_vars(E1#elixir_env.export_vars, E2#elixir_env.export_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}.
merge_vars(V1, V2) -> ordsets:union(V1, V2).
merge_opt_vars(_V1, nil) -> nil;
merge_opt_vars(nil, _V2) -> nil;
merge_opt_vars(V1, V2) -> ordsets:union(V1, V2).
+18 -72
View File
@@ -1,57 +1,37 @@
% 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) ->
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_all(), binary() | string()) -> no_return().
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), 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_all(), iolist() | atom(), [] | iolist()) -> no_return().
@@ -80,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_all(), 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, []).
@@ -133,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;
@@ -177,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) ->
@@ -217,7 +163,7 @@ raise(Meta, File, Kind, Message) when is_list(Meta) ->
raise(none, File, Kind, Message) ->
raise(0, File, Kind, Message);
raise(Line, File, Kind, Message) when is_integer(Line) ->
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
%% Populate the stacktrace so we can raise it
try
throw(ok)
@@ -225,13 +171,13 @@ raise(Line, File, Kind, Message) when is_integer(Line) ->
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
Exception = Kind:new([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
file_format(0, File, Message) ->
file_format(0, File, Message) when is_binary(File) ->
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
file_format(Line, File, Message) ->
file_format(Line, File, Message) when is_binary(File) ->
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
format_var(Var) ->
+562
View File
@@ -0,0 +1,562 @@
-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, export_vars=Export} = E) when is_atom(Name), is_atom(Kind) ->
Pair = { Name, var_kind(Meta, Kind) },
NewVars = ordsets:add_element(Pair, E#elixir_env.vars),
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= { export, false }) of
true -> ordsets:add_element(Pair, Export);
false -> Export
end,
{ Var, E#elixir_env{vars=NewVars, export_vars=NewExport} };
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, [{records,false}])]);
%% 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 expression in match");
assert_no_match_scope(_Meta, _Kind, _E) -> [].
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
compile_error(Meta, File, "invalid expression in guard");
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
+200
View File
@@ -0,0 +1,200 @@
%% 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) ->
EE = E#elixir_env{export_vars=[]},
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, EE) end, [], KV),
{ EClauses, elixir_env:mergev(EVars, E) }.
do_case(Meta, { 'do', _ } = Do, Acc, E) ->
expand_with_export(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) ->
EE = E#elixir_env{export_vars=[]},
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, EE) end, [], KV),
{ EClauses, elixir_env:mergev(EVars, E) }.
do_receive(_Meta, { 'do', nil } = Do, Acc, _E) ->
{ Do, Acc };
do_receive(Meta, { 'do', _ } = Do, Acc, E) ->
expand_with_export(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:merge_vars(Acc, ER#elixir_env.export_vars) };
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_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
do_try(Meta, { 'catch', _ } = Catch, E) ->
expand_without_export(Meta, 'try', fun elixir_exp:expand_many/2, Catch, E);
do_try(Meta, { 'rescue', _ } = Rescue, E) ->
expand_without_export(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
export_vars({ Left, Meta, Right }) when is_atom(Left), is_list(Meta), is_atom(Right) ->
{ Left, [{export,false}|Meta], Right };
export_vars({ Left, Meta, Right }) ->
{ export_vars(Left), Meta, export_vars(Right) };
export_vars({ Left, Right }) ->
{ export_vars(Left), export_vars(Right) };
export_vars(List) when is_list(List) ->
[export_vars(X) || X <- List];
export_vars(Other) ->
Other.
%% 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_export(Meta, Kind, Fun, { Key, Clauses }, Acc, E) when is_list(Clauses) ->
EFun =
case lists:keyfind(export_all, 1, Meta) of
{ export_all, true } -> Fun;
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
end,
Transformer = fun(Clause, Vars) ->
{ EClause, EC } = clause(Meta, Kind, EFun, Clause, E),
{ EClause, elixir_env:merge_vars(Vars, EC#elixir_env.export_vars) }
end,
{ EClauses, EVars } = lists:mapfoldl(Transformer, Acc, Clauses),
{ { Key, EClauses }, EVars };
expand_with_export(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_export(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_export(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) ->
do_capture(Meta, List, S, is_sequential_and_not_empty(List));
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 -52
View File
@@ -7,55 +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, S) ->
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_lexical:record_import(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
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;
@@ -76,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
@@ -113,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) ->
@@ -150,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
@@ -196,7 +181,6 @@ 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;
@@ -210,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;
@@ -219,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.
+7 -1
View File
@@ -138,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
+2 -3
View File
@@ -13,7 +13,6 @@ run(File, Callback) ->
case code:is_loaded(?tracker) of
{ file, _ } ->
Pid = ?tracker:start_link(),
try
Callback(Pid)
after
@@ -75,6 +74,6 @@ warn_unused_aliases(File, Pid) ->
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
format_error({unused_alias, Module}) ->
io_lib:format("unused alias ~ts", [elixir_errors:inspect(Module)]);
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
format_error({unused_import, Module}) ->
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]).
io_lib:format("unused import ~ts", [elixir_aliases:inspect(Module)]).
-199
View File
@@ -1,199 +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 }, S) when is_list(Args) ->
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;
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
{ TArgs, SE } = translate_args(Args, S),
{ { tuple, ?line(Meta), TArgs }, SE };
translate({ Left, Right }, S) ->
translate({ '{}', [], [Left, Right]}, S);
translate([], S) ->
{ { nil, 0 }, S };
translate([_|_] = Args, S) ->
[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, 0 }, ST } end
end,
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
{ Tail, ST } = TailFun(SE),
{ elixir_utils:list_to_cons(0, Exprs, Tail), ST };
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].
@@ -1,19 +1,76 @@
%% Module responsible for tracking invocations of module calls.
-module(elixir_tracker).
-module(elixir_locals).
-export([
setup/1, cleanup/1,
record_local/2, record_local/3,
record_import/4, record_remote/4,
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
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').
-define(attr, '__dispatch_tracker').
-define(tracker, 'Elixir.Module.DispatchTracker').
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.
%% RECORD
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
@@ -45,14 +102,6 @@ record_import(Tuple, Receiver, Module, Function) ->
true
end).
record_remote(_Tuple, Receiver, Module, _Function)
when Module == nil; Module == Receiver -> false;
record_remote(Tuple, Receiver, Module, Function) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_remote(Pid, Function, Receiver, Tuple),
true
end).
record_definition(Tuple, Kind, Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_definition(Pid, Kind, Tuple),
@@ -67,8 +116,6 @@ record_defaults(Tuple, Kind, Module, Defaults) ->
true
end).
%% HELPERS
if_tracker(Module, Callback) ->
try ets:lookup_element(Module, ?attr, 2) of
Pid -> Callback(Pid)
@@ -76,6 +123,24 @@ if_tracker(Module, Callback) ->
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) ->
@@ -101,7 +166,7 @@ warn_unused_local(File, Module, Private) ->
format_error({function_conflict,{Receivers, Name, Arity}}) ->
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
[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]);
-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) ->
translate_in(Meta, Left, Right, S);
%% @
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, [{warn,false}], 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 } = translate_each(Left, S),
{ 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 -> { { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
false -> { 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 } }.
%% TODO: Once we have elixir_exp, we can move this
%% clause to Elixir code and out of case.
rewrite_case_clauses([
{do,Meta1,[{'when',_,[{V,M,C},{in,_,[{V,M,C},[false,nil]]}]}],False},
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
case lists:keyfind('cond', 1, M) of
{ 'cond', true } ->
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
_ ->
Clauses
end;
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,
S#elixir_scope.lexical_tracker) 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].
+82 -125
View File
@@ -1,6 +1,6 @@
-module(elixir_module).
-export([translate/4, compile/5, data_table/1, docs_table/1,
eval_quoted/4, format_error/1, eval_callbacks/5]).
-export([compile/4, data_table/1, docs_table/1,
format_error/1, eval_callbacks/5]).
-include("elixir.hrl").
-define(acc_attr, '__acc_attributes').
@@ -9,25 +9,6 @@
-define(persisted_attr, '__persisted_attributes').
-define(overridable_attr, '__overridable').
eval_quoted(Module, Quoted, RawBinding, Opts) ->
Binding = orddict:store('_@MODULE', Module, RawBinding),
Scope = scope_for_eval(Module, Opts),
elixir_def:reset_last(Module),
case lists:keyfind(line, 1, Opts) of
{ line, Line } -> Line;
false -> Line = 1
end,
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
{ Value, FinalBinding }.
scope_for_eval(Module, #elixir_scope{} = S) ->
S#elixir_scope{module=Module};
scope_for_eval(Module, Opts) ->
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
%% TABLE METHODS
data_table(Module) ->
@@ -36,82 +17,73 @@ data_table(Module) ->
docs_table(Module) ->
ets:lookup_element(Module, ?docs_attr, 2).
%% TRANSFORMATION FUNCTIONS
%% Compilation hook
%% Transformation of args and scope into a compiled erlang call.
%% The abstract form for extra arguments may be given and they
%% will be passed to the invoked function.
translate(Meta, Ref, Block, S) ->
Line = ?line(Meta),
LexS = case S#elixir_scope.function of
nil -> S;
_ -> S#elixir_scope{lexical_tracker=nil}
compile(Module, Block, Vars, #elixir_env{line=Line} = Env) when is_atom(Module) ->
%% In case we are generating a module from inside a function,
%% we get rid of the lexical tracker information as, at this
%% point, the lexical tracker process is long gone.
LexEnv = case Env#elixir_env.function of
nil -> Env#elixir_env{module=Module, local=nil};
_ -> Env#elixir_env{lexical_tracker=nil, function=nil, module=Module, local=nil}
end,
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, LexS),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, LexS),
case LexEnv#elixir_env.lexical_tracker of
nil ->
elixir_lexical:run(LexEnv#elixir_env.file, fun(Pid) ->
do_compile(Line, Module, Block, Vars, LexEnv#elixir_env{lexical_tracker=Pid})
end);
_ ->
do_compile(Line, Module, Block, Vars, LexEnv)
end;
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
?wrap_call(Line, ?MODULE, compile, Args).
compile(Module, _Block, _Vars, #elixir_env{line=Line,file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Module });
%% The compilation hook.
compile(Module, Block, Vars, ExEnv) ->
compile(Module, Block, Vars, elixir_env:ex_to_env(ExEnv)).
compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} = RawS) when is_atom(Module) ->
C = elixir_compiler:get_opts(),
S = case lists:member(Module, FileModules) of
true -> RawS;
false -> RawS#elixir_scope{context_modules=[Module|FileModules]}
end,
File = S#elixir_scope.file,
FileList = elixir_utils:characters_to_list(File),
check_module_availability(Line, File, Module, C),
build(Line, File, Module, S#elixir_scope.lexical_tracker),
do_compile(Line, Module, Block, Vars, E) ->
File = E#elixir_env.file,
check_module_availability(Line, File, Module),
build(Line, File, Module, E#elixir_env.lexical_tracker),
try
Result = eval_form(Line, Module, Block, Vars, S),
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ Result, NE } = eval_form(Line, Module, Block, Vars, E),
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_definitions(Module),
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
Forms1 = specs_form(Module, Private, Defmacro, Forms0, C),
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
Forms2 = attributes_form(Line, File, Module, Forms1),
Forms3 = typedocs_form(Module, Forms2),
case ets:lookup(data_table(Module), 'on_load') of
[] -> ok;
[{on_load,OnLoad}] ->
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
end,
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
elixir_tracker:warn_unused_local(File, Module, Private),
elixir_locals:ensure_no_function_conflict(Line, File, Module, AllFunctions),
elixir_locals:warn_unused_local(File, Module, Private),
warn_invalid_clauses(Line, File, Module, All),
warn_unused_docs(Line, File, Module),
Location = { elixir_utils:relative_to_cwd(elixir_utils:characters_to_list(File)), Line },
Final = [
{ attribute, Line, file, { FileList, Line } },
{ attribute, Line, file, Location },
{ attribute, Line, module, Module } | Forms3
],
Binary = load_form(Line, Final, compile_opts(Module), S),
Binary = load_form(Line, Final, compile_opts(Module), NE),
{ module, Module, Binary, Result }
after
elixir_tracker:cleanup(Module),
elixir_locals:cleanup(Module),
elixir_def:cleanup(Module),
ets:delete(docs_table(Module)),
ets:delete(data_table(Module))
end;
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other });
compile(Line, Module, Block, Vars, RawS) ->
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
S = elixir_scope:deserialize_with_vars(RawS, Dict),
compile(Line, Module, Block, Vars, S).
end.
%% Hook that builds both attribute and functions and set up common hooks.
@@ -142,28 +114,26 @@ build(Line, File, Module, Lexical) ->
%% Setup other modules
elixir_def:setup(Module),
elixir_tracker:setup(Module).
elixir_locals:setup(Module).
%% Receives the module representation and evaluates it.
eval_form(Line, Module, Block, Vars, RawS) ->
S = scope_for_eval(Module, RawS),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
eval_form(Line, Module, Block, Vars, E) ->
{ Value, EE } = elixir_compiler:eval_forms(Block, Vars, E),
elixir_def_overridable:store_pending(Module),
Env = elixir_scope:to_ex_env({ Line, S }),
eval_callbacks(Line, Module, before_compile, [Env], NewS),
EC = eval_callbacks(Line, Module, before_compile, [elixir_env:env_to_ex({ Line, EE })], EE),
elixir_def_overridable:store_pending(Module),
Value.
{ Value, EC }.
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions, C) ->
BaseFunctions = case elixir_compiler:get_opt(internal, C) of
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions) ->
BaseFunctions = case elixir_compiler:get_opt(internal) of
true -> RawFunctions;
false -> record_rewrite_functions(Module, RawFunctions)
end,
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro, C),
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
All = [{ '__info__', 1 }|BaseAll],
Export = [{ '__info__', 1 }|BaseExport],
@@ -220,11 +190,10 @@ typedocs_form(Module, Current) ->
%% Specs
specs_form(Module, Private, Defmacro, Forms, C) ->
specs_form(Module, Private, Defmacro, Forms) ->
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
case elixir_compiler:get_opt(internal, C) of
true -> Forms;
_ ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{ module, 'Elixir.Kernel.Typespec' } ->
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
@@ -233,7 +202,9 @@ specs_form(Module, Private, Defmacro, Forms, C) ->
'Elixir.Module':delete_attribute(Module, callback),
Temp = specs_attributes(spec, Forms, Specs),
specs_attributes(callback, Temp, Callbacks)
specs_attributes(callback, Temp, Callbacks);
{ error, _ } ->
Forms
end.
specs_attributes(Type, Forms, Specs) ->
@@ -273,11 +244,10 @@ compile_opts(Module) ->
[] -> []
end.
load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
load_form(Line, Forms, Opts, #elixir_env{file=File} = E) ->
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
EvalS = scope_for_eval(Module, S),
Env = elixir_scope:to_ex_env({ Line, EvalS }),
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
Env = elixir_env:env_to_ex({ Line, E }),
eval_callbacks(Line, Module, after_compile, [Env, Binary], E),
case get(elixir_compiled) of
Current when is_list(Current) ->
@@ -297,8 +267,8 @@ load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
Binary
end).
check_module_availability(Line, File, Module, Compiler) ->
case elixir_compiler:get_opt(ignore_module_conflict, Compiler) of
check_module_availability(Line, File, Module) ->
case elixir_compiler:get_opt(ignore_module_conflict) of
false ->
case code:ensure_loaded(Module) of
{ module, _ } ->
@@ -310,7 +280,6 @@ check_module_availability(Line, File, Module, Compiler) ->
[]
end.
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel', _All) -> ok;
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
warn_invalid_clauses(_Line, File, Module, All) ->
ets:foldl(fun
@@ -337,16 +306,16 @@ warn_unused_docs(Line, File, Module) ->
% EXTRA FUNCTIONS
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
add_info_function(Line, File, Module, Export, Def, Defmacro) ->
Pair = { '__info__', 1 },
case lists:member(Pair, Export) of
true ->
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
false ->
Docs = elixir_compiler:get_opt(docs, C),
Docs = elixir_compiler:get_opt(docs),
{ function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Module, Def, Defmacro),
macros_clause(Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
module_clause(Module),
@@ -357,14 +326,8 @@ add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
functions_clause(Def) ->
{ clause, 0, [{ atom, 0, functions }], [], [elixir_utils:elixir_to_erl(Def)] }.
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(All)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
elixir_dispatch:in_erlang_macros()), Def);
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
macros_clause(Defmacro) ->
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(Defmacro)] }.
module_clause(Module) ->
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
@@ -392,41 +355,41 @@ else_clause() ->
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
{ Binding, S } = elixir_scope:load_binding([], RawS, Module),
eval_callbacks(Line, Module, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Meta = [{line,Line},{require,false}],
lists:foreach(fun({M,F}) ->
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
lists:foldl(fun({M,F}, Acc) ->
{ Expr, ET } = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(_) ->
apply(M, F, Args),
{ { atom, 0, nil }, S }
{ nil, Acc }
end),
case Tree of
{ atom, _, Atom } ->
Atom;
_ ->
if
is_atom(Expr) ->
ET;
true ->
try
erl_eval:exprs([Tree], Binding)
{ _Value, _Binding, EE, _S } = elixir:eval_forms(Expr, [], ET),
EE
catch
Kind:Reason ->
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
Info = { M, F, length(Args), location(Line, E) },
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
end
end
end, Callbacks).
end, E, Callbacks).
%% We've reached the elixir_module or erl_eval internals, skip it with the rest
prune_stacktrace(Info, [{ erl_eval, _, _, _ }|_]) ->
location(Line, E) ->
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
%% We've reached the elixir_module or eval internals, skip it with the rest
prune_stacktrace(Info, [{ elixir, eval_forms, _, _ }|_]) ->
[Info];
prune_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
[Info];
prune_stacktrace(Info, [H|T]) ->
[H|prune_stacktrace(Info, T)];
prune_stacktrace(Info, []) ->
[Info].
@@ -434,22 +397,16 @@ prune_stacktrace(Info, []) ->
format_error({ invalid_clause, { Name, Arity } }) ->
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
format_error({ unused_doc, typedoc }) ->
"@typedoc provided but no type follows it";
format_error({ unused_doc, doc }) ->
"@doc provided but no definition follows it";
format_error({ internal_function_overridden, { Name, Arity } }) ->
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
format_error({ invalid_module, Module}) ->
io_lib:format("invalid module name: ~p", [Module]);
format_error({ module_defined, Module }) ->
io_lib:format("redefining module ~ts", [elixir_errors:inspect(Module)]);
io_lib:format("redefining module ~ts", [elixir_aliases:inspect(Module)]);
format_error({ module_in_definition, Module }) ->
io_lib:format("cannot define module ~ts because it is currently being defined",
[elixir_errors:inspect(Module)]).
[elixir_aliases:inspect(Module)]).
+88 -84
View File
@@ -3,10 +3,9 @@ Nonterminals
expr container_expr block_expr no_parens_expr identifier_expr max_expr
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
op_expr matched_op_expr no_parens_op_expr
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
when_op_eol in_op_eol inc_op_eol
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
open_paren close_paren empty_paren
open_bracket close_bracket
open_curly close_curly
@@ -30,9 +29,8 @@ Terminals
fn 'end' aliases
number signed_number atom atom_string bin_string list_string sigil
dot_call_op op_identifier
comp_op at_op unary_op and_op or_op arrow_op match_op
range_op in_op inc_op when_op than_op default_op tail_op
dual_op add_op mult_op exp_op two_op type_op stab_op
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
dual_op add_op mult_op exp_op two_op type_op stab_op when_op
'true' 'false' 'nil' 'do' eol ',' '.' '&'
'(' ')' '[' ']' '{' '}' '<<' '>>'
.
@@ -46,22 +44,18 @@ Expect 2.
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
Right 30 type_op_eol. %% ::
Right 40 when_op_eol. %% when
Left 50 inc_op_eol. %% inlist, inbits
Right 60 default_op_eol. %% //
Left 70 tail_op_eol. %% |
Right 40 type_op_eol. %% ::
Right 50 in_match_op_eol. %% inlist, inbits, //, | (allowed in matches, lower precedence than =)
Right 70 when_op_eol. %% when
Right 80 match_op_eol. %% =
Left 130 or_op_eol. %% ||, |||, or, xor
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
Left 170 in_op_eol. %% in
Left 200 range_op_eol. %% ..
Right 200 two_op_eol. %% ++, --, **, .., <>
Left 210 add_op_eol. %% + (op), - (op)
Left 220 mult_op_eol. %% * (op), / (op)
Right 230 than_op_eol. %% < (op) > (e.g <>)
Right 240 two_op_eol. %% ++, --, **
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
Left 310 dot_call_op.
@@ -71,12 +65,12 @@ Nonassoc 330 var.
%%% MAIN FLOW OF EXPRESSIONS
grammar -> eol : [nil].
grammar -> expr_list : lists:reverse('$1').
grammar -> eol expr_list : lists:reverse('$2').
grammar -> expr_list eol : lists:reverse('$1').
grammar -> eol expr_list eol : lists:reverse('$2').
grammar -> '$empty' : [nil].
grammar -> eol : nil.
grammar -> expr_list : to_block('$1').
grammar -> eol expr_list : to_block('$2').
grammar -> expr_list eol : to_block('$1').
grammar -> eol expr_list eol : to_block('$2').
grammar -> '$empty' : nil.
% Note expressions are on reverse order
expr_list -> expr : ['$1'].
@@ -87,6 +81,28 @@ expr -> matched_expr : '$1'.
expr -> no_parens_expr : '$1'.
expr -> unmatched_expr : '$1'.
%% In Elixir we have three main call syntaxes: with parentheses,
%% without parentheses and with do blocks. They are represented
%% in the AST as matched, no_parens and unmatched.
%%
%% The distinction is required because we can't, for example, have
%% a function call with a do block as argument inside another do
%% block call, unless there are parentheses:
%%
%% if if true do true else false end do #=> invalid
%% if(if true do true else false end) do #=> valid
%%
%% Similarly, it is not possible to nest calls without parentheses
%% if their arity is more than 1:
%%
%% foo a, bar b, c #=> invalid
%% foo(a, bar b, c) #=> invalid
%% foo a, bar b #=> valid
%% foo a, bar(b, c) #=> valid
%%
%% So the different grammar rules need to take into account
%% if calls without parentheses are do blocks in particular
%% segments and act accordingly.
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
@@ -118,13 +134,9 @@ op_expr -> exp_op_eol expr : { '$1', '$2' }.
op_expr -> two_op_eol expr : { '$1', '$2' }.
op_expr -> and_op_eol expr : { '$1', '$2' }.
op_expr -> or_op_eol expr : { '$1', '$2' }.
op_expr -> tail_op_eol expr : { '$1', '$2' }.
op_expr -> than_op_eol expr : { '$1', '$2' }.
op_expr -> in_op_eol expr : { '$1', '$2' }.
op_expr -> inc_op_eol expr : { '$1', '$2' }.
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
op_expr -> when_op_eol expr : { '$1', '$2' }.
op_expr -> range_op_eol expr : { '$1', '$2' }.
op_expr -> default_op_eol expr : { '$1', '$2' }.
op_expr -> type_op_eol expr : { '$1', '$2' }.
op_expr -> comp_op_eol expr : { '$1', '$2' }.
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
@@ -136,17 +148,16 @@ no_parens_op_expr -> exp_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> two_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> tail_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> than_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> inc_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> when_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> range_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> default_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
%% Allow when (and only when) with keywords
no_parens_op_expr -> when_op_eol no_parens_expr : { '$1', '$2' }.
no_parens_op_expr -> when_op_eol call_args_no_parens_kw : { '$1', '$2' }.
matched_op_expr -> match_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> add_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> mult_op_eol matched_expr : { '$1', '$2' }.
@@ -154,13 +165,9 @@ matched_op_expr -> exp_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> two_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> tail_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> than_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> inc_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> range_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> default_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
@@ -191,19 +198,19 @@ bracket_at_expr -> bracket_at_expr list :
base_expr -> atom : ?exprs('$1').
base_expr -> number : ?exprs('$1').
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> signed_number : { element(4, '$1'), meta('$1'), ?exprs('$1') }.
base_expr -> list : element(1, '$1').
base_expr -> tuple : '$1'.
base_expr -> 'true' : ?id('$1').
base_expr -> 'false' : ?id('$1').
base_expr -> 'nil' : ?id('$1').
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> aliases : { '__aliases__', meta('$1', 0), ?exprs('$1') }.
base_expr -> bin_string : build_bin_string('$1').
base_expr -> list_string : build_list_string('$1').
base_expr -> atom_string : build_atom_string('$1').
base_expr -> bit_string : '$1'.
base_expr -> sigil : build_sigil('$1').
base_expr -> '&' number : { '&', [{line,?line('$1')}], [?exprs('$2')] }.
base_expr -> '&' number : { '&', meta('$1'), [?exprs('$2')] }.
%% Blocks
@@ -237,7 +244,7 @@ stab_expr -> call_args_no_parens_all stab_op_eol stab_maybe_expr :
stab_expr -> stab_parens_many stab_op_eol stab_maybe_expr :
build_op('$2', unwrap_splice('$1'), '$3').
stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
build_op('$4', [{ 'when', meta('$2'), unwrap_splice('$1') ++ ['$3'] }], '$5').
stab_maybe_expr -> 'expr' : '$1'.
stab_maybe_expr -> '$empty' : nil.
@@ -290,9 +297,6 @@ exp_op_eol -> exp_op eol : '$1'.
two_op_eol -> two_op : '$1'.
two_op_eol -> two_op eol : '$1'.
default_op_eol -> default_op : '$1'.
default_op_eol -> default_op eol : '$1'.
type_op_eol -> type_op : '$1'.
type_op_eol -> type_op eol : '$1'.
@@ -310,17 +314,11 @@ and_op_eol -> and_op eol : '$1'.
or_op_eol -> or_op : '$1'.
or_op_eol -> or_op eol : '$1'.
tail_op_eol -> tail_op : '$1'.
tail_op_eol -> tail_op eol : '$1'.
than_op_eol -> than_op : '$1'.
than_op_eol -> than_op eol : '$1'.
in_op_eol -> in_op : '$1'.
in_op_eol -> in_op eol : '$1'.
inc_op_eol -> inc_op : '$1'.
inc_op_eol -> inc_op eol : '$1'.
in_match_op_eol -> in_match_op : '$1'.
in_match_op_eol -> in_match_op eol : '$1'.
when_op_eol -> when_op : '$1'.
when_op_eol -> when_op eol : '$1'.
@@ -328,9 +326,6 @@ when_op_eol -> when_op eol : '$1'.
stab_op_eol -> stab_op : '$1'.
stab_op_eol -> stab_op eol : '$1'.
range_op_eol -> range_op : '$1'.
range_op_eol -> range_op eol : '$1'.
at_op_eol -> at_op : '$1'.
at_op_eol -> at_op eol : '$1'.
@@ -363,7 +358,7 @@ dot_paren_identifier -> paren_identifier : '$1'.
dot_paren_identifier -> matched_expr dot_op paren_identifier : build_dot('$2', '$1', '$3').
parens_call -> dot_paren_identifier : '$1'.
parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }. % Fun/local calls
parens_call -> matched_expr dot_call_op : { '.', meta('$2'), ['$1'] }. % Fun/local calls
% Function calls with no parentheses
@@ -455,14 +450,15 @@ tuple -> open_curly container_expr ',' container_args close_curly : build_tuple
% Bitstrings
bit_string -> open_bit '>>' : { '<<>>', [{line,?line('$1')}], [] }.
bit_string -> open_bit container_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
bit_string -> open_bit '>>' : { '<<>>', meta('$1'), [] }.
bit_string -> open_bit container_args close_bit : { '<<>>', meta('$1'), '$2' }.
Erlang code.
-define(id(Node), element(1, Node)).
-define(line(Node), element(2, Node)).
-define(exprs(Node), element(3, Node)).
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__').
-define(rearrange_uop(Op), Op == 'not' orelse Op == '!').
-define(rearrange_bop(Op), Op == 'in' orelse Op == 'inlist' orelse Op == 'inbits').
@@ -471,28 +467,32 @@ Erlang code.
%% compilation of the generated .erl file by the HiPE compiler
-compile([{hipe,[{regalloc,linear_scan}]}]).
meta(Line, Counter) -> [{counter,Counter}|meta(Line)].
meta(Line) when is_integer(Line) -> [{line,Line}];
meta(Node) -> meta(?line(Node)).
%% Operators
build_op({ _Kind, Line, '/' }, { '&', _, [{ Kind, _, Atom } = Left] }, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
{ '&', meta(Line), [{ '/', meta(Line), [Left, Right] }] };
build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left] }, Right) when is_number(Right) ->
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
{ '&', meta(Line), [{ '/', meta(Line), [Left, Right] }] };
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
{ UOp, meta(Line), [{ BOp, meta(Line), [Left, Right] }] };
build_op({ _Kind, Line, Op }, Left, Right) ->
{ Op, [{line,Line}], [Left, Right] }.
{ Op, meta(Line), [Left, Right] }.
build_unary_op({ _Kind, Line, Op }, Expr) ->
{ Op, [{line,Line}], [Expr] }.
{ Op, meta(Line), [Expr] }.
build_tuple(_Marker, [Left, Right]) ->
{ Left, Right };
build_tuple(Marker, Args) ->
{ '{}', [{line,?line(Marker)}], Args }.
{ '{}', meta(Marker), Args }.
%% Blocks
@@ -505,13 +505,13 @@ build_block(Exprs) -> { '__block__', [], Ex
%% Dots
build_dot_alias(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
{ '__aliases__', [{line,?line(Dot)}], Left ++ Right };
{ '__aliases__', meta(Dot), Left ++ Right };
build_dot_alias(Dot, Other, { 'aliases', _, Right }) ->
{ '__aliases__', [{line,?line(Dot)}], [Other|Right] }.
{ '__aliases__', meta(Dot), [Other|Right] }.
build_dot(Dot, Left, Right) ->
{ '.', [{line,?line(Dot)}], [Left, extract_identifier(Right)] }.
{ '.', meta(Dot), [Left, extract_identifier(Right)] }.
%% Identifiers
@@ -527,13 +527,16 @@ build_identifier({ '.', Meta, _ } = Dot, Args) ->
{ Dot, Meta, FArgs };
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
{ fn, [{line,Line}], Args };
{ fn, meta(Line), Args };
build_identifier({ op_identifier, Line, Identifier }, [Arg]) ->
{ Identifier, [{ambiguous_op,nil},{line,Line}], [Arg] };
{ Identifier, [{ambiguous_op,nil}|meta(Line)], [Arg] };
build_identifier({ _, Line, Identifier }, Args) when ?lexical(Identifier) ->
{ Identifier, meta(Line, 0), Args };
build_identifier({ _, Line, Identifier }, Args) ->
{ Identifier, [{line,Line}], Args }.
{ Identifier, meta(Line), Args }.
extract_identifier({ Kind, _, Identifier }) when
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
@@ -545,35 +548,35 @@ extract_identifier(Other) -> Other.
%% Fn
build_fn(Op, Stab) ->
{ fn, [{line,?line(Op)}], [Stab] }.
{ fn, meta(Op), Stab }.
%% Access
build_access(Expr, { List, Line }) ->
Meta = [{line,Line}],
Meta = meta(Line),
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [Expr, List] }.
%% Interpolation aware
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
Meta = [{line,Line}],
Meta = meta(Line),
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, string_parts(Parts) }, Modifiers ] }.
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) ->
H;
build_bin_string({ bin_string, Line, Args }) ->
{ '<<>>', [{line,Line}], string_parts(Args) }.
{ '<<>>', meta(Line), string_parts(Args) }.
build_list_string({ list_string, _Line, [H] }) when is_binary(H) ->
elixir_utils:characters_to_list(H);
build_list_string({ list_string, Line, Args }) ->
Meta = [{line,Line}],
Meta = meta(Line),
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, string_parts(Args) }] }.
build_atom_string({ atom_string, _Line, Safe, [H] }) when is_binary(H) ->
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
build_atom_string({ atom_string, Line, Safe, Args }) ->
Meta = [{line,Line}],
Meta = meta(Line),
{ { '.', Meta, [erlang, binary_to_atom_op(Safe)] }, Meta, [{ '<<>>', Meta, string_parts(Args) }, utf8] }.
binary_to_atom_op(true) -> binary_to_existing_atom;
@@ -584,35 +587,33 @@ string_parts(Parts) ->
string_part(Binary) when is_binary(Binary) ->
Binary;
string_part({ Line, Tokens }) ->
Form = string_tokens_parse(Line, Tokens),
Meta = [{line,Line}],
Form = string_tokens_parse(Tokens),
Meta = meta(Line),
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
string_tokens_parse(Line, Tokens) ->
string_tokens_parse(Tokens) ->
case parse(Tokens) of
{ ok, [] } -> nil;
{ ok, [Forms] } when not is_list(Forms) -> Forms;
{ ok, Forms } -> { '__block__', [{line, Line}], Forms };
{ ok, Forms } -> Forms;
{ error, _ } = Error -> throw(Error)
end.
%% Keywords
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
{ '->', Meta, build_stab(Meta, T, Left, [Right], []) };
build_stab(Meta, T, Left, [Right], []);
build_stab(Else) ->
build_block(Else).
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
H = { Marker, Old, build_block(lists:reverse(Temp)) },
H = { '->', Old, [Marker, build_block(lists:reverse(Temp))] },
build_stab(New, T, Left, [Right], [H|Acc]);
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
build_stab(Meta, T, Marker, [H|Temp], Acc);
build_stab(Meta, [], Marker, Temp, Acc) ->
H = { Marker, Meta, build_block(lists:reverse(Temp)) },
H = { '->', Meta, [Marker, build_block(lists:reverse(Temp))] },
lists:reverse([H|Acc]).
%% Every time the parser sees a (unquote_splicing())
@@ -635,6 +636,9 @@ unwrap_when(Args) ->
Args
end.
to_block([One]) when not is_list(One) -> One;
to_block(Other) -> { '__block__', [], lists:reverse(Other) }.
%% Errors
throw(Line, Error, Token) ->
+178 -159
View File
@@ -1,47 +1,73 @@
%% Implements Elixir quote.
-module(elixir_quote).
-export([escape/2, erl_escape/3, erl_quote/4,
linify/2, linify/3, unquote/4, tail_join/3, join/2]).
-export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
-export([dot/6, tail_list/3, list/2]). %% Quote callbacks
-include("elixir.hrl").
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__').
-compile({ inline, [keyfind/2, keystore/3, keydelete/2, keyreplace/3, keynew/3] }).
%% Apply the line from site call on quoted contents.
%% Receives a Key to look for the default line as argument.
linify(Line, Exprs) when is_integer(Line) ->
do_linify(Line, nil, Exprs).
do_linify(Line, line, nil, Exprs).
linify(Line, Var, Exprs) when is_integer(Line) ->
do_linify(Line, Var, Exprs).
linify(Line, Key, Exprs) when is_integer(Line) ->
do_linify(Line, Key, nil, Exprs).
do_linify(Line, { Receiver, Counter } = Var, { Left, Meta, Receiver }) when is_atom(Left), is_list(Meta) ->
NewMeta = case keyfind(counter, Meta) of
{ counter, _ } -> Meta;
_ -> keystore(counter, Meta, Counter)
end,
do_tuple_linify(Line, Var, NewMeta, Left, Receiver);
%% Same as linify but also considers the context counter.
linify_with_context_counter(Line, Var, Exprs) when is_integer(Line) ->
do_linify(Line, line, Var, Exprs).
do_linify(Line, Var, { Left, Meta, Right }) when is_list(Meta) ->
do_tuple_linify(Line, Var, Meta, Left, Right);
do_linify(Line, Key, { Receiver, Counter } = Var, { Left, Meta, Receiver })
when is_atom(Left), is_list(Meta), Left /= '_' ->
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver);
do_linify(Line, Var, { Left, Right }) ->
{ do_linify(Line, Var, Left), do_linify(Line, Var, Right) };
do_linify(Line, Key, { _, Counter } = Var, { Lexical, [_|_] = Meta, [_|_] = Args }) when ?lexical(Lexical) ->
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Lexical, Args);
do_linify(Line, Var, List) when is_list(List) ->
[do_linify(Line, Var, X) || X <- List];
do_linify(Line, Key, Var, { Left, Meta, Right }) when is_list(Meta) ->
do_tuple_linify(Line, Key, Var, Meta, Left, Right);
do_linify(_, _, Else) -> Else.
do_linify(Line, Key, Var, { Left, Right }) ->
{ do_linify(Line, Key, Var, Left), do_linify(Line, Key, Var, Right) };
do_tuple_linify(Line, Var, Meta, Left, Right) ->
NewMeta = case ?line(Meta) of
0 -> keystore(line, Meta, Line);
_ -> Meta
end,
{ do_linify(Line, Var, Left), NewMeta, do_linify(Line, Var, Right) }.
do_linify(Line, Key, Var, List) when is_list(List) ->
[do_linify(Line, Key, Var, X) || X <- List];
do_linify(_, _, _, Else) -> Else.
do_tuple_linify(Line, Key, Var, Meta, Left, Right) ->
{ do_linify(Line, Key, Var, Left),
do_linify_meta(Line, Key, Meta),
do_linify(Line, Key, Var, Right) }.
do_linify_meta(0, line, Meta) ->
Meta;
do_linify_meta(Line, line, Meta) ->
case keyfind(line, Meta) of
{ line, Int } when is_integer(Int), Int /= 0 ->
Meta;
_ ->
keystore(line, Meta, Line)
end;
do_linify_meta(Line, Key, Meta) ->
case keyfind(Key, Meta) of
{ Key, Int } when is_integer(Int), Int /= 0 ->
keyreplace(Key, Meta, { line, Int });
_ ->
do_linify_meta(Line, line, Meta)
end.
%% Some expressions cannot be unquoted at compilation time.
%% This function is responsible for doing runtime unquoting.
unquote(Meta, Left, { '__aliases__', _, Args }, nil) ->
dot(Meta, Left, Right, Args, Context, File) ->
annotate(dot(Meta, Left, Right, Args), Context, File).
dot(Meta, Left, { '__aliases__', _, Args }, nil) ->
{ '__aliases__', Meta, [Left|Args] };
unquote(Meta, Left, Right, nil) when is_atom(Right) ->
dot(Meta, Left, Right, nil) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir." ++ _ ->
{ '__aliases__', Meta, [Left, Right] };
@@ -49,71 +75,84 @@ unquote(Meta, Left, Right, nil) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, [] }
end;
unquote(Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
dot(Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
{ { '.', Meta, [Left, Right] }, Meta, [] };
unquote(Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
dot(Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(_Meta, _Left, Right, nil) ->
dot(_Meta, _Left, Right, nil) ->
argument_error(<<"expected unquote after dot to return an atom, an alias or a quoted call, got: ",
('Elixir.Macro':to_string(Right))/binary>>);
unquote(Meta, Left, Right, Args) when is_atom(Right) ->
dot(Meta, Left, Right, Args) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
dot(Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(_Meta, _Left, Right, _Args) ->
dot(_Meta, _Left, Right, _Args) ->
argument_error(<<"expected unquote after dot with args to return an atom or a quoted call, got: ",
('Elixir.Macro':to_string(Right))/binary>>).
join(Left, Right) when is_list(Right) ->
validate_join(Left),
list(Left, Right) when is_list(Right) ->
validate_list(Left),
Left ++ Right.
tail_join(Left, Right, Tail) when is_list(Right), is_list(Tail) ->
validate_join(Left),
tail_list(Left, Right, Tail) when is_list(Right), is_list(Tail) ->
validate_list(Left),
Tail ++ Left ++ Right;
tail_join(Left, Right, Tail) when is_list(Left) ->
validate_join(Left),
tail_list(Left, Right, Tail) when is_list(Left) ->
validate_list(Left),
[H|T] = lists:reverse(Tail ++ Left),
lists:reverse([{ '|', [], [H, Right] }|T]).
validate_join(List) when is_list(List) ->
validate_list(List) when is_list(List) ->
ok;
validate_join(List) when not is_list(List) ->
validate_list(List) when not is_list(List) ->
argument_error(<<"expected a list with quoted expressions in unquote_splicing/1, got: ",
('Elixir.Kernel':inspect(List))/binary>>).
argument_error(Message) ->
error('Elixir.ArgumentError':exception([{message,Message}])).
%% Annotates the AST with context and other info
annotate({ Def, Meta, [{ H, M, A }|T] }, Context, File) when ?defs(Def) ->
%% Store the context information in the first element of the
%% definition tuple so we can access it later on.
MM = keystore(context, keystore(file, M, File), Context),
{ Def, Meta, [{ H, MM, A }|T] };
annotate({ { '.', _, [_, Def] } = Target, Meta, [{ H, M, A }|T] }, Context, File) when ?defs(Def) ->
MM = keystore(context, keystore(file, M, File), Context),
{ Target, Meta, [{ H, MM, A }|T] };
annotate({ Lexical, Meta, [_|_] = Args }, Context, _File) when Lexical == import; Lexical == alias ->
NewMeta = keystore(context, keydelete(counter, Meta), Context),
{ Lexical, NewMeta, Args };
annotate(Tree, _Context, _File) -> Tree.
%% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Unquote) ->
quote(Expr, nil, #elixir_quote{
line=keep,
{ Res, Q } = quote(Expr, nil, #elixir_quote{
line=true,
keep=false,
vars_hygiene=false,
aliases_hygiene=false,
imports_hygiene=false,
unquote=Unquote,
escape=true
}, nil).
erl_escape(Expr, Unquote, S) ->
{ QExpr, TQ } = escape(Expr, Unquote),
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
{ TExpr, TQ, TS }.
}, nil),
{ Res, Q#elixir_quote.unquoted }.
%% Quotes an expression and return its quoted Elixir AST.
quote(Expr, nil, Q, S) ->
do_quote(Expr, Q, S);
quote(Expr, nil, Q, E) ->
do_quote(Expr, Q, E);
quote(Expr, Binding, Q, S) ->
quote(Expr, Binding, Q, E) ->
Context = Q#elixir_quote.context,
Vars = [ { '{}', [],
@@ -123,170 +162,139 @@ quote(Expr, Binding, Q, S) ->
] ]
} || { K, V } <- Binding],
{ TExprs, TQ } = do_quote(Expr, Q, S),
{ TExprs, TQ } = do_quote(Expr, Q, E),
{ { '{}',[], ['__block__',[], Vars ++ [TExprs] ] }, TQ }.
erl_quote(Expr, Binding, Q, S) ->
{ QExpr, TQ } = quote(Expr, Binding, Q, S),
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
{ TExpr, TQ, TS }.
%% Actual quoting and helpers
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S),
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, E) when length(Args) == 1; length(Args) == 2 ->
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, E),
{ TTuple, TQ#elixir_quote{unquote=true} };
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
{ Expr, Q#elixir_quote{unquoted=true} };
%% Context mark
do_quote({ defmodule, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) ->
%% Only store the context if we actually have a full alias as
%% argument, otherwise the expression is being automatically
%% generated and we don't want the alias to count.
NewMeta =
case hd(Args) of
{ '__aliases__', _, Atoms } ->
case lists:all(fun is_atom/1, Atoms) of
true -> keystore(context, Meta, Q#elixir_quote.context);
false -> Meta
end;
_ ->
Meta
end,
do_quote_tuple({ defmodule, NewMeta, Args }, Q, S);
do_quote({ Def, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == alias; Def == import ->
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
do_quote_tuple({ Target, NewMeta, Args }, Q, S);
%% Aliases
do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S) ->
{ TExpr, TQ } = do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S),
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, E) when is_atom(H) and (H /= 'Elixir') ->
Annotation = case elixir_aliases:expand(Alias, E#elixir_env.aliases,
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
Atom when is_atom(Atom) -> Atom;
Aliases when is_list(Aliases) -> false
end,
AliasMeta = keystore(alias, Meta, Annotation),
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
AliasMeta = keystore(alias, keydelete(counter, Meta), Annotation),
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, E);
%% Vars
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, S) when is_atom(Left) ->
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, S);
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, E) when is_atom(Left) ->
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, E);
%% Unquote
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
do_quote_call(Left, Meta, Expr, Args, Q, S);
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, E) ->
do_quote_call(Left, Meta, Expr, Args, Q, E);
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
do_quote_call(Left, Meta, Expr, nil, Q, S);
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, E) ->
do_quote_call(Left, Meta, Expr, nil, Q, E);
%% Imports
do_quote({ '&', Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa('&', Meta, Args, F, A, Q, S);
#elixir_quote{imports_hygiene=true} = Q, E) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa('&', Meta, Args, F, A, Q, E);
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
end,
case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, S) of
NewMeta = case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, E) of
false ->
AmbMeta =
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
_ -> Meta
end,
do_quote_tuple({ Name, AmbMeta, ArgsOrAtom }, Q, S);
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
_ -> Meta
end;
Receiver ->
ImportMeta = keystore(import,
keystore(context, Meta, Q#elixir_quote.context),
Receiver),
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
end;
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
end,
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, S) ->
do_quote_tuple(Tuple, Q, S);
Annotated = annotate({ Name, NewMeta, ArgsOrAtom }, Q#elixir_quote.context, file(E, Q)),
do_quote_tuple(Annotated, Q, E);
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, E) ->
Annotated = annotate(Tuple, Q#elixir_quote.context, file(E, Q)),
do_quote_tuple(Annotated, Q, E);
%% Literals
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, S) when
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, E) when
is_tuple(Left) andalso (element(1, Left) == unquote_splicing);
is_tuple(Right) andalso (element(1, Right) == unquote_splicing) ->
do_quote({ '{}', [], [Left, Right] }, Q, S);
do_quote({ '{}', [], [Left, Right] }, Q, E);
do_quote({ Left, Right }, Q, S) ->
{ TLeft, LQ } = do_quote(Left, Q, S),
{ TRight, RQ } = do_quote(Right, LQ, S),
do_quote({ Left, Right }, Q, E) ->
{ TLeft, LQ } = do_quote(Left, Q, E),
{ TRight, RQ } = do_quote(Right, LQ, E),
{ { TLeft, TRight }, RQ };
do_quote(Tuple, #elixir_quote{escape=true} = Q, S) when is_tuple(Tuple) ->
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, E),
{ { '{}', [], TT }, TQ };
do_quote(List, #elixir_quote{escape=true} = Q, S) when is_list(List) ->
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
% The improper case is pretty inefficient, but improper lists are hopefully
% rare.
case reverse_improper(List) of
{ L } -> do_splice(L, Q, S);
{ L } -> do_splice(L, Q, E);
{ L, R } ->
{ TL, QL } = do_splice(L, Q, S, [], []),
{ TR, QR } = do_quote(R, QL, S),
{ TL, QL } = do_splice(L, Q, E, [], []),
{ TR, QR } = do_quote(R, QL, E),
{ update_last(TL, fun(X) -> { '|', [], [X, TR] } end), QR }
end;
do_quote(List, Q, S) when is_list(List) ->
do_splice(lists:reverse(List), Q, S);
do_quote(List, Q, E) when is_list(List) ->
do_splice(lists:reverse(List), Q, E);
do_quote(Other, Q, _) ->
{ Other, Q }.
%% Quote helpers
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, TAll }, TQ }.
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args,
Q#elixir_quote.context, file(E, Q)],
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, E) end, Q, All),
{ { { '.', Meta, [elixir_quote, dot] }, Meta, TAll }, TQ }.
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
do_quote_fa(Target, Meta, Args, F, A, Q, E) ->
NewMeta =
case (keyfind(import_fa, Meta) == false) andalso
elixir_dispatch:find_import(Meta, F, A, S) of
elixir_dispatch:find_import(Meta, F, A, E) of
false -> Meta;
Receiver -> keystore(import_fa, Meta, { Receiver, Q#elixir_quote.context })
end,
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
do_quote_tuple({ Target, NewMeta, Args }, Q, E).
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
{ TLeft, LQ } = do_quote(Left, Q, S),
{ TRight, RQ } = do_quote(Right, LQ, S),
do_quote_tuple({ Left, Meta, Right }, Q, E) ->
{ TLeft, LQ } = do_quote(Left, Q, E),
{ TRight, RQ } = do_quote(Right, LQ, E),
{ { '{}', [], [TLeft, meta(Meta, Q), TRight] }, RQ }.
meta(Meta, #elixir_quote{line=keep}) ->
file(#elixir_env{file=File}, #elixir_quote{keep=true}) -> File;
file(_, _) -> nil.
meta(Meta, #elixir_quote{keep=true}) ->
[case KV of { line, V } -> { keep, V }; _ -> KV end || KV <- Meta];
meta(Meta, #elixir_quote{line=true}) ->
Meta;
meta(Meta, #elixir_quote{line=nil}) ->
meta(Meta, #elixir_quote{line=false}) ->
keydelete(line, Meta);
meta(Meta, #elixir_quote{line=Line}) ->
keystore(line, Meta, Line).
reverse_improper(L) -> reverse_improper(L, []).
reverse_improper([], Acc) -> { Acc };
reverse_improper([H|T], Acc) when is_list(T) -> reverse_improper(T, [H|Acc]);
reverse_improper([H|T], Acc) -> { [H|Acc], T }.
@@ -299,35 +307,46 @@ keyfind(Key, Meta) ->
lists:keyfind(Key, 1, Meta).
keydelete(Key, Meta) ->
lists:keydelete(Key, 1, Meta).
keystore(_Key, Meta, nil) ->
Meta;
keystore(Key, Meta, Value) ->
lists:keystore(Key, 1, Meta, { Key, Value }).
keyreplace(Key, Meta, { Key, _V }) ->
Meta;
keyreplace(Key, Meta, Tuple) ->
lists:keyreplace(Key, 1, Meta, Tuple).
keynew(Key, Meta, Value) ->
case keyfind(Key, Meta) of
{ Key, _ } -> Meta;
_ -> keystore(Key, Meta, Value)
end.
%% Quote splicing
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, E) ->
%% Process the remaining entries on the list.
%% For [1, 2, 3, unquote_splicing(arg)|tail], this will quote
%% 1, 2 and 3, which could even be unquotes.
{ TT, QT } = do_splice(T, Q, S, [], []),
{ TR, QR } = do_quote(Right, QT, S),
{ do_runtime_join(Meta, tail_join, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
{ TT, QT } = do_splice(T, Q, E, [], []),
{ TR, QR } = do_quote(Right, QT, E),
{ do_runtime_list(Meta, tail_list, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
do_splice(List, Q, S) ->
do_splice(List, Q, S, [], []).
do_splice(List, Q, E) ->
do_splice(List, Q, E, [], []).
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_runtime_join(Meta, join, [Expr, do_join(Buffer, Acc)]));
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, E, Buffer, Acc) ->
do_splice(T, Q#elixir_quote{unquoted=true}, E, [], do_runtime_list(Meta, list, [Expr, do_join(Buffer, Acc)]));
do_splice([H|T], Q, S, Buffer, Acc) ->
{ TH, TQ } = do_quote(H, Q, S),
do_splice(T, TQ, S, [TH|Buffer], Acc);
do_splice([H|T], Q, E, Buffer, Acc) ->
{ TH, TQ } = do_quote(H, Q, E),
do_splice(T, TQ, E, [TH|Buffer], Acc);
do_splice([], Q, _S, Buffer, Acc) ->
do_splice([], Q, _E, Buffer, Acc) ->
{ do_join(Buffer, Acc), Q }.
do_join(Left, []) -> Left;
do_join([], Right) -> Right;
do_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
do_join(Left, Right) -> { { '.', [], [erlang, '++'] }, [], [Left, Right] }.
do_runtime_join(Meta, Fun, Args) ->
do_runtime_list(Meta, Fun, Args) ->
{ { '.', Meta, [elixir_quote, Fun] }, Meta, Args }.
+74 -193
View File
@@ -1,255 +1,136 @@
%% Convenience functions used to manipulate scope and its variables.
-module(elixir_scope).
-export([translate_var/5,
build_erl_var/2, build_ex_var/2,
serialize/1, deserialize/1, to_erl/1,
serialize_with_vars/2, deserialize_with_vars/2,
to_erl_env/1, to_ex_env/1,
load_binding/3, dump_binding/2,
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
-export([translate_var/4, build_var/2,
load_binding/2, dump_binding/2,
mergev/2, mergec/2, mergef/2,
merge_vars/2, merge_opt_vars/2
]).
-include("elixir.hrl").
%% VAR HANDLING
translate_var(Meta, Name, Kind, S, Callback) when is_atom(Kind); is_integer(Kind) ->
Line = ?line(Meta),
Vars = S#elixir_scope.vars,
Temp = lists:keyfind(temp, 1, Meta) == { temp, true },
translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
Line = ?line(Meta),
Tuple = { Name, Kind },
Vars = S#elixir_scope.vars,
case Name of
'_' ->
{ { var, Line, Name }, S };
_ ->
case S#elixir_scope.context of
match ->
TempVars = S#elixir_scope.temp_vars,
case { orddict:is_key(Tuple, Vars), is_list(TempVars) andalso ordsets:is_element(Tuple, TempVars) } of
{ true, true } ->
{ { var, Line, orddict:fetch(Tuple, Vars) }, S };
{ Else, _ } ->
{ NewVar, NS } = if
Kind /= nil -> build_erl_var(Line, S);
Else -> build_erl_var(Line, Name, Name, S);
S#elixir_scope.noname -> build_erl_var(Line, Name, Name, S);
true -> { { var, Line, Name }, S }
end,
RealName = element(3, NewVar),
ClauseVars = S#elixir_scope.clause_vars,
{ NewVar, NS#elixir_scope{
vars=orddict:store(Tuple, RealName, Vars),
temp_vars=if
TempVars == nil -> TempVars;
true -> ordsets:add_element(Tuple, TempVars)
end,
clause_vars=if
ClauseVars == nil; Temp == true -> ClauseVars;
true -> orddict:store(Tuple, RealName, ClauseVars)
end
} }
end;
_ ->
case orddict:find({ Name, Kind }, Vars) of
{ ok, VarName } -> { { var, Line, VarName }, S };
error -> Callback()
end
end
end.
% Handle variables translation
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
New = orddict:update_counter(Key, 1, Counter),
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
build_erl_var(Line, Key, Name, S) when is_integer(Line) ->
{ Counter, NS } = build_var_counter(Key, S),
Var = { var, Line, ?atom_concat([Name, "@", Counter]) },
{ Var, NS }.
build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
Context = case S#elixir_scope.module of
nil -> 'Elixir';
Mod -> Mod
case orddict:find({ Name, Kind }, Vars) of
{ ok, { Current, _ } } -> Exists = true;
error -> Current = nil, Exists = false
end,
{ Counter, NS } = build_var_counter(Key, S),
Var = { ?atom_concat([Name, "@", Counter]), [{line,Line}], Context },
{ Var, NS }.
case S#elixir_scope.context of
match ->
MatchVars = S#elixir_scope.match_vars,
%% Macro.Env <-> #elixir_scope conversion
case Exists andalso ordsets:is_element(Tuple, MatchVars) of
true ->
{ { var, Line, Current }, S };
false ->
%% If the variable is not exported, we use a counter name.
%% The same if the variable already exists or we are in a
%% noname context.
Private = (lists:keyfind(export, 1, Meta) == { export, false }),
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
Requires, Functions, Macros, ContextModules, MacroAliases, _Vars, Lexical }) ->
#elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases,
lexical_tracker=Lexical}.
{ NewVar, Counter, NS } =
if
Kind /= nil ->
build_var('_', S);
Private orelse Exists orelse S#elixir_scope.noname ->
build_var(Name, S);
true ->
{ Name, 0, S }
end,
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases,
vars=Vars,lexical_tracker=Lexical} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
Context, Requires, Functions, Macros, ContextModules, MacroAliases,
list_vars(Vars), Lexical }.
FS = NS#elixir_scope{
vars=orddict:store(Tuple, { NewVar, Counter }, Vars),
match_vars=ordsets:add_element(Tuple, MatchVars),
export_vars=case S#elixir_scope.export_vars of
EV when Private; EV == nil -> EV;
EV -> orddict:store(Tuple, { NewVar, Counter }, EV)
end
},
list_vars(Vars) -> [Pair || { { _, K } = Pair, _ } <- Vars, is_atom(K)].
{ { var, Line, NewVar }, FS }
end;
_ when Exists ->
{ { var, Line, Current }, S }
end.
%% SERIALIZATION
%% When serializing scopes, we support serialization of pids.
to_erl(Structure) ->
elixir_utils:elixir_to_erl(Structure, fun
(X) when is_pid(X) ->
?wrap_call(0, erlang, binary_to_term, [elixir_utils:elixir_to_erl(term_to_binary(X))]);
(Other) ->
error({ badarg, Other })
end).
serialize(S) ->
to_erl({ S#elixir_scope.file, S#elixir_scope.functions,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
S#elixir_scope.context_modules, S#elixir_scope.lexical_tracker }).
serialize_with_vars(Line, S) when is_integer(Line) ->
{ Vars, _ } = orddict:fold(fun({ Key, Kind }, Value, { Acc, Counter }) ->
KindKey = if
is_atom(Kind) -> atom;
is_integer(Kind) -> integer
end,
{ { cons, Line, { tuple, Line, [
{ atom, Line, Key },
{ KindKey, Line, Kind },
{ atom, Line, ?atom_concat(["_@", Counter]) },
{ var, Line, Value }
] }, Acc }, Counter + 1 }
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
{ serialize(S), Vars }.
% Fill in the scope with the variables serialization set in serialize_scope.
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
deserialize_with_vars({ File, Functions, Requires, Macros, Aliases, MacroFunctions,
MacroMacros, MacroAliases, FileModules, LexicalTracker }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
requires=Requires,
macros=Macros,
aliases=Aliases,
macro_functions=MacroFunctions,
macro_macros=MacroMacros,
macro_aliases=MacroAliases,
context_modules=FileModules,
vars=orddict:from_list(Vars),
lexical_tracker=LexicalTracker,
counter=[{'',length(Vars)}]
}.
build_var(Key, S) ->
New = orddict:update_counter(Key, 1, S#elixir_scope.counter),
Cnt = orddict:fetch(Key, New),
{ ?atom_concat([Key, "@", Cnt]), Cnt, S#elixir_scope{counter=New} }.
%% SCOPE MERGING
%% Receives two scopes and return a new scope based on the second
%% with their variables merged.
%% Receives two scopes and return a new scope based on
%% the second with their variables merged.
umergev(S1, S2) ->
V1 = S1#elixir_scope.vars,
V2 = S2#elixir_scope.vars,
C1 = S1#elixir_scope.clause_vars,
C2 = S2#elixir_scope.clause_vars,
mergev(S1, S2) ->
S2#elixir_scope{
vars=merge_vars(V1, V2),
clause_vars=merge_clause_vars(C1, C2)
vars=merge_vars(S1#elixir_scope.vars, S2#elixir_scope.vars),
export_vars=merge_opt_vars(S1#elixir_scope.export_vars, S2#elixir_scope.export_vars)
}.
%% Receives two scopes and return the first scope with
%% counters and flags from the later.
umergec(S1, S2) ->
mergec(S1, S2) ->
S1#elixir_scope{
counter=S2#elixir_scope.counter,
macro_counter=S2#elixir_scope.macro_counter,
extra_guards=S2#elixir_scope.extra_guards,
super=S2#elixir_scope.super,
caller=S2#elixir_scope.caller
}.
%% Receives two scopes and return the later scope
%% keeping the variables from the first (counters,
%% imports and everything else are passed forward).
%% Similar to mergec but does not merge the user vars counter.
umergea(S1, S2) ->
S2#elixir_scope{
vars=S1#elixir_scope.vars,
temp_vars=S1#elixir_scope.temp_vars,
clause_vars=S1#elixir_scope.clause_vars
mergef(S1, S2) ->
S1#elixir_scope{
super=S2#elixir_scope.super,
caller=S2#elixir_scope.caller
}.
% Merge variables trying to find the most recently created.
%% Mergers.
merge_vars(V, V) -> V;
merge_vars(V1, V2) ->
orddict:merge(fun var_merger/3, V1, V2).
merge_clause_vars(nil, _C2) -> nil;
merge_clause_vars(_C1, nil) -> nil;
merge_clause_vars(C, C) -> C;
merge_clause_vars(C1, C2) ->
merge_opt_vars(nil, _C2) -> nil;
merge_opt_vars(_C1, nil) -> nil;
merge_opt_vars(C, C) -> C;
merge_opt_vars(C1, C2) ->
orddict:merge(fun var_merger/3, C1, C2).
var_merger({ Var, _ }, Var, K2) -> K2;
var_merger({ Var, _ }, K1, Var) -> K1;
var_merger(_Var, K1, K2) ->
V1 = var_number(atom_to_list(K1), []),
V2 = var_number(atom_to_list(K2), []),
case V1 > V2 of
true -> K1;
false -> K2
end.
var_number([$@|T], _Acc) -> var_number(T, []);
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
var_merger(_Var, { _, V1 } = K1, { _, V2 }) when V1 > V2 -> K1;
var_merger(_Var, _K1, K2) -> K2.
%% BINDINGS
load_binding(Binding, Scope, Module) ->
{ NewBinding, NewVars, NewCounter } = load_binding(Binding, [], [], 0, Module),
load_binding(Binding, Scope) ->
{ NewBinding, NewVars, NewCounter } = load_binding(Binding, [], [], 0),
{ NewBinding, Scope#elixir_scope{
vars=NewVars,
temp_vars=[],
clause_vars=nil,
counter=[{'',NewCounter}]
counter=[{'_',NewCounter}]
} }.
load_binding([{'_@MODULE',Value}|T], Binding, Vars, Counter, _Module) ->
load_binding(T, Binding, Vars, Counter, Value);
load_binding([{Key,Value}|T], Binding, Vars, Counter, Module) ->
load_binding([{Key,Value}|T], Binding, Vars, Counter) ->
Actual = case Key of
{ _Name, _Kind } -> Key;
Name when is_atom(Name) -> { Name, nil }
end,
InternalName = ?atom_concat(["_@", Counter]),
load_binding(T,
[{InternalName,Value}|Binding],
orddict:store(Actual, InternalName, Vars),
Counter + 1, Module);
load_binding([], Binding, Vars, Counter, Module) ->
{ lists:reverse([{'_@MODULE',Module}|Binding]), Vars, Counter }.
orddict:store(InternalName, Value, Binding),
orddict:store(Actual, { InternalName, 0 }, Vars), Counter + 1);
load_binding([], Binding, Vars, Counter) ->
{ Binding, Vars, Counter }.
dump_binding(Binding, #elixir_scope{vars=Vars}) ->
dump_binding(Vars, Binding, []).
dump_binding([{{Var,Kind}=Key,InternalName}|T], Binding, Acc) when is_atom(Kind) ->
dump_binding([{{ Var, Kind } = Key, { InternalName,_ }}|T], Binding, Acc) when is_atom(Kind) ->
Actual = case Kind of
nil -> Var;
_ -> Key
+18 -8
View File
@@ -1,21 +1,31 @@
-module(elixir_sup).
-behaviour(supervisor).
-export([init/1,start_link/1]).
-export([init/1, start_link/0]).
start_link(Args) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, ok).
init(Args) ->
Supervisors = [
init(ok) ->
Workers = [
{
elixir_code_server_sup,
{ elixir_code_server, start_link, Args },
elixir_code_server,
{ elixir_code_server, start_link, [] },
permanent, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir_code_server] % Modules = [Module] | dynamic
},
{
elixir_counter,
{ elixir_counter, start_link, [] },
permanent, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir_counter] % Modules = [Module] | dynamic
}
],
{ok, {{one_for_one, 3, 10}, Supervisors}}.
{ok, {{one_for_one, 3, 10}, Workers}}.
+48 -72
View File
@@ -4,8 +4,7 @@
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
-define(container(T1, T2),
T1 == ${, T2 == $};
T1 == $[, T2 == $]
T1 == ${, T2 == $}
).
-define(at_op(T),
@@ -25,10 +24,9 @@
-define(two_op(T1, T2),
T1 == $+, T2 == $+;
T1 == $-, T2 == $-;
T1 == $*, T2 == $*).
-define(than_op(T1, T2),
T1 == $<, T2 == $>).
T1 == $*, T2 == $*;
T1 == $<, T2 == $>;
T1 == $., T2 == $.).
-define(mult_op(T),
T == $* orelse T == $/).
@@ -36,9 +34,6 @@
-define(dual_op(T),
T == $+ orelse T == $-).
-define(range_op(T1, T2),
T1 == $., T2 == $.).
-define(arrow_op3(T1, T2, T3),
T1 == $<, T2 == $<, T3 == $<;
T1 == $>, T2 == $>, T3 == $>).
@@ -77,10 +72,10 @@
-define(match_op(T),
T == $=).
-define(tail_op(T),
-define(in_match_op(T),
T == $|).
-define(default_op(T1, T2),
-define(in_match_op2(T1, T2),
T1 == $/, T2 == $/).
-define(stab_op(T1, T2),
@@ -228,13 +223,6 @@ tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
end;
% ## Containers
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Three Token Operators
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
@@ -244,14 +232,13 @@ tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
% ## Two Token Operators
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
?stab_op(T1, T2); ?type_op(T1, T2) ->
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
% ## Single Token Operators
tokenize([$.,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?tail_op(T) ->
?match_op(T); ?in_match_op(T) ->
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
% Dot call
@@ -331,14 +318,13 @@ tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
% ## Two Token Operators
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
?stab_op(T1, T2); ?type_op(T1, T2) ->
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
% ## Single Token Operators
tokenize([$:,T|Rest], Line, Scope, Tokens) when
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
?match_op(T); ?tail_op(T); T == $. ->
?match_op(T); ?in_match_op(T); T == $. ->
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
% End of line
@@ -396,12 +382,6 @@ tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?two_op(T1, T2) ->
handle_op(Rest, Line, two_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?than_op(T1, T2) ->
handle_op(Rest, Line, than_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?range_op(T1, T2) ->
handle_op(Rest, Line, range_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?arrow_op(T1, T2) ->
handle_op(Rest, Line, arrow_op, list_to_atom([T1, T2]), Scope, Tokens);
@@ -414,8 +394,8 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?default_op(T1, T2) ->
handle_op(Rest, Line, default_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op2(T1, T2) ->
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
@@ -447,8 +427,8 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?tail_op(T) ->
handle_op(Rest, Line, tail_op, list_to_atom([T]), Scope, Tokens);
tokenize([T|Rest], Line, Scope, Tokens) when ?in_match_op(T) ->
handle_op(Rest, Line, in_match_op, list_to_atom([T]), Scope, Tokens);
tokenize([$.|Rest], Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
@@ -503,6 +483,7 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Toke
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
tokenize(Rest, Line, Scope, Tokens);
%% TODO: This token is deprecated once continuable heredocs are removed
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, Tokens);
@@ -594,7 +575,7 @@ collect_modifiers(Rest, Buffer) ->
%% Heredocs
extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
case extract_heredoc(Line, T, H) of
case extract_heredoc(Line, T, H, Scope) of
{ error, _ } = Error ->
Error;
{ Body, Rest } ->
@@ -606,8 +587,8 @@ extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
end
end.
extract_heredoc(Line0, Rest0, Marker) ->
case extract_heredoc_line(Rest0, []) of
extract_heredoc(Line0, Rest0, Marker, Scope) ->
case extract_heredoc_header(Rest0, [], {Line0,Scope#elixir_tokenizer.file}) of
{ ok, Extra, Rest1 } ->
%% We prepend a new line so we can transparently remove
%% spaces later. This new line is removed by calling `tl`
@@ -615,42 +596,52 @@ extract_heredoc(Line0, Rest0, Marker) ->
case extract_heredoc_body(Line0, Marker, [$\n|Rest1], []) of
{ Line1, Body, Rest2, Spaces } ->
{ tl(remove_heredoc_spaces(Body, Spaces)), merge_heredoc_extra(Line1, Extra, Rest2) };
{ error, Line1 } ->
heredoc_error(Line1, Line0, Marker)
{ error, ErrorLine } ->
Terminator = [Marker, Marker, Marker],
Message = "missing terminator: ~ts (for heredoc starting at line ~B)",
{ error, { ErrorLine, io_lib:format(Message, [Terminator, Line0]), [] } }
end;
{ error, eof } ->
heredoc_error(Line0, Line0, Marker)
error ->
%% TODO: Test me once the deprecated continuable heredocs are removed
Terminator = [Marker, Marker, Marker],
Message = "heredoc start ~ts must be followed by a new line",
{ error, { Line0, io_lib:format(Message, [Terminator]), [] } }
end.
heredoc_error(ErrorLine, StartLine, Marker) ->
Terminator = [Marker, Marker, Marker],
Message = io_lib:format("missing terminator: ~ts (for heredoc starting at line ~B)", [Terminator, StartLine]),
{ error, { ErrorLine, Message, [] } }.
%% Remove spaces from heredoc based on the position of the final quotes.
remove_heredoc_spaces(Body, 0) ->
lists:reverse([0|Body]);
remove_heredoc_spaces(Body, Spaces) ->
remove_heredoc_spaces([0|Body], [], Spaces, Spaces).
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, ?is_horizontal_space(H) ->
remove_heredoc_spaces([Backtrack,$\n|T], Buffer, Spaces - 1, Original);
remove_heredoc_spaces([$\n=H|T], Buffer, _Spaces, Original) ->
remove_heredoc_spaces(T, [H|Buffer], Original, Original);
remove_heredoc_spaces([H|T], Buffer, Spaces, Original) ->
remove_heredoc_spaces(T, [H|Buffer], Spaces, Original);
remove_heredoc_spaces([], Buffer, _Spaces, _Original) ->
Buffer.
%% Extract the heredoc header.
extract_heredoc_header("\r\n" ++ Rest, Buffer, _Scope) ->
{ ok, [$\n|Buffer], Rest };
extract_heredoc_header("\n" ++ Rest, Buffer, _Scope) ->
{ ok, [$\n|Buffer], Rest };
extract_heredoc_header([H|T], Buffer, {Line,File}) when not ?is_horizontal_space(H) ->
elixir_errors:deprecation([{line,Line}], File, "continuable heredocs are deprecated, parsing will no longer continue on the same line as the heredoc starts"),
extract_heredoc_header(T, [H|Buffer], nil);
extract_heredoc_header([H|T], Buffer, _Scope) ->
extract_heredoc_header(T, [H|Buffer], _Scope);
extract_heredoc_header(_, _, _Scope) ->
error.
%% Extract heredoc body. It returns the heredoc body (in reverse order),
%% the remaining of the document and the number of spaces the heredoc
%% is aligned.
%% TODO: This token is deprecated once continuable heredocs are removed
extract_heredoc_body(_Line, Marker, [{line,NewLine}|Rest], Buffer) ->
extract_heredoc_body(NewLine, Marker, Rest, Buffer);
@@ -659,7 +650,7 @@ extract_heredoc_body(Line, Marker, Rest, Buffer) ->
{ ok, NewBuffer, NewRest } ->
extract_heredoc_body(Line + 1, Marker, NewRest, NewBuffer);
{ ok, NewBuffer, NewRest, Spaces } ->
{ Line + 1, NewBuffer, NewRest, Spaces };
{ Line, NewBuffer, NewRest, Spaces };
{ error, eof } ->
{ error, Line }
end.
@@ -668,13 +659,10 @@ extract_heredoc_body(Line, Marker, Rest, Buffer) ->
extract_heredoc_line("\r\n" ++ Rest, Buffer) ->
{ ok, [$\n|Buffer], Rest };
extract_heredoc_line("\n" ++ Rest, Buffer) ->
{ ok, [$\n|Buffer], Rest };
extract_heredoc_line([H|T], Buffer) ->
extract_heredoc_line(T, [H|Buffer]);
extract_heredoc_line(_, _) ->
{ error, eof }.
@@ -682,31 +670,19 @@ extract_heredoc_line(_, _) ->
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when ?is_horizontal_space(H) ->
extract_heredoc_line(Marker, T, [H|Buffer], Counter + 1);
extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) ->
case next_is_break(T) of
false -> extract_heredoc_line(Rest, Buffer);
Final -> { ok, Buffer, Final, Counter }
end;
extract_heredoc_line(Marker, [Marker,Marker,Marker|T], Buffer, Counter) ->
{ ok, Buffer, T, Counter };
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
extract_heredoc_line(Rest, Buffer).
next_is_break([H|T]) when ?is_horizontal_space(H) -> next_is_break(T);
next_is_break("\r\n" ++ T) -> T;
next_is_break("\n" ++ T) -> T;
next_is_break([]) -> [];
next_is_break(_) -> false.
%% Merge heredoc extra by replying it on the buffer. It also adds
%% a special { line, Line } token to force a line change along the way.
%% TODO: This token is deprecated once continuable heredocs are removed
merge_heredoc_extra(Line, Extra, Buffer) ->
merge_heredoc_extra(Extra, [{line,Line}|Buffer]).
merge_heredoc_extra([H|T], Buffer) ->
merge_heredoc_extra(T, [H|Buffer]);
merge_heredoc_extra([], Buffer) ->
Buffer.
@@ -952,8 +928,8 @@ keyword('or') -> or_op;
keyword('xor') -> or_op;
keyword('when') -> when_op;
keyword('in') -> in_op;
keyword('inlist') -> inc_op;
keyword('inbits') -> inc_op;
keyword('inlist') -> in_match_op;
keyword('inbits') -> in_match_op;
% Block keywords
keyword('after') -> block;
+211 -567
View File
@@ -1,299 +1,125 @@
%% Main entry point for translations. All macros that cannot be
%% overriden are defined in this file.
%% Translate Elixir quoted expressions to Erlang Abstract Format.
%% Expects the tree to be expanded.
-module(elixir_translator).
-export([forms/4, 'forms!'/4]).
-export([translate/2, translate_each/2, translate_arg/2,
translate_args/2, translate_apply/7]).
-import(elixir_scope, [umergev/2, umergec/2, umergea/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4,
compile_error/3, compile_error/4,
assert_function_scope/3, assert_module_scope/3,
assert_no_guard_scope/3, assert_no_match_or_guard_scope/3]).
-export([translate_many/2, translate/2, translate_arg/3, translate_args/2]).
-import(elixir_scope, [mergev/2, mergec/2, mergef/2]).
-import(elixir_errors, [compile_error/3, compile_error/4]).
-include("elixir.hrl").
forms(String, StartLine, File, Opts) ->
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.
translate_many(Forms, S) ->
lists:mapfoldl(fun translate/2, S, Forms).
'forms!'(String, StartLine, File, Opts) ->
case forms(String, StartLine, File, Opts) of
{ ok, Forms } ->
Forms;
{ error, { Line, Error, Token } } ->
elixir_errors:parse_error(Line, File, Error, Token)
end.
%% =
translate(Forms, S) ->
lists:mapfoldl(fun translate_each/2, S, Forms).
%% Assignment operator
translate_each({ '=', Meta, [Left, Right] }, S) ->
assert_no_guard_scope(Meta, '=', S),
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
translate({ '=', Meta, [Left, Right] }, S) ->
{ TRight, SR } = translate(Right, S),
{ TLeft, SL } = elixir_clauses:match(fun translate/2, Left, SR),
{ { match, ?line(Meta), TLeft, TRight }, SL };
%% Containers
translate_each({ C, _, _ } = Original, S) when C == '{}'; C == '<<>>' ->
elixir_literal:translate(Original, S);
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
{ TArgs, SE } = translate_args(Args, S),
{ { tuple, ?line(Meta), TArgs }, SE };
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
elixir_bitstring:translate(Meta, Args, S);
%% Blocks and scope rewriters
translate_each({ '__block__', Meta, [] }, S) ->
{ { atom, ?line(Meta), nil }, S };
translate_each({ '__block__', _Line, [Arg] }, S) ->
translate_each(Arg, S);
translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate(Args, S),
translate({ '__block__', Meta, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate_many(Args, S),
{ { block, ?line(Meta), TArgs }, NS };
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
Old = S#elixir_scope.file,
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
{ TExpr, TS#elixir_scope{file=Old} };
%% Erlang op
translate_each({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
{ TExpr, NS } = translate_each(Expr, S),
{ { op, ?line(Meta), convert_op(Op), TExpr }, NS };
translate({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
{ TExpr, NS } = translate(Expr, S),
{ { op, ?line(Meta), Op, TExpr }, NS };
translate_each({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
assert_no_match_scope_for_bin_op(Meta, Op, S),
translate({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
{ { op, ?line(Meta), convert_op(Op), TLeft, TRight }, NS };
{ { op, ?line(Meta), Op, TLeft, TRight }, NS };
%% Lexical
translate_each({ alias, Meta, [Ref] }, S) ->
translate_each({ alias, Meta, [Ref,[]] }, S);
translate_each({ alias, Meta, [Ref, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, alias, S),
{ TRef, SR } = translate_each(Ref, S),
{ TKV, ST } = translate_opts(Meta, alias, [as, warn], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
translate_alias(Meta, true, Old, TKV, ST);
_ ->
compile_error(Meta, S#elixir_scope.file, "invalid args for alias, expected a compile time atom or alias as argument")
end;
translate_each({ require, Meta, [Ref] }, S) ->
translate_each({ require, Meta, [Ref, []] }, S);
translate_each({ require, Meta, [Ref, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, require, S),
{ TRef, SR } = translate_each(Ref, S),
{ TKV, ST } = translate_opts(Meta, require, [as, warn], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
elixir_aliases:ensure_loaded(Meta, Old, ST),
translate_require(Meta, Old, TKV, ST);
_ ->
compile_error(Meta, S#elixir_scope.file, "invalid args for require, expected a compile time atom or alias as argument")
end;
translate_each({ import, Meta, [Left] }, S) ->
translate_each({ import, Meta, [Left, []]}, S);
translate_each({ import, Meta, [Ref, KV] }, S) ->
assert_no_match_or_guard_scope(Meta, import, S),
{ TRef, SR } = translate_each(Ref, S),
{ TKV, ST } = translate_opts(Meta, import, [only, except, warn], KV, SR),
case TRef of
{ atom, _, Atom } ->
elixir_aliases:ensure_loaded(Meta, Atom, ST),
SF = elixir_import:import(Meta, Atom, TKV, ST),
translate_require(Meta, Atom, TKV, SF);
_ ->
compile_error(Meta, S#elixir_scope.file, "invalid name for import, expected a compile time atom or alias")
end;
translate({ Lexical, _, [_, _] }, S) when Lexical == import; Lexical == alias; Lexical == require ->
{ { atom, 0, nil }, S };
%% Pseudo variables
translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
{ { atom, ?line(Meta), S#elixir_scope.module }, S };
translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(S#elixir_scope.file, S);
translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each(filename:dirname(S#elixir_scope.file), S);
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
{ elixir_scope:to_erl(Env), S };
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
translate({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
%% Aliases
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
Receiver when is_atom(Receiver) ->
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
{ { atom, ?line(Meta), Receiver }, S };
Aliases ->
{ TAliases, SA } = translate_args(Aliases, S),
case lists:all(fun is_atom_tuple/1, TAliases) of
true ->
Atoms = [Atom || { atom, _, Atom } <- TAliases],
Receiver = elixir_aliases:concat(Atoms),
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
{ { atom, ?line(Meta), Receiver }, SA };
false ->
Args = [elixir_utils:list_to_cons(?line(Meta), TAliases)],
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
end
end;
%% Quoting
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
compile_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
case lists:keyfind(do, 1, Opts) of
{ do, Do } ->
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
false ->
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
end;
translate_each({ quote, Meta, [_] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
Exprs =
case lists:keyfind(do, 1, Do) of
{ do, E } -> E;
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
end,
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, bind_quoted],
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
{ hygiene, List } when is_list(List) ->
List;
false ->
[]
end,
Context = case lists:keyfind(context, 1, TKV) of
{ context, Atom } when is_atom(Atom) ->
Atom;
{ context, _ } ->
compile_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected a compile time atom or an alias");
false ->
case S#elixir_scope.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 },
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, TKV) of
{ location, keep } -> { keep, keep };
false -> { nil, nil }
end,
Line = proplists:get_value(line, TKV, DefaultLine),
File = proplists:get_value(file, TKV, DefaultFile),
Scope = case File of
keep -> S#elixir_scope.file;
_ -> File
end,
QExprs = if
is_binary(Scope) ->
{ '__scope__', Meta, [[{file,Scope}],[{do,Exprs}]] };
File == nil ->
Exprs;
true ->
compile_error(Meta, S#elixir_scope.file, "invalid :file for quote, expected a compile time binary")
end,
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, TKV) of
{ bind_quoted, BQ } -> { BQ, false };
false -> { nil, true }
end,
Unquote = case lists:keyfind(unquote, 1, TKV) of
{ unquote, Bool } when is_boolean(Bool) -> Bool;
false -> DefaultUnquote
end,
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(QExprs, Binding, Q, ST),
{ TExprs, TS };
translate_each({ quote, Meta, [_, _] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
translate_each(Arg, S);
translate_each({ 'var!', Meta, [Arg] }, S) ->
translate_each({ 'var!', Meta, [Arg, nil] }, S);
translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name), is_atom(Atom) ->
translate_each({ 'var!', Meta, [Name, Kind] }, S);
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
compile_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing "
"variable or be a part of a match", [Name])
end);
translate_each({ 'var!', Meta, [_, _] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid first argument for var!, expected a var or an atom");
%% Functions
translate_each({ '&', Meta, [Arg] }, S) ->
assert_no_match_or_guard_scope(Meta, '&', S),
elixir_fn:capture(Meta, Arg, S);
translate({ '&', Meta, [{ '/', [], [{ Fun, [], Atom }, Arity] }] }, S)
when is_atom(Fun), is_atom(Atom), is_integer(Arity) ->
{ { 'fun', ?line(Meta), { function, Fun, Arity } }, S };
translate({ '&', Meta, [Arg] }, S) when is_integer(Arg) ->
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
translate_each({ fn, Meta, [{ '->', _, Pairs }] }, S) ->
assert_no_match_or_guard_scope(Meta, 'fn', S),
elixir_fn:fn(Meta, Pairs, S);
translate({ fn, Meta, Clauses }, S) ->
elixir_fn:translate(Meta, Clauses, S);
%% Case
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
Clauses = elixir_clauses:get_pairs(do, KV),
{ TExpr, NS } = translate(Expr, S),
RClauses = case elixir_utils:returns_boolean(TExpr) of
true -> rewrite_case_clauses(Clauses);
false -> Clauses
end,
{ TClauses, TS } = elixir_clauses:clauses(Meta, RClauses, NS),
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
%% Try
translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
S = RS#elixir_scope{noname=true},
Do = proplists:get_value('do', Clauses, nil),
{ TDo, SB } = elixir_translator:translate(Do, S),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, mergec(S, SB)),
After = proplists:get_value('after', Clauses, nil),
{ TAfter, SA } = translate(After, mergec(S, SC)),
Else = elixir_clauses:get_pairs(else, Clauses),
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, unblock(TAfter) }, SF };
%% Receive
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
Do = elixir_clauses:get_pairs(do, KV, true),
case lists:keyfind('after', 1, KV) of
false ->
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do, S),
{ { 'receive', ?line(Meta), TClauses }, SC };
_ ->
After = elixir_clauses:get_pairs('after', KV),
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do ++ After, S),
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
{ _, _, [FExpr], _, FAfter } = TAfter,
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
end;
%% Comprehensions
translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Meta, Kind, Args, S);
%% Super
translate_each({ super, Meta, Args }, S) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, super, S),
translate({ super, Meta, Args }, S) when is_list(Args) ->
Module = assert_module_scope(Meta, super, S),
Function = assert_function_scope(Meta, super, S),
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
@@ -304,389 +130,207 @@ translate_each({ super, Meta, Args }, S) when is_list(Args) ->
length(Args) == Arity ->
translate_args(Args, S);
true ->
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
"arguments as the current function")
compile_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
"arguments as the current function")
end,
Super = elixir_def_overridable:name(Module, Function),
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
translate_each({ 'super?', Meta, [] }, S) ->
Module = assert_module_scope(Meta, 'super?', S),
Function = assert_function_scope(Meta, 'super?', S),
Bool = elixir_def_overridable:is_defined(Module, Function),
{ { atom, ?line(Meta), Bool }, S };
%% Variables
translate_each({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
translate({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
#elixir_scope{extra=fn_match, extra_guards=Extra} = S) when is_atom(Name), is_atom(Kind) ->
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
{ ok, Value } ->
Tuple = { Name, var_kind(VarMeta, Kind) },
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
{ ok, { Value, _Counter } } ->
Line = ?line(Meta),
{ TVar, TS } = translate_each(Var, S),
{ TVar, TS } = translate(Var, S),
Guard = { op, Line, '=:=', { var, ?line(Meta), Value }, TVar },
{ TVar, TS#elixir_scope{extra_guards=[Guard|Extra]} };
error ->
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
end;
translate_each({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
{ ok, Value } ->
translate({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
Tuple = { Name, var_kind(VarMeta, Kind) },
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
{ ok, { Value, _Counter } } ->
{ { var, ?line(Meta), Value }, S };
error ->
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
end;
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
compile_error(Meta, S#elixir_scope.file,
"cannot use ^~ts outside of match clauses", [Name]);
translate({ '_', Meta, Kind }, #elixir_scope{context=match} = S) when is_atom(Kind) ->
{ { var, ?line(Meta), '_' }, S };
translate_each({ '^', Meta, [ Expr ] }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
translate({ '_', Meta, Kind }, S) when is_atom(Kind) ->
compile_error(Meta, S#elixir_scope.file, "unbound variable _");
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S, fun() ->
translate_each({ Name, Meta, [] }, S)
end);
translate({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S);
%% Local calls
translate_each({ '->', Meta, _Args }, S) ->
syntax_error(Meta, S#elixir_scope.file, "unhandled operator ->");
translate_each({ Atom, Meta, Args }, S) when is_atom(Atom), is_list(Meta), is_list(Args) ->
assert_no_ambiguous_op(Atom, Meta, Args, S),
Callback = fun() ->
case S#elixir_scope.context of
match ->
compile_error(Meta, S#elixir_scope.file,
"cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
"function ~ts/~B inside guard", [Atom, Atom, Arity]);
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
[Atom, Arity])
end;
_ ->
translate_local(Meta, Atom, Args, S)
end
end,
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
translate({ Name, Meta, Args }, S) when is_atom(Name), is_list(Meta), is_list(Args) ->
if
S#elixir_scope.context == match ->
compile_error(Meta, S#elixir_scope.file,
"cannot invoke function ~ts/~B inside match", [Name, length(Args)]);
S#elixir_scope.context == guard ->
Arity = length(Args),
File = S#elixir_scope.file,
case Arity of
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
"function ~ts/~B inside guard", [Name, Name, Arity]);
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
[Name, Arity])
end;
S#elixir_scope.function == nil ->
compile_error(Meta, S#elixir_scope.file, "undefined function ~ts/~B", [Name, length(Args)]);
true ->
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, S),
{ { call, Line, { atom, Line, Name }, TArgs }, NS }
end;
%% Remote calls
translate_each({ { '.', _, [Left, Right] }, Meta, Args }, S)
translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
{ TLeft, SL } = translate_each(Left, S),
{ TLeft, SL } = translate(Left, S),
{ TRight, SR } = translate(Right, mergec(S, SL)),
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
Callback = fun() -> translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) end,
case TLeft of
{ atom, _, Receiver } ->
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
case S#elixir_scope.context of
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
_ ->
Callback()
end
end);
case S#elixir_scope.context of
Context when Left /= erlang, (Context == match) orelse (Context == guard) ->
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
['Elixir.Macro':to_string(Left), Right, length(Args), Context]);
_ ->
case S#elixir_scope.context of
Context when Context == match; Context == guard ->
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
[Right, length(Args), Context]);
_ ->
Callback()
end
Line = ?line(Meta),
{ TArgs, SA } = translate_args(Args, mergec(S, SR)),
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, mergev(SL, mergev(SR, SA)) }
end;
%% Anonymous function calls
translate_each({ { '.', _, [Expr] }, Meta, Args }, S) when is_list(Args) ->
{ TExpr, SE } = translate_each(Expr, S),
case TExpr of
{ atom, _, Atom } ->
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
_ ->
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
{ {call, ?line(Meta), TExpr, TArgs}, umergev(SE, SA) }
end;
%% Invalid calls
translate_each({ { '.', _, [Invalid, _] }, Meta, Args }, S) when is_list(Meta) and is_list(Args) ->
syntax_error(Meta, S#elixir_scope.file, "invalid remote call on ~ts",
['Elixir.Macro':to_string(Invalid)]);
translate_each({ _, Meta, Args } = Invalid, S) when is_list(Meta) and is_list(Args) ->
syntax_error(Meta, S#elixir_scope.file, "invalid call ~ts",
['Elixir.Macro':to_string(Invalid)]);
translate_each({ _, _, _ } = Tuple, S) ->
syntax_error([{line,0}], S#elixir_scope.file, "expected a valid quoted expression, got: ~ts",
['Elixir.Kernel':inspect(Tuple, [{raw,true}])]);
translate({ { '.', _, [Expr] }, Meta, Args }, S) when is_list(Args) ->
{ TExpr, SE } = translate(Expr, S),
{ TArgs, SA } = translate_args(Args, mergec(S, SE)),
{ { call, ?line(Meta), TExpr, TArgs }, mergev(SE, SA) };
%% Literals
translate_each(Literal, S) ->
elixir_literal:translate(Literal, S).
translate(List, S) when is_list(List) ->
Fun = case S#elixir_scope.context of
match -> fun translate/2;
_ -> fun(X, Acc) -> translate_arg(X, Acc, S) end
end,
translate_list(List, Fun, S, []);
translate({ Left, Right }, S) ->
{ TArgs, SE } = translate_args([Left, Right], S),
{ { tuple, 0, TArgs }, SE };
translate(Other, S) ->
{ elixir_utils:elixir_to_erl(Other), S }.
%% Helpers
translate_list([{ '|', _, [_, _]=Args}], Fun, Acc, List) ->
{ [TLeft,TRight], TAcc } = lists:mapfoldl(Fun, Acc, Args),
{ build_list([TLeft|List], TRight), TAcc };
translate_list([H|T], Fun, Acc, List) ->
{ TH, TAcc } = Fun(H, Acc),
translate_list(T, Fun, TAcc, [TH|List]);
translate_list([], _Fun, Acc, List) ->
{ build_list(List, { nil, 0 }), Acc }.
build_list([H|T], Acc) ->
build_list(T, { cons, 0, H, Acc });
build_list([], Acc) ->
Acc.
var_kind(Meta, Kind) ->
case lists:keyfind(counter, 1, Meta) of
{ counter, Counter } -> Counter;
false -> Kind
end.
%% Opts
%% Case
translate_opts(Meta, Kind, Allowed, Opts, S) ->
{ Expanded, TS } = case literal_opts(Opts) orelse skip_expansion(S#elixir_scope.module) of
true -> { Opts, S };
false -> 'Elixir.Macro':expand_all(Opts, elixir_scope:to_ex_env({ ?line(Meta), S }), S)
end,
validate_opts(Meta, Kind, Allowed, Expanded, TS),
{ Expanded, TS }.
skip_expansion('Elixir.Kernel') -> true;
skip_expansion('Elixir.Kernel.Typespec') -> true;
skip_expansion(_) -> false.
literal_opts({ X, Y }) -> literal_opts(X) andalso literal_opts(Y);
literal_opts(Opts) when is_list(Opts) -> lists:all(fun literal_opts/1, Opts);
literal_opts(Opts) when is_atom(Opts); is_number(Opts); is_bitstring(Opts) -> true;
literal_opts(_) -> false.
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
[begin
compile_error(Meta, S#elixir_scope.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]).
%% Quote
expand_quote_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
expand_quote_context(Meta, Alias, Msg, S) ->
case translate_each(Alias, S) of
{ { atom, _, Atom }, _ } ->
Atom;
rewrite_case_clauses([
{do,Meta1,[{'when',_,[{V,M,C},{'__op__',_,['orelse',_,_]}]}],False},
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
case lists:keyfind('cond', 1, M) of
{'cond',true} ->
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
_ ->
compile_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
end.
%% Require
translate_require(Meta, Old, TKV, S) ->
SF = S#elixir_scope{
requires=ordsets:add_element(Old, S#elixir_scope.requires)
},
translate_alias(Meta, false, Old, TKV, SF).
%% Aliases
translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
New = case lists:keyfind(as, 1, TKV) of
{ as, true } ->
elixir_aliases:last(Old);
{ as, false } ->
Old;
{ as, Atom } when is_atom(Atom) ->
Atom;
false ->
if
IncludeByDefault -> elixir_aliases:last(Old);
true -> Old
end;
_ ->
compile_error(Meta, S#elixir_scope.file,
"invalid :as for alias, expected a compile time atom or alias")
end,
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
true -> ok;
false -> compile_error(Meta, S#elixir_scope.file,
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
end,
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, TKV, S) }.
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
Clauses
end;
rewrite_case_clauses(Clauses) ->
Clauses.
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.
is_atom_tuple({ atom, _, _ }) -> true;
is_atom_tuple(_) -> false.
%% Locals
translate_local(Meta, Name, Args, #elixir_scope{local=nil,function=nil} = S) ->
compile_error(Meta, S#elixir_scope.file, "function ~ts/~B undefined", [Name, length(Args)]);
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
Line = ?line(Meta),
{ TArgs, NS } = translate_args(Args, S),
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
translate_local(Meta, Name, Args, S) ->
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
%% Pack a list of expressions from a block.
unblock({ 'block', _, Exprs }) -> Exprs;
unblock(Expr) -> [Expr].
%% Translate args
%% Variables in arguments are not propagated from one
%% argument to the other. For instance:
%%
%% x = 1
%% foo(x = x + 2, x)
%% x
%%
%% Should be the same as:
%%
%% foo(3, 1)
%% 3
%%
%% However, notice that if we are doing an assignment,
%% it behaves the same as translate.
translate_arg(Arg, { Acc, S }) ->
{ TArg, TAcc } = translate_each(Arg, Acc),
{ TArg, { umergea(Acc, TAcc), umergev(S, TAcc) } }.
translate_arg(Arg, Acc, S) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
{ TArg, _ } = translate(Arg, S),
{ TArg, Acc };
translate_arg(Arg, Acc, S) ->
{ TArg, TAcc } = translate(Arg, mergec(S, Acc)),
{ TArg, mergev(Acc, TAcc) }.
translate_args(Args, #elixir_scope{context=match} = S) ->
translate(Args, S);
translate_many(Args, S);
translate_args(Args, S) ->
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
{ TArgs, umergea(SV, SC) }.
%% Translate apply
%% Used by both apply and external function invocation macros.
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) ->
Line = ?line(Meta),
Optimize = case (Args == []) orelse lists:last(Args) of
{ '|', _, _ } -> false;
_ ->
case TRight of
{ atom, _, _ } -> true;
_ -> false
end
end,
case Optimize of
true ->
%% Register the remote
case TLeft of
{ atom, _, Receiver } ->
Tuple = { element(3, TRight), length(Args) },
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function);
_ ->
ok
end,
{ TArgs, SA } = translate_args(Args, umergec(S, SR)),
FS = umergev(SL, umergev(SR,SA)),
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, FS };
false ->
{ TArgs, SA } = translate_each(Args, umergec(S, SR)),
FS = umergev(SL, umergev(SR,SA)),
{ ?wrap_call(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
end.
%% __op__ helpers
assert_no_match_scope_for_bin_op(Meta, Op, S)
when Op == 'and'; Op == 'or' ->
elixir_errors:assert_no_match_scope(Meta, Op, S);
assert_no_match_scope_for_bin_op(_Meta, _Op, _S) -> ok.
convert_op('and') -> 'andalso';
convert_op('or') -> 'orelse';
convert_op('!==') -> '=/=';
convert_op('===') -> '=:=';
convert_op('!=') -> '/=';
convert_op('<=') -> '=<';
convert_op('<-') -> '!';
convert_op(Else) -> Else.
assert_no_ambiguous_op(Name, Meta, [Arg], S) ->
case lists:keyfind(ambiguous_op, 1, Meta) of
{ ambiguous_op, Kind } ->
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
error -> ok;
_ ->
syntax_error(Meta, S#elixir_scope.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])
end;
_ -> ok
end;
assert_no_ambiguous_op(_Atom, _Meta, _Args, _S) -> ok.
lists:mapfoldl(fun(X, Acc) -> translate_arg(X, Acc, S) end, S, Args).
%% Comprehensions
translate_comprehension(Meta, Kind, Args, S) ->
case elixir_utils:split_last(Args) of
{ Cases, [{do,Expr}] } ->
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
{ { Kind, ?line(Meta), TExpr, TCases }, umergec(S, SE) };
_ ->
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in comprehension ~s", [Kind])
end.
{ Cases, [{do,Expr}] } = elixir_utils:split_last(Args),
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
{ { Kind, ?line(Meta), TExpr, TCases }, mergef(S, SE) }.
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->
translate_each(Expr, S);
translate(Expr, S);
translate_comprehension_do(Meta, bc, _Expr, S) ->
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
translate_each(Expr, S).
translate(Expr, S).
translate_comprehension_clause(_Meta, {inbits, Meta, [{ '<<>>', _, _} = Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ TRight, SR } = translate(Right, S),
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ TRight, SR } = translate(Right, S),
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{ { generate, ?line(Meta), TLeft, TRight }, SL };
translate_comprehension_clause(Meta, X, S) ->
Line = ?line(Meta),
{ TX, TS } = translate_each(X, S),
{ BX, BS } = elixir_utils:convert_to_boolean(Line, TX, true, false, TS),
{ TX, TS } = translate(X, S),
{ BX, BS } = elixir_utils:convert_to_boolean(Line, TX, true, TS),
{ { match, Line, { var, Line, '_' }, BX }, BS }.
%% Assertions
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
compile_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}) ->
compile_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
+89 -140
View File
@@ -1,12 +1,14 @@
-module(elixir_try).
-export([clauses/3, format_error/1]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
clauses(Meta, Clauses, S) ->
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
Rescue = elixir_clauses:get_pairs(Meta, rescue, Clauses, S),
Transformer = fun(X, Acc) -> each_clause(X, umergec(S, Acc)) end,
clauses(_Meta, Clauses, S) ->
Catch = elixir_clauses:get_pairs('catch', Clauses),
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
Transformer = fun(X, SAcc) ->
{ TX, TS } = each_clause(X, SAcc),
{ TX, elixir_scope:mergec(S, TS) }
end,
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
@@ -17,87 +19,46 @@ each_clause({ 'catch', Meta, Raw, Expr }, S) ->
[X,Y] -> [X, Y, { '_', Meta, nil }];
[_,_,_] -> Args;
_ ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
elixir_errors:compile_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
end,
Condition = { '{}', Meta, Final },
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
Condition = [{ '{}', Meta, Final }],
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_many/2, Condition, Expr, Guards, S);
each_clause({ rescue, Meta, [Condition], Expr }, S) ->
case normalize_rescue(Meta, Condition, S) of
{ Left, Right } ->
case Left of
{ '_', _, _ } ->
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
each_clause({ 'catch', Meta, Clause, Expr }, CS);
_ ->
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
case Safe of
true ->
each_clause({ 'catch', Meta, Clause, Expr }, S);
false ->
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
Match = { '=', Meta, [
Left,
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
] },
FinalExpr = prepend_to_block(Meta, Match, Expr),
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
end
end;
each_clause({ rescue, Meta, [{ in, _, [Left, Right]}], Expr }, S) ->
case Left of
{ '_', _, LAtom } when is_atom(LAtom) ->
{ VarName, _, CS } = elixir_scope:build_var('_', S),
{ Clause, _ } = rescue_guards(Meta, { VarName, Meta, nil }, Right, S),
each_clause({ 'catch', Meta, Clause, Expr }, CS);
_ ->
each_clause({ 'catch', Meta, [error, Condition], Expr }, S)
end;
each_clause({rescue,Meta,_,_}, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
each_clause({Key,Meta,_,_}, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
%% Helpers
%% rescue [Error] -> _ in [Error]
normalize_rescue(Meta, List, S) when is_list(List) ->
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, List] }, S);
%% rescue _
normalize_rescue(_, { '_', _, Atom }, _S) when is_atom(Atom) ->
false;
%% rescue var -> var in _
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom) ->
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
%% rescue var in [Exprs]
normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
case Right of
{ '_', _, _ } ->
{ Left, nil };
_ when is_list(Right) ->
is_valid_rescue_list(Right, S) andalso { Left, Right };
_ ->
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
case is_valid_rescue_list(Expanded, ES) of
true -> { Left, Expanded };
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
case Safe of
true ->
each_clause({ 'catch', Meta, Clause, Expr }, S);
false ->
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
{ VarName, _, CS } = elixir_scope:build_var('_', S),
ClauseVar = { VarName, Meta, nil },
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
Match = { '=', Meta, [
Left,
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
] },
FinalExpr = prepend_to_block(Meta, Match, Expr),
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
end
end;
normalize_rescue(Meta, Condition, S) ->
case elixir_translator:translate_each(Condition, S#elixir_scope{context=match}) of
{ { atom, _, Atom }, _ } ->
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
_ ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should "
"match on an alias, a variable or be in the `var in [alias]` format")
end.
each_clause({rescue,Meta,_,_}, S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid arguments for rescue in try");
each_clause({Key,Meta,_,_}, S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
%% Helpers
%% Convert rescue clauses into guards.
rescue_guards(_, Var, nil, _) -> { [error, Var], false };
rescue_guards(_, Var, { '_', _, _ }, _) -> { [error, Var], false };
rescue_guards(Meta, Var, Guards, S) ->
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
@@ -106,15 +67,15 @@ rescue_guards(Meta, Var, Guards, S) ->
Final = case Elixir == [] of
true -> Erlang;
false ->
IsTuple = { is_tuple, Meta, [Var] },
IsException = { '==', Meta, [
{ { '.', Meta, [erlang, element] }, Meta, [2, Var] }, '__exception__'
IsTuple = { erl(Meta, is_tuple), Meta, [Var] },
IsException = { erl(Meta, '=='), Meta, [
{ erl(Meta, element), Meta, [2, Var] }, '__exception__'
] },
OrElse = join(Meta, 'or', Elixir),
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
OrElse = join(Meta, fun do_or/3, Elixir),
[join(Meta, fun do_and/3, [IsTuple, IsException, OrElse])|Erlang]
end,
{
[{ 'when', Meta, [error, Var, reverse_join(Meta, 'when', Final)] }],
[{ 'when', Meta, [error, Var, join_when(Meta, Final)] }],
Safe
}.
@@ -128,11 +89,10 @@ rescue_each_var(Meta, ClauseVar, Guards) ->
false ->
Elixir = [exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
Erlang = lists:map(fun(Rescue) ->
Compares = [{ '==', Meta, [Rescue, Var] } || Var <- Vars],
{ 'and', Meta, [
erlang_rescue_guard_for(Meta, ClauseVar, Rescue),
join(Meta, 'or', Compares)
] }
Compares = [{ erl(Meta, '=='), Meta, [Rescue, Var] } || Var <- Vars],
do_and(Meta,
erlang_rescue_guard_for(Meta, ClauseVar, Rescue),
join(Meta, fun do_or/3, Compares))
end, erlang_rescues()),
{ Elixir, Erlang }
end.
@@ -152,17 +112,16 @@ rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
H == 'Elixir.SystemLimitError' ->
Expr = { 'or', Meta, [
erlang_rescue_guard_for(Meta, Var, H),
exception_compare(Meta, Var, H)
] },
Expr = do_or(Meta,
erlang_rescue_guard_for(Meta, Var, H),
exception_compare(Meta, Var, H)),
rescue_each_ref(Meta, Var, T, Elixir, [Expr|Erlang], false, S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
case elixir_translator:translate_each(H, S) of
case elixir_translator:translate(H, S) of
{ { atom, _, Atom }, _ } ->
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
_ ->
@@ -182,65 +141,58 @@ erlang_rescues() ->
].
erlang_rescue_guard_for(Meta, Var, List) when is_list(List) ->
join(Meta, 'or', [erlang_rescue_guard_for(Meta, Var, X) || X <- List]);
join(Meta, fun do_or/3, [erlang_rescue_guard_for(Meta, Var, X) || X <- List]);
erlang_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
{ '==', Meta, [Var, undef] };
{ erl(Meta, '=='), Meta, [Var, undef] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.FunctionClauseError') ->
{ '==', Meta, [Var, function_clause] };
{ erl(Meta, '=='), Meta, [Var, function_clause] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
{ '==', Meta, [Var, system_limit] };
{ erl(Meta, '=='), Meta, [Var, system_limit] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
{ '==', Meta, [Var, badarith] };
{ erl(Meta, '=='), Meta, [Var, badarith] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, badarity)
] };
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, badarity));
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, badfun)
] };
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, badfun));
erlang_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, badmatch)
] };
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, badmatch));
erlang_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, case_clause)
] };
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, case_clause));
erlang_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, try_clause)
] };
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, try_clause));
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
{ 'or', Meta, [
{ '==', Meta, [Var, badarg] },
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, badarg)
] }
] };
do_or(Meta,
{ erl(Meta, '=='), Meta, [Var, badarg] },
do_and(Meta,
{ erl(Meta, is_tuple), Meta, [Var] },
exception_compare(Meta, Var, badarg)));
erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
IsNotTuple = { 'not', Meta, [{ is_tuple, Meta, [Var] }] },
IsException = { '!=', Meta, [
{ { '.', Meta, [ erlang, element ] }, Meta, [2, Var] }, '__exception__'
IsNotTuple = { erl(Meta, 'not'), Meta, [{ erl(Meta, is_tuple), Meta, [Var] }] },
IsException = { erl(Meta, '/='), Meta, [
{ erl(Meta, element), Meta, [2, Var] }, '__exception__'
] },
{ 'or', Meta, [IsNotTuple, IsException] }.
do_or(Meta, IsNotTuple, IsException).
%% Helpers
@@ -253,27 +205,24 @@ format_error({ rescue_no_match, Var, Alias }) ->
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
is_var(_) -> false.
is_erl_var_or_atom({ Kind, Line, Name }) when Kind == var orelse Kind == atom, is_integer(Line), is_atom(Name) -> true;
is_erl_var_or_atom(_) -> false.
is_valid_rescue_list(Right, S) when is_list(Right) ->
{ TRight, _ } = elixir_translator:translate(Right, S),
lists:all(fun is_erl_var_or_atom/1, TRight).
exception_compare(Meta, Var, Expr) ->
{ '==', Meta, [
{ { '.', Meta, [ erlang, element ] }, Meta, [1, Var] },
{ erl(Meta, '=='), Meta, [
{ erl(Meta, element), Meta, [1, Var] },
Expr
] }.
join(Meta, Kind, [H|T]) ->
lists:foldl(fun(X, Acc) -> { Kind, Meta, [Acc, X] } end, H, T).
lists:foldl(fun(X, Acc) -> Kind(Meta, Acc, X) end, H, T).
reverse_join(Meta, Kind, [H|T]) ->
lists:foldl(fun(X, Acc) -> { Kind, Meta, [X, Acc] } end, H, T).
join_when(Meta, [H|T]) ->
lists:foldl(fun(X, Acc) -> { 'when', Meta, [X, Acc] } end, H, T).
prepend_to_block(_Meta, Expr, { '__block__', Meta, Args }) ->
{ '__block__', Meta, [Expr|Args] };
prepend_to_block(Meta, Expr, Args) ->
{ '__block__', Meta, [Expr, Args] }.
erl(Meta, Op) -> { '.', Meta, [erlang, Op] }.
do_or(Meta, Left, Right) -> { '__op__', Meta, ['orelse', Left, Right] }.
do_and(Meta, Left, Right) -> { '__op__', Meta, ['andalso', Left, Right] }.
+35 -63
View File
@@ -1,11 +1,9 @@
%% Convenience functions used throughout elixir source code
%% for ast manipulation and querying.
-module(elixir_utils).
-export([elixir_to_erl/1, elixir_to_erl/2,
get_line/1, split_last/1, elixir_to_erl/3,
-export([elixir_to_erl/1, get_line/1, split_last/1,
characters_to_list/1, characters_to_binary/1,
cons_to_list/1, list_to_cons/2, list_to_cons/3,
convert_to_boolean/5, returns_boolean/1,
convert_to_boolean/4, returns_boolean/1,
file_type/1, file_type/2, relative_to_cwd/1]).
-include("elixir.hrl").
-include_lib("kernel/include/file.hrl").
@@ -52,58 +50,34 @@ characters_to_binary(Data) ->
false -> 'Elixir.String':'from_char_list!'(Data)
end.
%% List conversion
cons_to_list({ nil, _ }) -> [];
cons_to_list({ cons, _, Left, Right }) -> [Left|cons_to_list(Right)].
list_to_cons(Line, List) ->
list_to_cons(Line, List, { nil, Line }).
list_to_cons(Line, List, Tail) ->
lists:foldr(fun(X, Acc) ->
{ cons, Line, X, Acc }
end, Tail, List).
%% erl <-> elixir
elixir_to_erl(Tree) ->
elixir_to_erl(Tree, fun(X) -> error({ badarg, X }) end).
elixir_to_erl(Tree) when is_tuple(Tree) ->
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
elixir_to_erl(Line, Tree, S) ->
elixir_to_erl(Tree, fun
(X) when is_function(X) ->
elixir_errors:compile_error(Line, S#elixir_scope.file,
"anonymous functions cannot be translated into a quoted expression, got: ~ts",
['Elixir.Kernel':inspect(X)]);
(X) ->
elixir_errors:compile_error(Line, S#elixir_scope.file,
"cannot translate ~ts into a quoted expression",
['Elixir.Kernel':inspect(X)])
end).
elixir_to_erl(Tree, Fun) when is_tuple(Tree) ->
{ tuple, 0, [elixir_to_erl(X, Fun) || X <- tuple_to_list(Tree)] };
elixir_to_erl([], _Fun) ->
elixir_to_erl([]) ->
{ nil, 0 };
elixir_to_erl(Tree, Fun) when is_list(Tree) ->
elixir_to_erl_cons_1(Tree, [], Fun);
elixir_to_erl(Tree) when is_list(Tree) ->
elixir_to_erl_cons_1(Tree, []);
elixir_to_erl(Tree, _Fun) when is_atom(Tree) ->
elixir_to_erl(Tree) when is_atom(Tree) ->
{ atom, 0, Tree };
elixir_to_erl(Tree, _Fun) when is_integer(Tree) ->
elixir_to_erl(Tree) when is_integer(Tree) ->
{ integer, 0, Tree };
elixir_to_erl(Tree, _Fun) when is_float(Tree) ->
elixir_to_erl(Tree) when is_float(Tree) ->
{ float, 0, Tree };
elixir_to_erl(Tree, _Fun) when is_binary(Tree) ->
elixir_to_erl(Tree) when is_binary(Tree) ->
%% Note that our binaries are utf-8 encoded and we are converting
%% to a list using binary_to_list. The reason for this is that Erlang
%% considers a string in a binary to be encoded in latin1, so the bytes
%% are not changed in any fashion.
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] };
elixir_to_erl(Function, Fun) when is_function(Function) ->
elixir_to_erl(Function) when is_function(Function) ->
case (erlang:fun_info(Function, type) == { type, external }) andalso
(erlang:fun_info(Function, env) == { env, [] }) of
true ->
@@ -116,18 +90,21 @@ elixir_to_erl(Function, Fun) when is_function(Function) ->
{ atom, 0, Name },
{ integer, 0, Arity } } };
false ->
Fun(Function)
error(badarg)
end;
elixir_to_erl(Other, Fun) ->
Fun(Other).
elixir_to_erl(Pid) when is_pid(Pid) ->
?wrap_call(0, erlang, binary_to_term, [elixir_utils:elixir_to_erl(term_to_binary(Pid))]);
elixir_to_erl_cons_1([H|T], Acc, Fun) -> elixir_to_erl_cons_1(T, [H|Acc], Fun);
elixir_to_erl_cons_1(Other, Acc, Fun) -> elixir_to_erl_cons_2(Acc, elixir_to_erl(Other, Fun), Fun).
elixir_to_erl(_Other) ->
error(badarg).
elixir_to_erl_cons_2([H|T], Acc, Fun) ->
elixir_to_erl_cons_2(T, { cons, 0, elixir_to_erl(H, Fun), Acc }, Fun);
elixir_to_erl_cons_2([], Acc, _Fun) ->
elixir_to_erl_cons_1([H|T], Acc) -> elixir_to_erl_cons_1(T, [H|Acc]);
elixir_to_erl_cons_1(Other, Acc) -> elixir_to_erl_cons_2(Acc, elixir_to_erl(Other)).
elixir_to_erl_cons_2([H|T], Acc) ->
elixir_to_erl_cons_2(T, { cons, 0, elixir_to_erl(H), Acc });
elixir_to_erl_cons_2([], Acc) ->
Acc.
%% Boolean checks
@@ -164,20 +141,21 @@ returns_boolean({ 'case', _, _, Clauses }) ->
returns_boolean(_) -> false.
convert_to_boolean(Line, Expr, Bool, InGuard, S) when is_integer(Line) ->
convert_to_boolean(Line, Expr, Bool, S) when is_integer(Line) ->
case { returns_boolean(Expr), Bool } of
{ true, true } -> { Expr, S };
{ true, false } -> { { op, Line, 'not', Expr }, S };
_ -> do_convert_to_boolean(Line, Expr, Bool, InGuard, S)
_ -> do_convert_to_boolean(Line, Expr, Bool, S)
end.
%% Notice we use a temporary var and bundle nil
%% and false checks in the same clause since
%% it makes dialyzer happy.
do_convert_to_boolean(Line, Expr, Bool, false, S) ->
Any = { var, Line, '_' },
{ Var, TS } = elixir_scope:build_erl_var(Line, S),
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
do_convert_to_boolean(Line, Expr, Bool, S) ->
{ Name, _, TS } = elixir_scope:build_var('_', S),
Var = { var, Line, Name },
Any = { var, Line, '_' },
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
FalseResult = { atom,Line,not Bool },
TrueResult = { atom,Line,Bool },
@@ -185,13 +163,7 @@ do_convert_to_boolean(Line, Expr, Bool, false, S) ->
{ { 'case', Line, Expr, [
{ clause, Line, [Var], [[OrElse]], [FalseResult] },
{ clause, Line, [Any], [], [TrueResult] }
] }, TS };
do_convert_to_boolean(Line, Expr, true, true, S) ->
{ do_guarded_convert_to_boolean(Line, Expr, 'andalso', '/='), S };
do_convert_to_boolean(Line, Expr, false, true, S) ->
{ do_guarded_convert_to_boolean(Line, Expr, 'orelse', '=='), S }.
] }, TS }.
do_guarded_convert_to_boolean(Line, Expr, Op, Comp) ->
Left = { op, Line, Comp, Expr, { atom, Line, false } },
+1
View File
@@ -27,5 +27,6 @@ defmodule KernelTest do
doctest Stream
doctest String
doctest Tuple
doctest URI
doctest Version
end
+26 -9
View File
@@ -10,31 +10,48 @@ defmodule BehaviourTest do
defcallback first(integer) :: integer
@doc "Foo"
defcallback foo(atom, binary) :: binary
defcallback foo(atom(), binary) :: binary
@doc "Bar"
defcallback bar(External.hello, my_var :: binary) :: binary
defcallback guarded(my_var) :: my_var when my_var: binary
defcallback orr(atom | integer) :: atom
defcallback literal(123, { atom }, :atom, [integer], true) :: atom
@doc "I should be last."
defmacrocallback last(integer) :: Macro.t
end
test :docs do
docs = Sample.__behaviour__(:docs)
assert docs == [
{{:first, 1}, 10, :def, "I should be first."},
{{:foo, 2}, 13, :def, "Foo"},
{{:bar, 2}, 16, :def, "Bar"},
{{:last, 1}, 19, :defmacro, "I should be last."}
]
assert [
{{:first, 1}, 10, :def, _, "I should be first."},
{{:foo, 2}, 13, :def, _, "Foo"},
{{:bar, 2}, 16, :def, _, "Bar"},
{{:guarded, 1}, 18, :def, _, nil},
{{:orr, 1}, 20, :def, _, nil},
{{:literal, 5}, 22, :def, _, nil},
{{:last, 1}, 25, :defmacro, _, "I should be last."}
] = docs
assert [{ :integer, _, nil }] = List.keyfind(docs, { :first, 1 }, 0) |> elem(3)
assert [{ :atom, _, nil }, { :binary, _, nil }] = List.keyfind(docs, { :foo, 2 }, 0) |> elem(3)
assert [{ :hello, _, nil }, { :my_var, _, nil }] = List.keyfind(docs, { :bar, 2 }, 0) |> elem(3)
assert [{ :my_var, _, nil }] = List.keyfind(docs, { :guarded, 1 }, 0) |> elem(3)
assert [{ :atom, _, nil }] = List.keyfind(docs, { :orr, 1 }, 0) |> elem(3)
assert [{ :int1, _, nil }, { :tuple2, _, nil }, { :atom3, _, nil }, { :list4, _, nil }, { :bool5, _, nil} ] = List.keyfind(docs, { :literal, 5 }, 0) |> elem(3)
assert [{ :integer, _, nil }] = List.keyfind(docs, { :last, 1 }, 0) |> elem(3)
end
test :callbacks do
assert Sample.__behaviour__(:callbacks) == [first: 1, foo: 2, bar: 2, "MACRO-last": 2]
assert Sample.__behaviour__(:callbacks) == [first: 1, foo: 2, bar: 2, guarded: 1, orr: 1, literal: 5, "MACRO-last": 2]
end
test :specs do
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 4
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 7
end
test :default_is_not_supported do
+2 -6
View File
@@ -13,7 +13,7 @@ defmodule CodeTest do
contents = quote do
defmodule CodeTest.Sample do
def eval_quoted_info, do: { __MODULE__, __FILE__, __ENV__.line }
def eval_quoted_info, do: { __MODULE__, __ENV__.file, __ENV__.line }
end
end
@@ -69,10 +69,6 @@ defmodule CodeTest do
assert Code.require_file(fixture_path("code_sample.exs")) != nil
end
test :file do
assert :filename.absname(__FILE__) == __FILE__
end
test :string_to_quoted do
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
assert { :error, _ } = Code.string_to_quoted("a.1")
@@ -96,7 +92,7 @@ defmodule CodeTest do
end
test :compile_source do
assert __MODULE__.__info__(:compile)[:source] == String.to_char_list!(__FILE__)
assert __MODULE__.__info__(:compile)[:source] == String.to_char_list!(__ENV__.file)
end
test :compile_info_returned_with_source_accessible_through_keyword_module do
+7 -13
View File
@@ -1,7 +1,7 @@
Code.require_file "test_helper.exs", __DIR__
defmodule DictTest.Common do
defmacro __using__(module) do
defmacro __using__(_) do
quote location: :keep do
# Most underlying Dict implementations have no key
# order guarantees, sort them before we compare:
@@ -12,18 +12,18 @@ defmodule DictTest.Common do
end
end
defp empty_dict, do: unquote(module).new
defp empty_dict, do: dict_impl.new
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
unquote(module).new list
dict_impl.new list
end
defp new_dict(list, transform) do
unquote(module).new list, transform
dict_impl.new list, transform
end
defp int_dict do
unquote(module).new [{1,1}]
dict_impl.new [{1,1}]
end
test :access do
@@ -333,19 +333,13 @@ defmodule DictTest.Common do
refute Enum.member?(new_dict([{1,1}]), { 1, 1.0 })
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1.0 })
end
test "unsupported dict" do
assert_raise ArgumentError, "unsupported dict: :bad_dict", fn ->
Dict.to_list :bad_dict
end
end
end
end
end
defmodule Dict.HashDictTest do
use ExUnit.Case, async: true
use DictTest.Common, HashDict
use DictTest.Common
doctest Dict
defp dict_impl, do: HashDict
@@ -353,7 +347,7 @@ end
defmodule Dict.ListDictTest do
use ExUnit.Case, async: true
use DictTest.Common, ListDict
use DictTest.Common
doctest Dict
defp dict_impl, do: ListDict
+167 -125
View File
@@ -97,7 +97,9 @@ defmodule EnumTest.List do
assert Enum.drop([1, 2, 3], 2) == [3]
assert Enum.drop([1, 2, 3], 3) == []
assert Enum.drop([1, 2, 3], 4) == []
assert Enum.drop([1, 2, 3], -1) == [3]
assert Enum.drop([1, 2, 3], -1) == [1, 2]
assert Enum.drop([1, 2, 3], -2) == [1]
assert Enum.drop([1, 2, 3], -4) == []
assert Enum.drop([], 3) == []
end
@@ -141,11 +143,6 @@ defmodule EnumTest.List do
assert Enum.fetch([2, 4, 6], -4) == :error
end
test :first do
assert Enum.first([]) == nil
assert Enum.first([1, 2, 3]) == 1
end
test :filter do
assert Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == [2]
assert Enum.filter([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
@@ -165,6 +162,16 @@ defmodule EnumTest.List do
test :flat_map do
assert Enum.flat_map([], fn(x) -> [x, x] end) == []
assert Enum.flat_map([1, 2, 3], fn(x) -> [x, x] end) == [1, 1, 2, 2, 3, 3]
assert Enum.flat_map([1, 2, 3], fn(x) -> x..x+1 end) == [1, 2, 2, 3, 3, 4]
end
test :flat_map_reduce do
assert Enum.flat_map_reduce([1, 2, 3], 0, &{ [&1, &2], &1 + &2 }) ==
{ [1, 0, 2, 1, 3, 3], 6 }
assert Enum.flat_map_reduce(1..100, 0, fn i, acc ->
if acc < 3, do: { [i], acc + 1 }, else: { :halt, acc }
end) == { [1,2,3], 3 }
end
test :reduce do
@@ -173,10 +180,16 @@ defmodule EnumTest.List do
assert Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end) == 6
assert_raise Enum.EmptyError, fn ->
assert Enum.reduce([], fn(x, acc) -> x + acc end)
Enum.reduce([], fn(x, acc) -> x + acc end)
end
end
test :intersperse do
assert Enum.intersperse([], true) == []
assert Enum.intersperse([1], true) == [1]
assert Enum.intersperse([1,2,3], true) == [1, true, 2, true, 3]
end
test :join do
assert Enum.join([], " = ") == ""
assert Enum.join([1, 2, 3], " = ") == "1 = 2 = 3"
@@ -219,6 +232,15 @@ defmodule EnumTest.List do
test :reverse do
assert Enum.reverse([]) == []
assert Enum.reverse([1, 2, 3]) == [3, 2, 1]
assert Enum.reverse([1, 2, 3], [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
end
test :scan do
assert Enum.scan([1,2,3,4,5], &(&1 + &2)) == [1,3,6,10,15]
assert Enum.scan([], &(&1 + &2)) == []
assert Enum.scan([1,2,3,4,5], 0, &(&1 + &2)) == [1,3,6,10,15]
assert Enum.scan([], 0, &(&1 + &2)) == []
end
test :shuffle do
@@ -260,7 +282,9 @@ defmodule EnumTest.List do
assert Enum.take([1, 2, 3], 2) == [1, 2]
assert Enum.take([1, 2, 3], 3) == [1, 2, 3]
assert Enum.take([1, 2, 3], 4) == [1, 2, 3]
assert Enum.take([1, 2, 3], -1) == [1, 2]
assert Enum.take([1, 2, 3], -1) == [3]
assert Enum.take([1, 2, 3], -2) == [2, 3]
assert Enum.take([1, 2, 3], -4) == [1, 2, 3]
assert Enum.take([], 3) == []
end
@@ -291,10 +315,10 @@ defmodule EnumTest.List do
test :zip do
assert Enum.zip([:a, :b], [1, 2]) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b], [1, 2, 3, 4]) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b, :c, :d], [1, 2]) == [{:a, 1}, {:b, 2}, {:c, nil}, {:d, nil}]
assert Enum.zip([:a, :b, :c, :d], [1, 2]) == [{:a, 1}, {:b, 2}]
assert Enum.zip([], [1]) == []
assert Enum.zip([1], []) == [{ 1, nil }]
assert Enum.zip([], []) == []
assert Enum.zip([1], []) == []
assert Enum.zip([], []) == []
end
test :with_index do
@@ -307,7 +331,7 @@ defmodule EnumTest.List do
assert Enum.max([1, 2, 3]) == 3
assert Enum.max([1, [], :a, {}]) == []
assert_raise Enum.EmptyError, fn ->
assert Enum.max([])
Enum.max([])
end
end
@@ -323,7 +347,7 @@ defmodule EnumTest.List do
assert Enum.min([1, 2, 3]) == 1
assert Enum.min([[], :a, {}]) == :a
assert_raise Enum.EmptyError, fn ->
assert Enum.min([])
Enum.min([])
end
end
@@ -334,20 +358,21 @@ defmodule EnumTest.List do
end
end
test :chunks do
assert Enum.chunks([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4]]
assert Enum.chunks([1, 2, 3, 4, 5], 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
assert Enum.chunks([1, 2, 3, 4, 5, 6], 3, 2) == [[1, 2, 3], [3, 4, 5]]
assert Enum.chunks([1, 2, 3, 4, 5, 6], 2, 3) == [[1, 2], [4, 5]]
assert Enum.chunks([1, 2, 3, 4, 5, 6], 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
assert Enum.chunks([1, 2, 3, 4, 5], 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
test :chunk do
assert Enum.chunk([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4]]
assert Enum.chunk([1, 2, 3, 4, 5], 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2) == [[1, 2, 3], [3, 4, 5]]
assert Enum.chunk([1, 2, 3, 4, 5, 6], 2, 3) == [[1, 2], [4, 5]]
assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) == [[1, 2, 3], [4, 5, 6]]
assert Enum.chunk([1, 2, 3, 4, 5], 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
end
test :chunks_by do
assert Enum.chunks_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1)) == [[1], [2, 2], [3], [4, 4, 6], [7, 7]]
assert Enum.chunks_by([1, 2, 3, 4], fn _ -> true end) == [[1, 2, 3, 4]]
assert Enum.chunks_by([], fn _ -> true end) == []
assert Enum.chunks_by([1], fn _ -> true end) == [[1]]
test :chunk_by do
assert Enum.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1)) == [[1], [2, 2], [3], [4, 4, 6], [7, 7]]
assert Enum.chunk_by([1, 2, 3, 4], fn _ -> true end) == [[1, 2, 3, 4]]
assert Enum.chunk_by([], fn _ -> true end) == []
assert Enum.chunk_by([1], fn _ -> true end) == [[1]]
end
test :slice do
@@ -371,14 +396,14 @@ defmodule EnumTest.List do
assert Enum.slice([1,2,3,4,5], 0..1) == [1, 2]
assert Enum.slice([1,2,3,4,5], 0..2) == [1, 2, 3]
assert Enum.slice([1,2,3,4,5], 1..2) == [2, 3]
assert Enum.slice([1,2,3,4,5], 1..0) == nil
assert Enum.slice([1,2,3,4,5], 1..0) == nil
assert Enum.slice([1,2,3,4,5], 2..5) == [3, 4, 5]
assert Enum.slice([1,2,3,4,5], 4..4) == [5]
assert Enum.slice([1,2,3,4,5], 5..5) == []
assert Enum.slice([1,2,3,4,5], 6..5) == nil
assert Enum.slice([1,2,3,4,5], 6..0) == nil
assert Enum.slice([1,2,3,4,5], -6..0) == nil
assert Enum.slice([1,2,3,4,5], -6..5) == nil
assert Enum.slice([1,2,3,4,5], 6..5) == nil
assert Enum.slice([1,2,3,4,5], 6..0) == nil
assert Enum.slice([1,2,3,4,5], -6..0) == nil
assert Enum.slice([1,2,3,4,5], -6..5) == nil
assert Enum.slice([1,2,3,4,5], -5..-1) == [1, 2, 3, 4, 5]
assert Enum.slice([1,2,3,4,5], -5..-3) == [1, 2, 3]
assert Enum.slice([1,2,3,4,5], -6..-1) == nil
@@ -390,25 +415,25 @@ defmodule EnumTest.Range do
use ExUnit.Case, async: true
test :all? do
range = Range.new(first: 0, last: 5)
range = 0..5
refute Enum.all?(range, fn(x) -> rem(x, 2) == 0 end)
range = Range.new(first: 0, last: 1)
range = 0..1
assert Enum.all?(range, fn(x) -> x < 2 end)
assert Enum.all?(range)
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.all?(range)
end
test :any? do
range = Range.new(first: 0, last: 5)
range = 0..5
refute Enum.any?(range, &(&1 > 10))
range = Range.new(first: 0, last: 5)
range = 0..5
assert Enum.any?(range, &(&1 > 3))
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.any?(range)
end
@@ -421,58 +446,61 @@ defmodule EnumTest.Range do
assert Enum.fetch!(-2..-6, 4) == -6
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.fetch!(2..6, 8)
Enum.fetch!(2..6, 8)
end
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.fetch!(-2..-6, 8)
Enum.fetch!(-2..-6, 8)
end
assert_raise Enum.OutOfBoundsError, fn ->
assert Enum.fetch!(2..6, -8)
Enum.fetch!(2..6, -8)
end
end
test :count do
range = Range.new(first: 1, last: 5)
range = 1..5
assert Enum.count(range) == 5
range = Range.new(first: 1, last: 1)
range = 1..1
assert Enum.count(range) == 1
end
test :count_fun do
range = Range.new(first: 1, last: 5)
range = 1..5
assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 2
range = Range.new(first: 1, last: 1)
range = 1..1
assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 0
end
test :drop do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.drop(range, 0) == [1, 2, 3]
assert Enum.drop(range, 1) == [2, 3]
assert Enum.drop(range, 2) == [3]
assert Enum.drop(range, 3) == []
assert Enum.drop(range, 4) == []
assert Enum.drop(range, -1) == [1, 2]
assert Enum.drop(range, -2) == [1]
assert Enum.drop(range, -4) == []
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.drop(range, 3) == []
end
test :drop_while do
range = Range.new(first: 0, last: 6)
range = 0..6
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == [4, 5, 6]
assert Enum.drop_while(range, fn(_) -> false end) == [0, 1, 2, 3, 4, 5, 6]
range = Range.new(first: 0, last: 3)
range = 0..3
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == []
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.drop_while(range, fn(_) -> false end) == [1, 0]
end
test :find do
range = Range.new(first: 2, last: 6)
range = 2..6
assert Enum.find(range, fn(x) -> rem(x, 2) == 0 end) == 2
assert Enum.find(range, fn(x) -> rem(x, 2) == 1 end) == 3
assert Enum.find(range, fn _ -> false end) == nil
@@ -480,37 +508,29 @@ defmodule EnumTest.Range do
end
test :find_value do
range = Range.new(first: 2, last: 6)
range = 2..6
assert Enum.find_value(range, fn(x) -> rem(x, 2) == 1 end)
end
test :find_index do
range = Range.new(first: 2, last: 6)
range = 2..6
assert Enum.find_index(range, fn(x) -> rem(x, 2) == 1 end) == 1
end
test :empty? do
range = Range.new(first: 1, last: 0)
range = 1..0
refute Enum.empty?(range)
range = Range.new(first: 1, last: 2)
range = 1..2
refute Enum.empty?(range)
end
test :first do
range = Range.new(first: 1, last: 0)
assert Enum.first(range) == 1
range = Range.new(first: 1, last: 2)
assert Enum.first(range) == 1
end
test :each do
try do
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.each(range, fn(x) -> x end) == :ok
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
assert Process.get(:enum_test_each) == 6
after
@@ -518,7 +538,7 @@ defmodule EnumTest.Range do
end
try do
range = Range.new(first: -1, last: -3)
range = -1..-3
assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
assert Process.get(:enum_test_each) == -6
after
@@ -527,109 +547,122 @@ defmodule EnumTest.Range do
end
test :filter do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2]
range = Range.new(first: 1, last: 6)
range = 1..6
assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
end
test :filter_with_match do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.filter(range, &match?(1, &1)) == [1]
assert Enum.filter(range, &match?(x when x < 3, &1)) == [1, 2]
assert Enum.filter(range, &match?(_, &1)) == [1, 2, 3]
end
test :filter_map do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]
range = Range.new(first: 2, last: 6)
range = 2..6
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
end
test :flat_map do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.flat_map(range, fn(x) -> [x, x] end) == [1, 1, 2, 2, 3, 3]
end
test :reduce do
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 2
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 7
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.reduce(range, fn(x, acc) -> x + acc end) == 6
end
test :reject do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.reject(range, fn(x) -> rem(x, 2) == 0 end) == [1, 3]
range = Range.new(first: 1, last: 6)
range = 1..6
assert Enum.reject(range, fn(x) -> rem(x, 2) == 0 end) == [1, 3, 5]
end
test :intersperse do
range = 1..0
assert Enum.intersperse(range, true) == [1, true, 0]
range = 1..3
assert Enum.intersperse(range, false) == [1, false, 2, false, 3]
end
test :join do
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.join(range, " = ") == "1 = 0"
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.join(range, " = ") == "1 = 2 = 3"
assert Enum.join(range) == "123"
end
test :map_join do
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 0"
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 4 = 6"
assert Enum.map_join(range, &(&1 * 2)) == "246"
end
test :map do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.map(range, fn x -> x * 2 end) == [2, 4, 6]
range = Range.new(first: -1, last: -3)
range = -1..-3
assert Enum.map(range, fn x -> x * 2 end) == [-2, -4, -6]
end
test :map_reduce do
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 0], 2 }
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 4, 6], 7 }
end
test :partition do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1, 3] }
end
test :scan do
assert Enum.scan(1..5, &(&1 + &2)) == [1,3,6,10,15]
assert Enum.scan(1..5, 0, &(&1 + &2)) == [1,3,6,10,15]
end
test :shuffle do
# set a fixed seed so the test can be deterministic
:random.seed(1374, 347975, 449264)
assert Enum.shuffle(Range.new(first: 1, last: 5)) == [2, 4, 1, 5, 3]
assert Enum.shuffle(1..5) == [2, 4, 1, 5, 3]
end
test :sort do
assert Enum.sort(Range.new(first: 3, last: 1)) == [1, 2, 3]
assert Enum.sort(Range.new(first: 2, last: 1)) == [1, 2]
assert Enum.sort(Range.new(first: 1, last: 1)) == [1]
assert Enum.sort(3..1) == [1, 2, 3]
assert Enum.sort(2..1) == [1, 2]
assert Enum.sort(1..1) == [1]
assert Enum.sort(Range.new(first: 3, last: 1), &(&1 > &2)) == [3, 2, 1]
assert Enum.sort(Range.new(first: 2, last: 1), &(&1 > &2)) == [2, 1]
assert Enum.sort(Range.new(first: 1, last: 1), &(&1 > &2)) == [1]
assert Enum.sort(3..1, &(&1 > &2)) == [3, 2, 1]
assert Enum.sort(2..1, &(&1 > &2)) == [2, 1]
assert Enum.sort(1..1, &(&1 > &2)) == [1]
end
test :split do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.split(range, 0) == { [], [1, 2, 3] }
assert Enum.split(range, 1) == { [1], [2, 3] }
assert Enum.split(range, 2) == { [1, 2], [3] }
@@ -640,31 +673,34 @@ defmodule EnumTest.Range do
assert Enum.split(range, -3) == { [], [1, 2, 3] }
assert Enum.split(range, -10) == { [], [1, 2, 3] }
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.split(range, 3) == { [1, 0], [] }
end
test :split_while do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.split_while(range, fn(_) -> false end) == { [], [1, 2, 3] }
assert Enum.split_while(range, fn(_) -> true end) == { [1, 2, 3], [] }
assert Enum.split_while(range, fn(x) -> x > 2 end) == { [], [1, 2, 3] }
assert Enum.split_while(range, fn(x) -> x > 3 end) == { [], [1, 2, 3] }
assert Enum.split_while(range, fn(x) -> x < 3 end) == { [1, 2], [3] }
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.split_while(range, fn(_) -> true end) == { [1, 0], [] }
end
test :take do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.take(range, 0) == []
assert Enum.take(range, 1) == [1]
assert Enum.take(range, 2) == [1, 2]
assert Enum.take(range, 3) == [1, 2, 3]
assert Enum.take(range, 4) == [1, 2, 3]
assert Enum.take(range, -1) == [3]
assert Enum.take(range, -2) == [2, 3]
assert Enum.take(range, -4) == [1, 2, 3]
range = Range.new(first: 1, last: 0)
range = 1..0
assert Enum.take(range, 3) == [1, 0]
end
@@ -675,7 +711,7 @@ defmodule EnumTest.Range do
end
test :take_while do
range = Range.new(first: 1, last: 3)
range = 1..3
assert Enum.take_while(range, fn(x) -> x > 3 end) == []
assert Enum.take_while(range, fn(x) -> x <= 1 end) == [1]
assert Enum.take_while(range, fn(x) -> x <= 3 end) == [1, 2, 3]
@@ -690,14 +726,14 @@ defmodule EnumTest.Range do
test :zip do
assert Enum.zip([:a, :b], 1..2) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b], 1..4) == [{:a, 1}, {:b, 2}]
assert Enum.zip([:a, :b, :c, :d], 1..2) == [{:a, 1}, {:b, 2}, {:c, nil}, {:d, nil}]
assert Enum.zip([:a, :b, :c, :d], 1..2) == [{:a, 1}, {:b, 2}]
assert Enum.zip(1..2, [:a, :b]) == [{1, :a}, {2, :b}]
assert Enum.zip(1..4, [:a, :b]) == [{1, :a}, {2, :b}, {3, nil}, {4, nil}]
assert Enum.zip(1..4, [:a, :b]) == [{1, :a}, {2, :b}]
assert Enum.zip(1..2, [:a, :b, :c, :d]) == [{1, :a}, {2, :b}]
assert Enum.zip(1..2, 1..2) == [{1, 1}, {2, 2}]
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}, {3, nil}, {4, nil}]
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}]
assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
end
@@ -727,18 +763,18 @@ defmodule EnumTest.Range do
assert Enum.min_by(1..3, fn(x) -> :math.pow(-2, x) end) == 3
end
test :chunks do
assert Enum.chunks(1..5, 2) == [[1, 2], [3, 4]]
assert Enum.chunks(1..5, 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
assert Enum.chunks(1..6, 3, 2) == [[1, 2, 3], [3, 4, 5]]
assert Enum.chunks(1..6, 2, 3) == [[1, 2], [4, 5]]
assert Enum.chunks(1..6, 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
assert Enum.chunks(1..5, 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
test :chunk do
assert Enum.chunk(1..5, 2) == [[1, 2], [3, 4]]
assert Enum.chunk(1..5, 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
assert Enum.chunk(1..6, 3, 2) == [[1, 2, 3], [3, 4, 5]]
assert Enum.chunk(1..6, 2, 3) == [[1, 2], [4, 5]]
assert Enum.chunk(1..6, 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
assert Enum.chunk(1..5, 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
end
test :chunks_by do
assert Enum.chunks_by(1..4, fn _ -> true end) == [[1, 2, 3, 4]]
assert Enum.chunks_by(1..4, &(rem(&1, 2) == 1)) == [[1], [2], [3], [4]]
test :chunk_by do
assert Enum.chunk_by(1..4, fn _ -> true end) == [[1, 2, 3, 4]]
assert Enum.chunk_by(1..4, &(rem(&1, 2) == 1)) == [[1], [2], [3], [4]]
end
test :slice do
@@ -756,25 +792,37 @@ defmodule EnumTest.Range do
assert Enum.slice(1..5, -2, 5) == [4, 5]
assert Enum.slice(1..5, -3, 1) == [3]
end
test :slice_range do
assert Enum.slice(1..5, 0..0) == [1]
assert Enum.slice(1..5, 0..1) == [1, 2]
assert Enum.slice(1..5, 0..2) == [1, 2, 3]
assert Enum.slice(1..5, 1..2) == [2, 3]
assert Enum.slice(1..5, 1..0) == nil
assert Enum.slice(1..5, 1..0) == nil
assert Enum.slice(1..5, 2..5) == [3, 4, 5]
assert Enum.slice(1..5, 4..4) == [5]
assert Enum.slice(1..5, 5..5) == []
assert Enum.slice(1..5, 6..5) == nil
assert Enum.slice(1..5, 6..0) == nil
assert Enum.slice(1..5, -6..0) == nil
assert Enum.slice(1..5, -6..5) == nil
assert Enum.slice(1..5, 6..5) == nil
assert Enum.slice(1..5, 6..0) == nil
assert Enum.slice(1..5, -6..0) == nil
assert Enum.slice(1..5, -6..5) == nil
assert Enum.slice(1..5, -5..-1) == [1, 2, 3, 4, 5]
assert Enum.slice(1..5, -5..-3) == [1, 2, 3]
assert Enum.slice(1..5, -6..-1) == nil
assert Enum.slice(1..5, -6..-3) == nil
end
test :reverse_range do
assert Enum.reverse([]) == []
assert Enum.reverse([1, 2, 3]) == [3, 2, 1]
assert Enum.reverse([1, 2, 3], [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
assert Enum.reverse(0..0) == [0]
assert Enum.reverse(1..3) == [3, 2, 1]
assert Enum.reverse(1..3, 4..6) == [3, 2, 1, 4, 5, 6]
assert Enum.reverse([1, 2, 3], 4..6) == [3, 2, 1, 4, 5, 6]
assert Enum.reverse(1..3, [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
end
end
defmodule EnumTest.Others do
@@ -800,15 +848,9 @@ defmodule EnumTest.Others do
end
test :take_with_side_effects do
reducible = fn(acc, fun) ->
Enum.reduce([1, 2, 3], acc, fn(x, acc) ->
IO.puts to_string(x)
fun.(x, acc)
end)
end
stream = Stream.unfold(1, fn x -> IO.puts x; { x, x + 1 } end)
assert capture_io(fn ->
Enum.take(reducible, 1)
Enum.take(stream, 1)
end) == "1\n"
end
+33 -31
View File
@@ -6,14 +6,14 @@ defmodule Kernel.ExceptionTest do
# Ensure fields passed through an expression are valid
defexception Custom, %w(message)a
test :is_exception do
test "is_exception" do
assert is_exception(RuntimeError.new)
refute is_exception(empty_tuple)
refute is_exception(a_tuple)
refute is_exception(a_list)
end
test :normalize do
test "normalize" do
assert is_record Exception.normalize(:badarg), ArgumentError
assert is_record Exception.normalize(ArgumentError[]), ArgumentError
@@ -22,35 +22,36 @@ defmodule Kernel.ExceptionTest do
assert is_record Exception.normalize(:error, ArgumentError[]), ArgumentError
end
test :format_stacktrace_entry_with_no_file_or_line do
test "format_stacktrace_entry with no file or line" do
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace_entry({Foo, :bar, [], []}) == "Foo.bar()"
assert Exception.format_stacktrace_entry({Foo, :bar, 1, []}) == "Foo.bar/1"
end
test :format_stacktrace_entry_with_file_and_line do
test "format_stacktrace_entry with file and line" do
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar()"
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace_entry({Foo, :bar, 1, [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar/1"
end
test :format_stacktrace_entry_with_file_and_line_and_cwd do
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: '/foo/file.ex', line: 10]}, "/foo") == "file.ex:10: Foo.bar()"
end
test :format_stacktrace_entry_with_file_no_line do
test "format_stacktrace_entry with file no line" do
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex']}) == "file.ex: Foo.bar()"
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex', line: 0]}) == "file.ex: Foo.bar()"
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex']}) == "file.ex: Foo.bar(1, 2, 3)"
assert Exception.format_stacktrace_entry({Foo, :bar, 1, [file: 'file.ex']}) == "file.ex: Foo.bar/1"
end
test :format_stacktrace_entry_with_fun do
test "format_stacktrace_entry with application" do
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex']}) == "(elixir) file.ex: Exception.bar()"
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex', line: 10]}) == "(elixir) file.ex:10: Exception.bar()"
assert Exception.format_stacktrace_entry({:lists, :bar, [1, 2, 3], []}) == "(stdlib) :lists.bar(1, 2, 3)" end
test "format_stacktrace_entry with fun" do
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
end
test :format_mfa do
test "format_mfa" do
assert Exception.format_mfa(Foo, nil, 1) == "Foo.nil/1"
assert Exception.format_mfa(Foo, :bar, 1) == "Foo.bar/1"
assert Exception.format_mfa(Foo, :bar, []) == "Foo.bar()"
@@ -59,53 +60,54 @@ defmodule Kernel.ExceptionTest do
assert Exception.format_mfa(Foo, :"bar baz", 1) == "Foo.\"bar baz\"/1"
end
test :format_fa do
test "format_fa" do
assert Exception.format_fa(fn -> end, 1) =~
%r"#Function<\d\.\d+ in Kernel\.ExceptionTest\.test_format_fa/1>/1"
%r"#Function<\d\.\d+ in Kernel\.ExceptionTest\.test format_fa/1>/1"
end
test :runtime_error_message do
test "runtime error message" do
assert RuntimeError.new.message == "runtime error"
assert RuntimeError.new(message: "exception").message == "exception"
end
test :argument_error_message do
test "argument error message" do
assert ArgumentError.new.message == "argument error"
assert ArgumentError.new(message: "exception").message == "exception"
end
test :undefined_function_message do
test "undefined function message" do
assert UndefinedFunctionError.new.message == "undefined function"
assert UndefinedFunctionError.new(module: Foo, function: :bar, arity: 1).message == "undefined function: Foo.bar/1"
assert UndefinedFunctionError.new(module: nil, function: :bar, arity: 0).message == "undefined function: nil.bar/0"
end
test :function_clause_message do
test "function clause message" do
assert FunctionClauseError.new.message == "no function clause matches"
assert FunctionClauseError.new(module: Foo, function: :bar, arity: 1).message == "no function clause matching in Foo.bar/1"
end
test :erlang_error_message do
test "erlang error message" do
assert ErlangError.new(original: :sample).message == "erlang error: :sample"
end
test :raise_preserves_the_stacktrace do
test "raise preserves the stacktrace" do
stacktrace =
try do
raise "a"
rescue _ ->
[top|_] = System.stacktrace
top
end
file = to_char_list(__FILE__)
assert {Kernel.ExceptionTest, :test_raise_preserves_the_stacktrace, _,
[file: ^file, line: 95]} = stacktrace # line is sensitive
try do
raise "a"
rescue _ ->
[top|_] = System.stacktrace
top
end
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
assert {Kernel.ExceptionTest, :"test raise preserves the stacktrace", _,
[file: ^file, line: 96]} = stacktrace
end
test :defexception do
defexception SampleError, field: :ok do
test "defexception" do
defexception SampleError, message: nil do
# Check do block is properly inline
def message(_), do: "hello"
def exception(_), do: SampleError[message: "hello"]
end
end

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