Compare commits

...
223 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
John Warwick 6c84492618 Fixed doc typos in Kernel and String 2013-12-15 14:19:07 +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
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
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
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
193 changed files with 6080 additions and 4283 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
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
+54 -1
View File
@@ -1,7 +1,60 @@
# 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 overriden and promote it as the main mechanism to customize exceptions
* [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`
+13 -11
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,6 +20,8 @@ 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) 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
@@ -27,7 +29,7 @@ lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
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)"
@@ -50,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
@@ -63,18 +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/kernel.ex" -o lib/elixir/ebin;
$(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;
$(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))
@@ -95,7 +97,7 @@ 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
@@ -154,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:
+1 -1
View File
@@ -1 +1 @@
0.12.0
0.12.2
+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
+21 -29
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,37 +18,29 @@
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
macro_aliases=[], %% keep aliases defined inside a macro
macro_counter=0, %% macros expansions counter
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
macros, %% a list with macros imported from module
functions %% a list with functions imported from module
counter=[], %% a dict counting the variables defined
file=(<<"nofile">>) %% the current scope filename
}).
-record(elixir_env, {
module=nil,
file=nil,
line=nil,
function=nil,
context=nil,
requires,
aliases,
functions,
macros,
macro_aliases=[],
macro_counter=0,
context_modules=[],
vars=[],
lexical_tracker=nil
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
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
local=nil %% the module to delegate local functions to
}).
-record(elixir_quote, {
+84 -17
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
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)
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
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)
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
+7 -13
View File
@@ -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 """
+33 -9
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
+43 -35
View File
@@ -282,24 +282,6 @@ defmodule Enum do
end
end
@doc false
def chunks(coll, n) do
IO.write "Enum.chunks/2 is deprecated, please use Enum.chunk/2 instead\n#{Exception.format_stacktrace}"
chunk(coll, n)
end
@doc false
def chunks(coll, n, step, pad) do
IO.write "Enum.chunks/4 is deprecated, please use Enum.chunk/4 instead\n#{Exception.format_stacktrace}"
chunk(coll, n, step, pad)
end
@doc false
def chunks_by(coll, fun) do
IO.write "Enum.chunks_by/2 is deprecated, please use Enum.chunk_by/2 instead\n#{Exception.format_stacktrace}"
chunk_by(coll, fun)
end
@doc """
Shortcut to `chunk(coll, n, n)`.
"""
@@ -759,22 +741,15 @@ defmodule Enum do
end
end
@doc """
Returns the first item in the collection or `nil` if the collection is empty.
## Examples
iex> Enum.first([])
nil
iex> Enum.first([1, 2, 3])
1
"""
@doc false
@spec first(t) :: :nil | element
def first([]), do: nil
def first([h|_]), do: h
def first(list) when is_list(list) do
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 or List.first/1 instead\n#{Exception.format_stacktrace}"
List.first(list)
end
def first(collection) do
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 instead\n#{Exception.format_stacktrace}"
Enumerable.reduce(collection, { :cont, nil }, fn(entry, _) ->
{ :halt, entry }
end) |> elem(1)
@@ -784,7 +759,7 @@ defmodule Enum do
Returns a new collection appending the result of invoking `fun`
on each corresponding item of `collection`.
The given function should return a list.
The given function should return an enumerable.
## Examples
@@ -795,15 +770,48 @@ defmodule Enum do
[1, 2, 3, 4, 5, 6]
"""
@spec flat_map(t, (element -> any)) :: list
def flat_map([], _fun), do: []
@spec flat_map(t, (element -> t)) :: list
def flat_map(collection, fun) do
reduce(collection, [], fn(entry, acc) ->
reduce(fun.(entry), acc, &[&1|&2])
end) |> :lists.reverse
end
@doc """
Maps and reduces a collection, flattening the given results.
It expects an accumulator and a function that receives each stream item
and an accumulator, and must return a tuple containing a new stream
(often a list) with the new accumulator or a tuple with `:halt` as first
element and the accumulator as second.
## Examples
iex> enum = 1..100
iex> n = 3
iex> Enum.flat_map_reduce(enum, 0, fn i, acc ->
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
...> end)
{ [1,2,3], 3 }
"""
@spec flat_map_reduce(t, acc, fun) :: list when
fun: (element, acc -> { t, acc } | { :halt, acc }),
acc: any
def flat_map_reduce(collection, acc, fun) do
{ _, { list, acc } } =
Enumerable.reduce(collection, { :cont, { [], acc } }, fn(entry, { list, acc }) ->
case fun.(entry, acc) do
{ :halt, acc } ->
{ :halt, { list, acc } }
{ entries, acc } ->
{ :cont, { reduce(entries, list, &[&1|&2]), acc } }
end
end)
{ :lists.reverse(list), acc }
end
@doc """
Intersperses `element` between each element of the enumeration.
+30 -33
View File
@@ -9,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
@@ -212,33 +215,37 @@ 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__, 1, 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__, 1, 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 """
@@ -255,11 +262,9 @@ 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
@@ -330,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)
@@ -357,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
@@ -372,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
+5 -14
View File
@@ -85,7 +85,7 @@ defmodule File do
## Examples
File.regular? __FILE__ #=> true
File.regular? __ENV__.file #=> true
"""
def regular?(path) do
@@ -649,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 =
@@ -1024,15 +1024,6 @@ defmodule File do
end
end
@doc false
def binstream!(file, mode // [], line_or_bytes // :line) do
IO.write "File.binstream! is deprecated, simply use File.stream! instead\n" <>
Exception.format_stacktrace
Stream.resource(fn -> open!(file, mode) end,
&IO.do_binstream(&1, line_or_bytes),
&F.close/1)
end
@doc """
Changes the unix file `mode` for a given `file`.
Returns `:ok` on success, or `{:error, reason}`
@@ -1074,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.
"""
@@ -1085,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)
+29 -22
View File
@@ -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
@@ -129,27 +141,22 @@ defmodule Float do
@doc """
Rounds a floating point value to an arbitrary number of fractional digits
(between 0 and 15) with an optional midpoint rounding mode (:up or :down,
defaults to :up).
(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.5675, 3, :down)
5.567
iex> Float.round(-5.5675, 3)
iex> Float.round(-5.5674, 3)
-5.567
iex> Float.round(-5.5675, 3, :down)
iex> Float.round(-5.5675, 3)
-5.568
"""
@spec round(float, integer, atom | nil) :: float
def round(number, precision, midpoint_rounding // :up) when is_float(number) and is_integer(precision) and precision in 0..15 do
# Default to :up if anything but :down is provided for midpoint rounding mode
case midpoint_rounding do
:down -> Kernel.round(Float.floor(number * :math.pow(10, precision))) / :math.pow(10, precision)
_ -> Kernel.round(Float.ceil(number * :math.pow(10, precision))) / :math.pow(10, precision)
end
@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
+64 -28
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,20 +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
surround_record(name, fields, tail, opts)
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
else
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
end
end
defp record_fields(name) do
@@ -342,7 +378,7 @@ defimpl Inspect, for: Tuple do
end
end
defp surround_record(name, fields, tail, opts) do
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
@@ -361,7 +397,7 @@ defimpl Inspect, for: Tuple do
end
defp keyword({ k, v }, opts) do
concat(k <> ": ", Kernel.inspect(v, opts))
concat(k <> ": ", to_doc(v, opts))
end
end
@@ -410,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
+21
View File
@@ -100,6 +100,27 @@ defmodule Inspect.Algebra do
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.
@@ -1,75 +1,90 @@
defmodule IEx.ANSIDocs do
defmodule IO.ANSI.Docs do
@moduledoc false
@bullets [?*, ?-, ?+]
@doc """
Prints the head of the documentation (i.e. the function signature)
"""
def print_heading(string, use_ansi // IO.ANSI.terminal?) do
if use_ansi do
write_doc_heading(string)
else
IO.puts "* #{string}\n"
end
IEx.dont_display_result
end
@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" ]
defp write_doc_heading(heading) do
@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)
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, use_ansi // IO.ANSI.terminal?) do
if use_ansi do
doc
|> String.split(["\r\n","\n"], trim: false)
|> Enum.map(&String.rstrip/1)
|> process("")
else
IO.puts doc
end
IEx.dont_display_result
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), do: nil
defp process([], _indent, _colors), do: nil
defp process(["# " <> heading | rest], _indent) do
write_h1(String.strip(heading))
process(rest, "")
defp process(["# " <> heading | rest], _indent, colors) do
write_h1(String.strip(heading), colors)
process(rest, "", colors)
end
defp process(["## " <> heading | rest], _indent) do
write_h2(String.strip(heading))
process(rest, "")
defp process(["## " <> heading | rest], _indent, colors) do
write_h2(String.strip(heading), colors)
process(rest, "", colors)
end
defp process(["### " <> heading | rest], indent) do
write_h3(String.strip(heading), indent)
process(rest, indent)
defp process(["### " <> heading | rest], indent, colors) do
write_h3(String.strip(heading), indent, colors)
process(rest, indent, colors)
end
defp process(["" | rest], indent) do
process(rest, indent)
defp process(["" | rest], indent, colors) do
process(rest, indent, colors)
end
defp process([" " <> line | rest], indent) do
process_code(rest, [line], indent)
defp process([" " <> line | rest], indent, colors) do
process_code(rest, [line], indent, colors)
end
defp process([line | rest], indent) do
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)
process_list(item, rest, count, indent, colors)
_ ->
process_text(rest, [line], indent, false)
process_text(rest, [line], indent, false, colors)
end
end
@@ -83,27 +98,27 @@ defmodule IEx.ANSIDocs do
## Headings
defp write_h1(heading) do
write_h2(String.upcase(heading))
defp write_h1(heading, colors) do
write_h2(String.upcase(heading), colors)
end
defp write_h2(heading) do
write(:doc_headings, heading)
defp write_h2(heading, colors) do
write(:doc_headings, heading, colors)
end
defp write_h3(heading, indent) do
defp write_h3(heading, indent, colors) do
IO.write(indent)
write(:doc_headings, heading)
write(:doc_headings, heading, colors)
end
## Lists
defp process_list(line, rest, count, indent) do
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)
process_text(contents, [line], indent <> " ", true, colors)
if done, do: IO.puts(IO.ANSI.reset)
process(rest, indent)
process(rest, indent, colors)
end
# Process the thing after a list item entry. It can be either:
@@ -141,35 +156,35 @@ defmodule IEx.ANSIDocs do
## Text (paragraphs / lists)
defp process_text(doc=["" | _], para, indent, from_list) do
write_text(Enum.reverse(para), indent, from_list)
process(doc, indent)
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) do
write_text(Enum.reverse(para), indent, from_list)
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) do
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)
process_list(item, rest, count, indent)
write_text(Enum.reverse(para), indent, true, colors)
process_list(item, rest, count, indent, colors)
_ ->
process_text(rest, [line | para], indent, true)
process_text(rest, [line | para], indent, true, colors)
end
end
defp process_text([line | rest], para, indent, from_list) do
process_text(rest, [line | para], indent, from_list)
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) do
defp write_text(lines, indent, from_list, colors) do
lines
|> Enum.join(" ")
|> handle_links
|> handle_inline(nil, [], [])
|> handle_inline(nil, [], [], colors)
|> String.split(%r{\s})
|> write_with_wrap(column_width() - size(indent), indent, from_list)
@@ -178,32 +193,32 @@ defmodule IEx.ANSIDocs do
## Code blocks
defp process_code([], code, indent) do
write_code(code, indent)
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) do
process_code(rest, [line, "" | code], indent)
defp process_code([ "", " " <> line | rest ], code, indent, colors) do
process_code(rest, [line, "" | code], indent, colors)
end
defp process_code([ " " <> line | rest ], code, indent) do
process_code(rest, [line|code], indent)
defp process_code([ " " <> line | rest ], code, indent, colors) do
process_code(rest, [line|code], indent, colors)
end
defp process_code(rest, code, indent) do
write_code(code, indent)
process(rest, indent)
defp process_code(rest, code, indent, colors) do
write_code(code, indent, colors)
process(rest, indent, colors)
end
defp write_code(code, indent) do
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}")
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) do
IO.puts IEx.color(style, string)
defp write(style, string, colors) do
IO.puts color(style, colors) <> string <> IO.ANSI.reset
IO.puts IO.ANSI.reset
end
@@ -249,8 +264,8 @@ defmodule IEx.ANSIDocs do
defp length_without_escape(rest, count) do
case String.next_grapheme(rest) do
:no_grapheme -> count
{ _, rest } -> length_without_escape(rest, count + 1)
nil -> count
end
end
@@ -280,81 +295,77 @@ defmodule IEx.ANSIDocs do
@spaced [?_, ?*]
# Clauses for handling spaces
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc) do
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc)
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) when mark in @spaced do
handle_inline(rest, nil, [?\s, mark|buffer], acc)
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) do
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc)
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) when mark in @spaced do
handle_inline(rest, limit, [mark, ?\s|buffer], acc)
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) do
handle_inline(rest, limit, [?\\|buffer], acc)
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) do
handle_inline(rest, limit, [?*, ?*|buffer], acc)
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)
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)
handle_inline(rest, limit, [mark|buffer], acc, colors)
end
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc) when rest != "" do
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc])
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) when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc])
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) do
handle_inline(rest, nil, [], [inline_buffer(buffer)|acc])
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) when mark in @single do
handle_inline(rest, nil, [], [inline_buffer(buffer)|acc])
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) do
handle_inline(rest, mark, [char|buffer], acc)
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) do
defp handle_inline(<<>>, _mark, buffer, acc, _colors) do
iolist_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
end
defp inline_buffer(buffer) do
defp inline_buffer(buffer, colors) do
[h|t] = Enum.reverse([IO.ANSI.reset|buffer])
[color_for(h)|t]
[color_for(h, colors)|t]
end
defp color_for("`"), do: color(:doc_inline_code)
defp color_for("_"), do: color(:doc_underline)
defp color_for("*"), do: color(:doc_bold)
defp color_for("**"), do: color(:doc_bold)
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(color_name) do
colors = IEx.Options.get(:colors)
if colors[:enabled] do
IO.ANSI.escape_fragment("%{#{colors[color_name]}}", true)
else
""
end
defp color(style, colors) do
color = colors[style]
enabled = colors[:enabled]
IO.ANSI.escape_fragment("%{#{color}}", enabled)
end
defp column_width() do
+132 -159
View File
@@ -506,12 +506,10 @@ defmodule Kernel do
:erlang.iolist_to_binary(item)
end
@doc """
Returns `true` if the local node is alive; that is,
if the node can be part of a distributed system.
"""
@doc false
@spec is_alive :: boolean
def is_alive do
IO.write "is_alive/0 is deprecated, please use Node.alive?/0 instead\n#{Exception.format_stacktrace}"
:erlang.is_alive
end
@@ -868,6 +866,24 @@ defmodule Kernel do
:erlang.round(number)
end
@doc """
Sends a message to the given `dest` and returns the message.
`dest` may be a remote or local pid, a (local) port, a locally
registered name, or a tuple `{registed_name, node}` for a registered
name at another node.
## Examples
iex> send self(), :hello
:hello
"""
@spec send(dest :: pid | port | atom | { atom, node }, msg) :: msg when msg: any
def send(dest, msg) do
:erlang.send(dest, msg)
end
@doc """
Returns the pid (process identifier) of the calling process.
Allowed in guard clauses.
@@ -895,7 +911,7 @@ defmodule Kernel do
## Examples
current = Kernel.self
child = spawn(fn -> current <- { Kernel.self, 1 + 2 } end)
child = spawn(fn -> send current, { Kernel.self, 1 + 2 } end)
receive do
{ ^child, 3 } -> IO.puts "Received 3 back"
@@ -933,7 +949,7 @@ defmodule Kernel do
## Examples
current = Kernel.self
child = spawn_link(fn -> current <- { Kernel.self, 1 + 2 } end)
child = spawn_link(fn -> send current, { Kernel.self, 1 + 2 } end)
receive do
{ ^child, 3 } -> IO.puts "Received 3 back"
@@ -1120,19 +1136,10 @@ defmodule Kernel do
quote do: __op__(:/, unquote(left), unquote(right))
end
@doc """
Sends a message to the process identified on the left.
A process can be identified by its PID or, if it is registered,
by an atom.
## Examples
process = Kernel.self
process <- { :ok, "Sending myself a message" }
"""
defmacro pid <- msg do
quote do: :erlang.!(unquote(pid), unquote(msg))
@doc false
def pid <- msg do
IO.write "<-/2 is deprecated, please use send/2 instead\n#{Exception.format_stacktrace}"
:erlang.!(pid, msg)
end
@doc """
@@ -1423,25 +1430,19 @@ defmodule Kernel do
# Extracts concatenations in order to optimize many
# concatenations into one single clause.
defp extract_concatenations({ :<>, _, [left, right] }) do
wrap_concatenation(left) ++ extract_concatenations(right)
[wrap_concatenation(left)|extract_concatenations(right)]
end
defp extract_concatenations(other) do
wrap_concatenation(other)
[wrap_concatenation(other)]
end
# If it is a binary, we don't need to add the binary
# tag. This allows us to use <> on pattern matching.
defp wrap_concatenation(binary) when is_binary(binary) do
[binary]
end
defp wrap_concatenation({ :<<>>, _, parts }) do
parts
binary
end
defp wrap_concatenation(other) do
[{ :::, [], [other, { :binary, [], nil }] }]
{ :::, [], [other, { :binary, [], nil }] }
end
@doc """
@@ -1714,17 +1715,24 @@ defmodule Kernel do
The second argument is a keywords list with options to control
inspection.
The second argument may also be an instance of the `Inspect.Opts`
record and, in such cases, instead of returning a string, it
returns an algebra document which can be converted to a string
via `Inspect.Algebra`.
## Options
The following options are supported:
* `:raw` - when true, record tuples are not formatted by the inspect protocol,
but are printed as just tuples, defaults to false;
* `:records` - when false, records are not formatted by the inspect protocol,
they are instead printed as just tuples, defaults to true;
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
non-printable bytes will be escaped; when `:as_binaries` all
binaries will be printed in bit syntax; when the default
`:infer`, the binary will be printed as a string if it is
printable, otherwise in bit syntax;
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
non-printable elements will be escaped; when `:as_lists` all
lists will be printed as lists; when the default `:infer`, the
list will be printed as a char list if it is printable,
otherwise as list;
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
and lists, does not apply to strings nor char lists, defaults to 50;
@@ -1745,9 +1753,18 @@ defmodule Kernel do
iex> inspect(ArgumentError[])
"ArgumentError[message: \"argument error\"]"
iex> inspect(ArgumentError[], raw: true)
iex> inspect(ArgumentError[], records: false)
"{ArgumentError, :__exception__, \"argument error\"}"
iex> inspect("josé" <> <<0>>)
"<<106, 111, 115, 195, 169, 0>>"
iex> inspect("josé" <> <<0>>, binaries: :as_strings)
"\"josé\\000\""
iex> inspect("josé", binaries: :as_binaries)
"<<106, 111, 115, 195, 169>>"
Note that the inspect protocol does not necessarily return a valid
representation of an Elixir term. In such cases, the inspected result
must start with `#`. For example, inspecting a function will return:
@@ -1756,25 +1773,29 @@ defmodule Kernel do
#=> #Function<...>
"""
@spec inspect(Inspect.t, Inspect.Opts.t) :: Inspect.Algebra.t
@spec inspect(Inspect.t, Keyword.t) :: String.t
def inspect(arg, opts // [])
def inspect(arg, opts) when is_tuple(opts) and tuple_size(opts) > 0 and
elem(opts, 0) == Inspect.Opts do
case is_tuple(arg) and elem(opts, 1) do
true -> Inspect.Tuple.inspect(arg, opts)
false -> Inspect.inspect(arg, opts)
end
def inspect(arg, opts) when is_tuple(opts) and tuple_size(opts) > 0 and elem(opts, 0) == Inspect.Opts do
IO.write "Kernel.inspect/2 with Inspect.Opts is deprecated, please use Inspect.Algebra.to_doc/2 instead\n#{Exception.format_stacktrace}"
Inspect.Algebra.to_doc(arg, opts)
end
def inspect(arg, opts) when is_list(opts) do
opts = Inspect.Opts.new(opts)
case opts.pretty do
true -> Inspect.Algebra.pretty(inspect(arg, opts), opts.width)
false -> Inspect.Algebra.pretty(inspect(arg, opts), :infinity)
case Keyword.get(opts, :raw) do
nil ->
:ok
raw ->
IO.write "Kernel.inspect/2 with :raw option is deprecated, please use :records instead\n#{Exception.format_stacktrace}"
opts = opts ++ [records: not raw]
end
opts = Inspect.Opts.new(opts)
limit = case opts.pretty do
true -> opts.width
false -> :infinity
end
Inspect.Algebra.pretty(Inspect.Algebra.to_doc(arg, opts), limit)
end
@doc """
@@ -1863,8 +1884,6 @@ defmodule Kernel do
end
defmacro match?(left, right) do
{ left, _ } = kernelfy_var(left, [], &kernelfy_all/2)
quote do
case unquote(right) do
unquote(left) ->
@@ -1875,51 +1894,6 @@ defmodule Kernel do
end
end
defp kernelfy_all({ var, meta, scope }, acc) when is_atom(var) and is_atom(scope) do
{ { var, meta, Kernel }, [{ var, scope }|acc] }
end
defp kernelfy_selected({ var, meta, scope } = original, acc) when is_atom(var) and is_atom(scope) do
case :lists.member({ var, scope }, acc) do
true -> { { var, meta, Kernel }, acc }
false -> { original, acc }
end
end
defp kernelfy_var({ :^, _, [_] } = contents, acc, _fun) do
{ contents, acc }
end
defp kernelfy_var({ :when, meta, [left, right] }, acc, fun) do
{ left, acc } = kernelfy_var(left, acc, fun)
{ right, acc } = kernelfy_var(right, acc, &kernelfy_selected/2)
{ { :when, meta, [left, right] }, acc }
end
defp kernelfy_var({ var, _, scope } = original, acc, fun) when is_atom(var) and is_atom(scope) do
fun.(original, acc)
end
defp kernelfy_var({ left, meta, right }, acc, fun) do
{ left, acc } = kernelfy_var(left, acc, fun)
{ right, acc } = kernelfy_var(right, acc, fun)
{ { left, meta, right }, acc }
end
defp kernelfy_var({ left, right }, acc, fun) do
{ left, acc } = kernelfy_var(left, acc, fun)
{ right, acc } = kernelfy_var(right, acc, fun)
{ { left, right }, acc }
end
defp kernelfy_var(list, acc, fun) when is_list(list) do
:lists.mapfoldl(&kernelfy_var(&1, &2, fun), acc, list)
end
defp kernelfy_var(other, acc, _fun) do
{ other, acc }
end
@doc """
This macro is a shortcut to read and add attributes to the module
being compiled. Elixir module attributes are similar to Erlang's with
@@ -2160,7 +2134,7 @@ defmodule Kernel do
@doc """
Evaluates the expression corresponding to the first clause that
evaluates to true. Raises an error if all conditions evaluate to
to falsy values (nil or false).
to nil or false.
## Examples
@@ -2172,9 +2146,10 @@ defmodule Kernel do
true ->
"This will"
end
"""
defmacro cond([do: { :->, _, pairs }]) do
[{ [condition], meta, clause }|t] = :lists.reverse pairs
defmacro cond([do: pairs]) do
[{ :->, meta, [[condition], clause] }|t] = :lists.reverse pairs
new_acc =
case condition do
@@ -2205,35 +2180,27 @@ defmodule Kernel do
# end
# end
#
defp build_cond_clauses([{ [condition], new, clause }|t], acc, old) do
stab = { :->, [], [falsy_clause(old, acc), truthy_clause(new, clause)] }
acc = quote do
case unquote(condition), do: unquote(stab)
end
defp build_cond_clauses([{ :->, new, [[condition], clause] }|t], acc, old) do
clauses = [falsy_clause(old, acc), truthy_clause(new, clause)]
acc = quote do: (case unquote(condition), do: unquote(clauses))
build_cond_clauses(t, acc, new)
end
defp build_cond_clauses([], acc, _), do: acc
defp falsy_clause(meta, acc) do
{ [quote(do: unquote(cond_var) when unquote(cond_var) in [false, nil])], meta, acc }
{ :->, meta, [[quote(do: unquote(cond_var) when unquote(cond_var) in [false, nil])], acc] }
end
defp truthy_clause(meta, clause) do
{ [quote(do: _)], meta, clause }
{ :->, meta, [[quote(do: _)], clause] }
end
# Setting cond: true in metadata turns on a small optimization
# in Elixir compiler. In the long run, we want to bring this
# optimization to Elixir land, but not right now.
defp cond_var do
{ :x, [cond: true, temp: true], Kernel }
end
# A temporary var only lasts its current clause, never leak
# into other clauses.
defp temp_var do
{ :x, [temp: true], Kernel }
{ :x, [cond: true], Kernel }
end
defp get_line(meta) do
@@ -2297,15 +2264,17 @@ defmodule Kernel do
it will raise a CaseClauseError.
"""
defmacro destructure(left, right) when is_list(left) do
List.foldl left, right, fn item, acc ->
quote do
case unquote(acc) do
[unquote(item)|t] ->
t
other when other == [] or other == nil ->
unquote(item) = nil
Enum.reduce left, right, fn item, acc ->
{ :case, meta, args } =
quote do
case unquote(acc) do
[unquote(item)|t] ->
t
other when other == [] or other == nil ->
unquote(item) = nil
end
end
end
{ :case, [{:export_all,true}|meta], args }
end
end
@@ -2353,8 +2322,8 @@ defmodule Kernel do
defmacro left && right do
quote do
case unquote(left) do
unquote(temp_var) when unquote(temp_var) in [false, nil] ->
unquote(temp_var)
x when x in [false, nil] ->
x
_ ->
unquote(right)
end
@@ -2384,10 +2353,10 @@ defmodule Kernel do
defmacro left || right do
quote do
case unquote(left) do
unquote(temp_var) when unquote(temp_var) in [false, nil] ->
x when x in [false, nil] ->
unquote(right)
unquote(temp_var) ->
unquote(temp_var)
x ->
x
end
end
end
@@ -2407,7 +2376,7 @@ defmodule Kernel do
Enum.map(List.flatten([1, [2], 3]), &(&1 * 2))
Be aware of operator precendence when using this operator.
Be aware of operator precedence when using this operator.
For example, the following expression:
String.graphemes "Hello" |> Enum.reverse
@@ -2531,14 +2500,14 @@ defmodule Kernel do
end
rescue
UndefinedFunctionError ->
# We first try to call __access__ and just then check if
# We first try to call __record__ and just then check if
# it is loaded so we allow the ParallelCompiler to solve
# conflicts.
case :code.ensure_loaded(atom) do
{ :error, _ } ->
:elixir_aliases.ensure_loaded(caller.line, caller.file, atom, caller.context_modules)
:elixir_aliases.ensure_loaded(caller.line, atom, :elixir_env.ex_to_env(caller))
_ ->
raise ArgumentError, message: "cannot use module #{inspect atom} in access protocol because it does not export __record__/1"
raise ArgumentError, message: "cannot access module #{inspect atom} because it is not a record"
end
end
@@ -2795,7 +2764,7 @@ defmodule Kernel do
end
{ escaped, _ } = :elixir_quote.escape(block, false)
module_vars = module_vars(env_vars(env))
module_vars = module_vars(env_vars(env), 0)
quote do
unquote(with_alias)
@@ -2830,12 +2799,7 @@ defmodule Kernel do
do: :elixir_aliases.concat([env.module, module])
# quote vars to be injected into the module definition
defp module_vars(vars) do
{ vars, _ } = :lists.mapfoldl(&module_var_to_tuple/2, 0, vars)
vars
end
defp module_var_to_tuple({ key, kind }, counter) do
defp module_vars([{ key, kind }|vars], counter) do
var =
case is_atom(kind) do
true -> { key, [], kind }
@@ -2843,7 +2807,11 @@ defmodule Kernel do
end
args = [key, kind, binary_to_atom(<<"_@", integer_to_binary(counter)::binary>>), var]
{ { :{}, [], args }, counter + 1 }
[{ :{}, [], args }|module_vars(vars, counter+1)]
end
defp module_vars([], _counter) do
[]
end
# Gets two modules names and return an alias
@@ -2972,16 +2940,18 @@ defmodule Kernel do
defp define(kind, call, expr, env) do
assert_module_scope(env, kind, 2)
assert_no_function_scope(env, kind, 2)
line = env_line(env)
{ call, uc } = :elixir_quote.escape(call, true)
{ expr, ue } = :elixir_quote.escape(expr, true)
# Do not check clauses if any expression was unquoted
check_clauses = not(ue or uc)
pos = :elixir_locals.cache_env(env)
quote do
:elixir_def.store_definition(unquote(kind), unquote(check_clauses),
unquote(call), unquote(expr), __ENV__)
:elixir_def.store_definition(unquote(line), unquote(kind), unquote(check_clauses),
unquote(call), unquote(expr), unquote(pos))
end
end
@@ -3024,7 +2994,7 @@ defmodule Kernel do
the record name, we can get the raw record representation as
follows:
inspect User.new, raw: true
inspect User.new, records: false
#=> { User, nil, 0 }
In addition to defining readers and writers for each attribute, Elixir also
@@ -3542,10 +3512,11 @@ defmodule Kernel do
append_first = Keyword.get(opts, :append_first, false)
lc fun inlist List.wrap(funs) do
case Macro.decompose_call(fun) do
{ name, args } -> :ok
_ -> raise ArgumentError, message: "invalid syntax in defdelegate #{Macro.to_string(fun)}"
end
{ name, args } =
case Macro.decompose_call(fun) do
{ _, _ } = pair -> pair
_ -> raise ArgumentError, message: "invalid syntax in defdelegate #{Macro.to_string(fun)}"
end
actual_args =
case append_first and args != [] do
@@ -3565,7 +3536,7 @@ defmodule Kernel do
## Sigils
@doc """
Handles the sigil %S. It simples returns a string
Handles the sigil %S. It simply returns a string
without escaping characters and without interpolations.
## Examples
@@ -3613,7 +3584,7 @@ defmodule Kernel do
end
@doc """
Handles the sigil %c. It returns a char list as if it was a single
Handles the sigil %c. It returns a char list as if it were a single
quoted string, unescaping characters and replacing interpolations.
## Examples
@@ -3676,9 +3647,9 @@ defmodule Kernel do
## Modifiers
- `b`: binaries (default)
- `a`: atoms
- `c`: char lists
* `s`: strings (default)
* `a`: atoms
* `c`: char lists
## Examples
@@ -3706,9 +3677,9 @@ defmodule Kernel do
## Modifiers
- `b`: binaries (default)
- `a`: atoms
- `c`: char lists
* `s`: strings (default)
* `a`: atoms
* `c`: char lists
## Examples
@@ -3723,11 +3694,12 @@ defmodule Kernel do
defp split_words("", _modifiers), do: []
defp split_words(string, modifiers) do
mod = case modifiers do
[] -> ?s
[mod] when mod == ?s or mod == ?a or mod == ?c -> mod
_else -> raise ArgumentError, message: "modifier must be one of: s, a, c"
end
mod =
case modifiers do
[] -> ?s
[mod] when mod == ?s or mod == ?a or mod == ?c -> mod
_else -> raise ArgumentError, message: "modifier must be one of: s, a, c"
end
case is_binary(string) do
true ->
@@ -3773,9 +3745,10 @@ defmodule Kernel do
end
defp env_module(env), do: :erlang.element(2, env)
defp env_line(env), do: :erlang.element(4, env)
defp env_function(env), do: :erlang.element(5, env)
defp env_context(env), do: :erlang.element(6, env)
defp env_vars(env), do: :erlang.element(14, env)
defp env_vars(env), do: :erlang.element(13, env)
defp expand_compact([{ :compact, false }|t]), do: expand_compact(t)
defp expand_compact([{ :compact, true }|t]), do: [:compact|expand_compact(t)]
+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)
+103 -60
View File
@@ -1,16 +1,16 @@
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`, `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 2 special forms, `__block__` and
@@ -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:
@@ -738,7 +765,7 @@ defmodule Kernel.SpecialForms do
because `D` still expands to `HashDict`.
Similarly, even if we defined an alias with the same name
before invoking a macro, it won't affect the macro result:
before invoking a macro, it won't affect the macro's result:
defmodule Hygiene do
alias HashDict, as: D
@@ -878,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
@@ -900,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
@@ -918,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
@@ -1200,44 +1227,54 @@ defmodule Kernel.SpecialForms do
defmacro super(args)
@doc """
Matches the given expression against the match clauses.
Matches the given expression against the given clauses.
## Examples
case thing do
{ :selector, i, value } when is_integer(i) ->
value
value -> value
value ->
value
end
In the example above, we compare `thing` with each given match
clause and evaluate the expression corresponding to the first clause
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.
Since Elixir variables can be assigned more than once, variables
in a match clause will always be assigned instead of matching with
its previous values. For example:
## Variables handling
i = 1
case 10 do
i -> i * 2
Notice that variables bound in a clause "head" do not leak to the
outer context:
case data do
{ :ok, value } -> value
:error -> nil
end
The example above will return 20, because `i` is assigned to 10
and then multiplied by 2. If you desire to match the value of `i`
against the given condition, you need to use the `^` operator:
value #=> unbound variable value
i = 1
case 10 do
^i -> i * 2
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
The example above will actually fail because 10 does not match 1.
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 catch any error, exit
Evaluate the given expressions and handle any error, exit
or throw that may have happened.
## Examples
@@ -1284,14 +1321,14 @@ defmodule Kernel.SpecialForms do
[UndefinedFunctionError] -> nil
end
# rescue and assign to x
# rescue and bind to x
try do
UndefinedModule.undefined_function
rescue
x in [UndefinedFunctionError] -> nil
end
# rescue all and assign to x
# rescue all and bind to x
try do
UndefinedModule.undefined_function
rescue
@@ -1396,7 +1433,7 @@ defmodule Kernel.SpecialForms do
with a tail call as the final call inside an else clause. The same
is true for rescue and catch clauses.
## Variable visibility
## Variable handling
Since an expression inside `try` may not have been evaluated
due to an exception, any variable created inside `try` cannot
@@ -1410,7 +1447,7 @@ defmodule Kernel.SpecialForms do
_, _ -> :failed
end
x #=> Cannot access `x`
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
@@ -1429,8 +1466,11 @@ defmodule Kernel.SpecialForms do
defmacro try(args)
@doc """
The current process will hang until it receives a message
from other processes that matches the given clauses.
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
@@ -1443,8 +1483,6 @@ defmodule Kernel.SpecialForms do
IO.puts :stderr, "Unexpected message received"
end
The match clauses above follows the same rules as `case/2`.
An optional after clause can be given in case the message was not
received after the specified period of time:
@@ -1468,6 +1506,11 @@ defmodule Kernel.SpecialForms do
* 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
+218 -101
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
@@ -96,7 +206,7 @@ defmodule Kernel.Typespec do
@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
@@ -111,7 +221,7 @@ defmodule Kernel.Typespec do
@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
@@ -126,7 +236,7 @@ defmodule Kernel.Typespec do
@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
@@ -141,7 +251,7 @@ defmodule Kernel.Typespec do
@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
@@ -243,19 +353,21 @@ defmodule Kernel.Typespec do
Converts a spec clause back to Elixir AST.
"""
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
ast = { :::, [line: line], [{ name, [line: line], ast_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, [line: line], nil } })
|> Enum.map(&{ &1, { :var, meta, nil } })
unless vars == [] do
ast = { :when, [line: line], [ast, vars] }
result = if vars == [] do
typespec_to_ast(result)
else
{ :when, meta, [typespec_to_ast(result), vars] }
end
ast
{ :::, meta, [body, result] }
end
def spec_to_ast(name, { :type, line, :fun, [] }) do
@@ -268,16 +380,20 @@ defmodule Kernel.Typespec do
{ var, typespec_to_ast(type) }
end
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
meta = [line: line]
vars = args ++ [result]
|> Enum.flat_map(&collect_vars/1)
|> Enum.uniq
|> Kernel.--(Keyword.keys(guards))
|> Enum.map(&{ &1, { :var, meta, nil } })
vars = vars -- Keyword.keys(guards)
|> Enum.map(&{ &1, { :var, [line: line], nil } })
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :when, [line: line], [{ :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }, guards ++ vars] }
{ :::, meta, [
{ name, [line: line], args },
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
] }
end
@doc """
@@ -298,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
@@ -318,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
@@ -346,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
@@ -360,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
@@ -414,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
@@ -435,16 +551,17 @@ defmodule Kernel.Typespec do
end
@doc false
def defspec(type, { :when, meta2, [{ :::, _, [{ name, meta, args }, return] }, guard] }, caller) do
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) do
if is_atom(args), do: args = []
{ return, guard } = split_return_and_guard(return_and_guard)
unless Keyword.keyword?(guard) do
guard = Macro.to_string(guard)
compile_error caller, "invalid guard in function type specification `#{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, meta2, caller)
constraints = guard_to_constraints(guard, vars, meta, caller)
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
if constraints != [] do
@@ -456,17 +573,22 @@ defmodule Kernel.Typespec do
code
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
end
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 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
@@ -485,7 +607,7 @@ defmodule Kernel.Typespec do
## To AST conversion
defp collect_vars({ :ann_type, _line, args }) when is_list(args) do
Enum.flat_map(args, &collect_vars/1)
[]
end
defp collect_vars({ :type, _line, _kind, args }) when is_list(args) do
@@ -513,7 +635,7 @@ defmodule Kernel.Typespec 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
@@ -546,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
@@ -576,6 +697,10 @@ defmodule Kernel.Typespec do
{ 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)
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
typespec_to_ast({:type, line, :char_list, []})
@@ -635,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
@@ -669,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
@@ -707,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
@@ -720,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)
@@ -736,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 }
@@ -776,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
@@ -799,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
+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
+20 -6
View File
@@ -116,6 +116,23 @@ defmodule List 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.
@@ -130,12 +147,9 @@ defmodule List do
"""
@spec last([elem]) :: nil | elem when elem: var
def last([]), do: nil
def last(list) do
:lists.last(list)
end
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
+137 -131
View File
@@ -5,16 +5,19 @@ defmodule Macro do
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]
@binary_ops [ :===, :!==,
@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: @binary_ops
@@ -27,22 +30,18 @@ defmodule Macro do
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
:when -> {:right, 30}
::: -> {:right, 40}
o when o in [:inlist, :inbits] -> {:left, 50}
:// -> {:right, 60}
:| -> {:left, 70}
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}
o when o in [:++, :--, :**] -> {:right, 240}
:^^^ -> {:left, 250}
:. -> {:left, 310}
end
@@ -82,10 +81,39 @@ defmodule Macro do
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
end
@doc false
def extract_args(expr) do
IO.write "Macro.extract_args/1 is deprecated, use Macro.decompose_call/1 instead\n#{Exception.format_stacktrace}"
decompose_call(expr)
@doc """
Recurs the quoted expression applying the given function to
each metadata node.
This is often useful to remove information like lines and
hygienic counters from the expression for either storage or
comparison.
## Examples
iex> quoted = quote line: 10, do: sample()
{:sample, [line: 10], []}
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
{:sample, [], []}
"""
@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 """
@@ -298,33 +326,27 @@ defmodule Macro do
# Tuple containers
def to_string({ :{}, _, args } = ast, fun) do
if match?([_], args) do
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
else
args = args_to_string(args, fun)
tuple = "{" <> args <> "}"
end
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
@@ -367,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)
@@ -381,28 +403,29 @@ defmodule Macro do
# Lists
def to_string(list, fun) when is_list(list) do
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) <> "]"
not match?([_], list) ->
args = args_to_string(list, fun)
"[" <> args <> "]"
true ->
"[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]"
end)
end
# All other structures
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
# Block keywords
@kw_keywords [:do, :catch, :rescue, :after, :else]
defp is_kw_blocks?([_|_] = kw) do
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)
@@ -442,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)
@@ -483,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)
@@ -513,13 +536,13 @@ defmodule Macro do
* 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`);
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.
until the node can no longer be expanded.
## Examples
@@ -583,109 +606,122 @@ 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, meta, context } = original, env, cache)
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, cache }
{ original, false }
else
case expand_once({ atom, meta, [] }, env, cache) do
{ _, true, _ } = exp -> exp
{ _, false, cache } -> { original, false, cache }
case do_expand_once({ atom, meta, [] }, env) do
{ _, true } = exp -> exp
{ _, false } -> { original, false }
end
end
end
defp expand_once({ atom, meta, args } = original, env, cache)
defp do_expand_once({ atom, meta, args } = original, env)
when is_atom(atom) and is_list(args) and is_list(meta) do
case not is_partial?(args) do
false -> { original, false, cache }
true ->
module = env.module
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
cache = to_erl_env(env, cache)
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
env.module, extra, cache)
arity = length(args)
case expand do
{ :ok, _, expanded } -> { expanded, true, cache }
{ :error, _ } -> { original, false, cache }
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)
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_env.ex_to_scope(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 an AST node and expands it until it no longer represents
@@ -695,45 +731,15 @@ defmodule Macro do
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 """
+13 -9
View File
@@ -12,16 +12,19 @@ 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 }
@@ -35,18 +38,19 @@ defmodule Macro.Env do
@type macros :: [{ module, [name_arity] }]
@type context_modules :: [module]
@type vars :: [{ atom, atom | non_neg_integer }]
@type export_vars :: vars | nil
@type lexical_tracker :: pid
@type counter :: non_neg_integer
@type local :: module | nil
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
:macros, :macro_aliases, :macro_counter,
:context_modules, :vars, :lexical_tracker]
:macros, :macro_aliases, :context_modules, :vars, :export_vars, :lexical_tracker,
:local]
types = quote do: [module: module, file: file, line: line,
function: name_arity, context: context, requires: requires, aliases: aliases,
functions: functions, macros: macros, macro_aliases: aliases,
macro_counter: counter, context_modules: context_modules, vars: vars,
lexical_tracker: lexical_tracker]
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__)
+27 -28
View File
@@ -69,7 +69,7 @@ defmodule Module do
@before_compile A
end
* `@behaviour` (notice the british spelling)
* `@behaviour` (notice the British spelling)
Specify an OTP or user-defined behaviour.
@@ -183,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
@@ -298,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)
@@ -353,15 +353,15 @@ defmodule Module do
def eval_quoted(module, quoted, binding, opts) do
assert_not_compiled!(:eval_quoted, module)
:elixir_def.reset_last(module)
{ value, binding, _scope } =
:elixir.eval_quoted [quoted], binding, Keyword.put(opts, :module, 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
@@ -374,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
@@ -394,12 +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.
Concatenates a list of aliases and returns a new alias.
## Examples
@@ -415,7 +414,7 @@ defmodule Module do
end
@doc """
Concatenates the two given aliases and returns a new alias.
Concatenates two aliases and returns a new alias.
## Examples
@@ -431,9 +430,9 @@ defmodule Module do
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
@@ -451,9 +450,9 @@ defmodule Module do
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
@@ -486,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.
@@ -585,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
@@ -603,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
@@ -626,7 +625,7 @@ defmodule Module do
end
@doc """
Return all functions defined in the given module.
Return all functions defined in `module`.
## Examples
@@ -643,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
@@ -662,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.
@@ -678,8 +677,8 @@ defmodule Module do
clause ->
:elixir_def.delete_definition(module, tuple)
neighbours = if loaded?(Module.DispatchTracker) do
Module.DispatchTracker.yank(module, tuple)
neighbours = if loaded?(Module.LocalsTracker) do
Module.LocalsTracker.yank(module, tuple)
else
[]
end
@@ -695,7 +694,7 @@ 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)
@@ -790,7 +789,7 @@ defmodule Module do
end
@doc """
Deletes all attributes that matches the given key.
Deletes all attributes that match the given key.
## Examples
@@ -822,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
@@ -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.
"""
+3 -1
View File
@@ -73,7 +73,9 @@ defimpl Range.Iterator, for: Integer do
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
+8 -8
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
@@ -436,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) }
@@ -487,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
@@ -713,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
@@ -808,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
+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
+167 -96
View File
@@ -340,7 +340,7 @@ defmodule Stream do
## Examples
iex> stream = Stream.each([1, 2, 3], fn(x) -> self <- x end)
iex> stream = Stream.each([1, 2, 3], fn(x) -> send self, x end)
iex> Enum.to_list(stream)
iex> receive do: (x when is_integer(x) -> x)
1
@@ -360,6 +360,22 @@ defmodule Stream do
end
end
@doc """
Creates a stream that will apply the given function on enumeration and
flatten the result.
## Examples
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
iex> Enum.to_list(stream)
[1, 2, 2, 4, 3, 6]
"""
@spec flat_map(Enumerable.t, (element -> Enumerable.t)) :: Enumerable.t
def flat_map(enum, mapper) do
transform(enum, nil, fn val, nil -> { mapper.(val), nil } end)
end
@doc """
Creates a stream that filters elements according to
the given function on enumeration.
@@ -410,99 +426,6 @@ defmodule Stream do
lazy enum, fn(f1) -> R.map(fun, f1) end
end
@doc """
Creates a stream that applies the given function to each
element, emits the result and uses the same result as the accumulator
for the next computation.
## Examples
iex> stream = Stream.scan(1..5, &(&1 + &2))
iex> Enum.to_list(stream)
[1,3,6,10,15]
"""
@spec scan(Enumerable.t, (element, acc -> any)) :: Enumerable.t
def scan(enum, fun) do
lazy enum, :first, fn(f1) -> R.scan_2(fun, f1) end
end
@doc """
Creates a stream that applies the given function to each
element, emits the result and uses the same result as the accumulator
for the next computation. Uses the given `acc` as the starting value.
## Examples
iex> stream = Stream.scan(1..5, 0, &(&1 + &2))
iex> Enum.to_list(stream)
[1,3,6,10,15]
"""
@spec scan(Enumerable.t, acc, (element, acc -> any)) :: Enumerable.t
def scan(enum, acc, fun) do
lazy enum, acc, fn(f1) -> R.scan_3(fun, f1) end
end
@doc """
Creates a stream that will apply the given function on enumeration and
flatten the result.
## Examples
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
iex> Enum.to_list(stream)
[1, 2, 2, 4, 3, 6]
"""
@spec flat_map(Enumerable.t, (element -> any)) :: Enumerable.t
def flat_map(enum, mapper) do
&do_flat_map(enum, mapper, &1, &2)
end
defp do_flat_map(enumerables, mapper, acc, fun) do
fun = &do_flat_map_each(fun, &1, &2)
step = &do_flat_map_step/2
next = &Enumerable.reduce(enumerables, &1, step)
do_flat_map([], next, mapper, acc, fun)
end
defp do_flat_map(next_acc, next, mapper, acc, fun) do
case next.({ :cont, next_acc }) do
{ :suspended, [val|next_acc], next } ->
do_flat_map(next_acc, next, mapper, acc, fun, &Enumerable.reduce(mapper.(val), &1, fun))
{ reason, _ } ->
{ reason, elem(acc, 1) }
end
end
defp do_flat_map(next_acc, next, mapper, acc, fun, reduce) do
try do
reduce.(acc)
catch
{ :stream_flat_map, h } ->
next.({ :halt, next_acc })
{ :halted, h }
kind, reason ->
next.({ :halt, next_acc })
:erlang.raise(kind, reason, :erlang.get_stacktrace)
else
{ _, acc } -> do_flat_map(next_acc, next, mapper, { :cont, acc }, fun)
{ :suspended, acc, c } -> { :suspended, acc, &do_flat_map(next_acc, next, mapper, &1, fun, c) }
end
end
defp do_flat_map_each(f, x, acc) do
case f.(x, acc) do
{ :halt, h } -> throw({ :stream_flat_map, h })
{ _, _ } = o -> o
end
end
defp do_flat_map_step(x, acc) do
{ :suspend, [x|acc] }
end
@doc """
Creates a stream that will reject elements according to
the given function on enumeration.
@@ -543,6 +466,40 @@ defmodule Stream do
:ok
end
@doc """
Creates a stream that applies the given function to each
element, emits the result and uses the same result as the accumulator
for the next computation.
## Examples
iex> stream = Stream.scan(1..5, &(&1 + &2))
iex> Enum.to_list(stream)
[1,3,6,10,15]
"""
@spec scan(Enumerable.t, (element, acc -> any)) :: Enumerable.t
def scan(enum, fun) do
lazy enum, :first, fn(f1) -> R.scan_2(fun, f1) end
end
@doc """
Creates a stream that applies the given function to each
element, emits the result and uses the same result as the accumulator
for the next computation. Uses the given `acc` as the starting value.
## Examples
iex> stream = Stream.scan(1..5, 0, &(&1 + &2))
iex> Enum.to_list(stream)
[1,3,6,10,15]
"""
@spec scan(Enumerable.t, acc, (element, acc -> any)) :: Enumerable.t
def scan(enum, acc, fun) do
lazy enum, acc, fn(f1) -> R.scan_3(fun, f1) end
end
@doc """
Lazily takes the next `n` items from the enumerable and stops
enumeration.
@@ -639,6 +596,120 @@ defmodule Stream do
lazy enum, fn(f1) -> R.take_while(fun, f1) end
end
@doc """
Transforms an existing stream.
It expects an accumulator and a function that receives each stream item
and an accumulator, and must return a tuple containing a new stream
(often a list) with the new accumulator or a tuple with `:halt` as first
element and the accumulator as second.
Note: this function is similar to `Enum.flat_map_reduce/3` except the
latter returns both the flat list and accumulator, while this one returns
only the stream.
## Examples
`Stream.transform/3` is a useful as it can be used as basis to implement
many of the functions defined in this module. For example, we can implement
`Stream.take(enum, n)` as follows:
iex> enum = 1..100
iex> n = 3
iex> stream = Stream.transform(enum, 0, fn i, acc ->
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
...> end)
iex> Enum.to_list(stream)
[1,2,3]
"""
@spec transform(Enumerable.t, acc, fun) :: Enumerable.t when
fun: (element, acc -> { Enumerable.t, acc } | { :halt, acc }),
acc: any
def transform(enum, acc, reducer) do
&do_transform(enum, acc, reducer, &1, &2)
end
defp do_transform(enumerables, user_acc, user, inner_acc, fun) do
inner = &do_transform_each(&1, &2, fun)
step = &do_transform_step(&1, &2)
next = &Enumerable.reduce(enumerables, &1, step)
do_transform(user_acc, user, fun, [], next, inner_acc, inner)
end
defp do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner) do
case next.({ :cont, next_acc }) do
{ :suspended, [val|next_acc], next } ->
try do
user.(val, user_acc)
catch
kind, reason ->
next.({ :halt, next_acc })
:erlang.raise(kind, reason, :erlang.get_stacktrace)
else
{ [], user_acc } ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
{ list, user_acc } when is_list(list) ->
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
{ :halt, _user_acc } ->
next.({ :halt, next_acc })
{ :halted, elem(inner_acc, 1) }
{ other, user_acc } ->
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
end
{ reason, _ } ->
{ reason, elem(inner_acc, 1) }
end
end
defp do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
try do
reduce.(inner_acc)
catch
kind, reason ->
next.({ :halt, next_acc })
:erlang.raise(kind, reason, :erlang.get_stacktrace)
else
{ :done, acc } ->
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
{ :halted, acc } ->
next.({ :halt, next_acc })
{ :halted, acc }
{ :suspended, acc, c } ->
{ :suspended, acc, &do_list_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
end
end
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
try do
reduce.(inner_acc)
catch
{ :stream_transform, h } ->
next.({ :halt, next_acc })
{ :halted, h }
kind, reason ->
next.({ :halt, next_acc })
:erlang.raise(kind, reason, :erlang.get_stacktrace)
else
{ _, acc } ->
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
{ :suspended, acc, c } ->
{ :suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
end
end
defp do_transform_each(x, acc, f) do
case f.(x, acc) do
{ :halt, h } -> throw({ :stream_transform, h })
{ _, _ } = o -> o
end
end
defp do_transform_step(x, acc) do
{ :suspend, [x|acc] }
end
@doc """
Creates a stream that only emits elements if they are unique.
@@ -892,14 +963,14 @@ defmodule Stream do
## Examples
Stream.resource(fn -> File.open("sample") end,
Stream.resource(fn -> File.open!("sample") end,
fn file ->
case IO.read(file, :line) do
data when is_binary(data) -> { data, file }
_ -> nil
end
end,
fn file -> File.close!(file) end)
fn file -> File.close(file) end)
"""
@spec resource((() -> acc), (acc -> { element, acc } | nil), (acc -> term)) :: Enumerable.t
+13 -13
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
+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
+6 -6
View File
@@ -76,10 +76,10 @@ defmodule URI do
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
@doc """
Decodes a query string into an orddict.
Decodes a query string into a `HashDict`.
Given a query string of the form "key1=value1&key2=value2...", produces an
orddict with one entry for each key-value pair. Each key and value will be a
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.
@@ -119,10 +119,10 @@ defmodule URI do
end
defp do_decoder(q) do
next =
{ first, next } =
case :binary.split(q, "&") do
[first, rest] -> rest
[first] -> ""
[first, rest] -> { first, rest }
[first] -> { first, "" }
end
current =
+2 -2
View File
@@ -140,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>
"""
@@ -193,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>
"""
+82 -51
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,45 +46,48 @@ 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,
Module = case lists:keyfind(module, 1, Opts) of
@@ -90,54 +95,80 @@ scope_for_eval(Scope, Opts) ->
false -> nil
end,
Scope#elixir_scope{
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, Scope) ->
eval_forms(Tree, Binding, env_for_eval(Opts)).
eval_forms(Tree, Binding, Env, Scope) ->
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope),
{ Exprs, NewScope } = elixir_translator:translate(Tree, ParsedScope),
case Exprs of
[] ->
{ nil, Binding, NewScope };
ParsedEnv = Env#elixir_env{vars=[K || {K,_} <- ParsedScope#elixir_scope.vars]},
{ Erl, NewEnv, NewScope } = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
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.
+14 -13
View File
@@ -1,8 +1,14 @@
-module(elixir_aliases).
-export([last/1, concat/1, safe_concat/1, format_error/1,
ensure_loaded/3, ensure_loaded/4, expand/4, store/7]).
-export([inspect/1, last/1, concat/1, safe_concat/1, format_error/1,
ensure_loaded/3, expand/4, store/7]).
-include("elixir.hrl").
inspect(Atom) when is_atom(Atom) ->
case elixir_compiler:get_opt(internal) of
true -> atom_to_binary(Atom, utf8);
false -> 'Elixir.Inspect.Atom':inspect(Atom)
end.
%% Store an alias in the given scope
store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
{ Aliases, MacroAliases };
@@ -68,22 +74,17 @@ expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
%% 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.
@@ -136,8 +137,8 @@ lookup(Else, Dict, Counter) ->
%% 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) ->
[].
+6 -6
View File
@@ -11,12 +11,12 @@
unless_loaded('MACRO-@', [Caller, Tree], fun() -> nil end).
'MACRO-def'(Caller, Call) -> 'MACRO-def'(Caller, Call, nil).
'MACRO-def'(Caller, Call, Expr) -> definition(Caller, def, Call, Expr).
'MACRO-defp'(Caller, Call, Expr) -> definition(Caller, defp, Call, Expr).
'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) -> definition(Caller, defmacro, Call, Expr).
'MACRO-defmacrop'(Caller, Call, Expr) -> definition(Caller, defmacrop, Call, Expr).
'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),
@@ -35,10 +35,10 @@
{defmodule,2},
{defp,2}].
definition(_Caller, Kind, Call, Expr) ->
define({Line,E}, Kind, Call, Expr) ->
{ EscapedCall, UC } = elixir_quote:escape(Call, true),
{ EscapedExpr, UE } = elixir_quote:escape(Expr, true),
Args = [Kind, not(UC or UE), EscapedCall, EscapedExpr, env()],
Args = [Line, Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E)],
{ { '.', [], [elixir_def, store_definition] }, [], Args }.
unless_loaded(Fun, Args, Callback) ->
+69 -104
View File
@@ -1,127 +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_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] }) -> [Left|extract_or_clauses(Right)];
extract_or_clauses(Term) -> [Term].
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) ->
@@ -142,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;
@@ -160,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
@@ -182,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) ->
@@ -242,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",
+59 -34
View File
@@ -1,6 +1,6 @@
-module(elixir_compiler).
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
-export([core/0, module/4, eval_forms/4, format_error/1]).
-export([core/0, module/4, eval_forms/3, format_error/1]).
-include("elixir.hrl").
%% Public API
@@ -15,7 +15,7 @@ get_opt(Key) ->
%% 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) ->
@@ -25,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
@@ -45,47 +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 = list_to_tuple([V || { _, V } <- 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(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) ->
Tuple = { tuple, Line, [{ var, Line, K } || { K, _ } <- Vars] },
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, 1 }, { '__RELATIVE__', 0 }] },
{ function, Line, Fun, 1, [
{ clause, Line, [Tuple], [], Exprs }
{ clause, Line, [Tuple], [], [Expr] }
] },
{ function, Line, '__RELATIVE__', 0, [
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
@@ -98,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
@@ -177,6 +200,7 @@ core_main() ->
<<"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">>,
@@ -194,8 +218,7 @@ core_main() ->
<<"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) ->
@@ -207,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.
+70 -67
View File
@@ -2,7 +2,7 @@
-module(elixir_def).
-export([table/1, clauses_table/1, setup/1,
cleanup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/5, unwrap_definitions/2,
delete_definition/2, store_definition/6, unwrap_definitions/1,
store_each/8, format_error/1]).
-include("elixir.hrl").
@@ -48,17 +48,14 @@ delete_definition(Module, Tuple) ->
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
store_definition(Kind, CheckClauses, Call, Body, ExEnv) ->
#elixir_env{line=Line} = Env = elixir_env:ex_to_env(ExEnv),
S = elixir_env:env_to_scope(Env),
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 NameAndArgs of
{ N, _, A } when is_atom(N), is_atom(A) -> { N, [] };
{ N, _, A } when is_atom(N), is_list(A) -> { N, A };
_ ->
Format = [Kind, 'Elixir.Macro':to_string(NameAndArgs)],
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format)
_ -> elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE, { invalid_def, Kind, NameAndArgs })
end,
%% Now that we have verified the call format,
@@ -78,29 +75,29 @@ store_definition(Kind, CheckClauses, Call, Body, ExEnv) ->
LinifyGuards = elixir_quote:linify(Line, Key, Guards),
LinifyBody = elixir_quote:linify(Line, Key, Body),
assert_no_aliases_name(Line, Name, Args, S),
store_definition(Kind, Line, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, File, S).
assert_no_aliases_name(Line, Name, Args, E),
store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, File, E).
store_definition(Kind, Line, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_scope{module=Module} = DS) ->
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{function=Tuple},
elixir_tracker:record_definition(Tuple, Kind, Module),
E = ER#elixir_env{function=Tuple},
elixir_locals:record_definition(Tuple, Kind, Module),
Location = retrieve_file(Line, MetaFile, Module, S),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S),
{ 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,
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),
@@ -111,83 +108,85 @@ store_definition(Kind, Line, CheckClauses, Name, Args, Guards, Body, MetaFile, #
%% @on_definition
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S) ->
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
case elixir_compiler:get_opt(internal) of
true ->
ok;
_ ->
Env = elixir_env:scope_to_ex({ 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 meta file, @file or fallback to default
%% Retrieve location from meta file or @file, otherwise nil
retrieve_file(Line, File, Module, S) ->
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 ->
{ elixir_utils:characters_to_list(retrieve_file(File, S)), Line };
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.
retrieve_file(nil, S) -> S#elixir_scope.file;
retrieve_file(File, _S) -> File.
%% Compile super
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
compile_super(Module, true, #elixir_env{function=Function}) ->
elixir_def_overridable:store(Module, Function, true);
compile_super(_Module, _S) -> ok.
compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, Args, Guards, Body, S) when is_integer(Line) ->
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_env, scope_to_ex, [{ 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] }.
@@ -198,13 +197,13 @@ 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_definitions(File, Module) ->
unwrap_definitions(Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
unwrap_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
unwrap_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)],
@@ -220,28 +219,24 @@ unwrap_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, F
{ Functions, Tail };
_ ->
NewAll = [Tuple|All],
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
end,
unwrap_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_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_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
{ Fun, [Tuple|Exports], Private, [Tuple|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_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_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) },
@@ -249,10 +244,10 @@ unwrap_definition(defmacrop, Tuple, Fun, Exports, 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
@@ -312,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)].
@@ -323,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.
@@ -355,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.
+3 -3
View File
@@ -43,9 +43,9 @@ store(Module, Function, GenerateName) ->
false -> { Kind, Name }
end,
case code:is_loaded('Elixir.Module.DispatchTracker') of
case code:is_loaded('Elixir.Module.LocalsTracker') of
{ _, _ } ->
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
'Elixir.Module.LocalsTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
_ ->
ok
end,
@@ -67,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),
+144 -176
View File
@@ -2,33 +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]).
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
@@ -36,227 +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 } ->
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(Meta, Receiver, Name, Arity, S)
false -> remote_function(Receiver, Name, Arity, E)
end.
remote_function(Meta, Receiver, Name, Arity, S) ->
remote_function(Receiver, Name, Arity, E) ->
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
Tuple = { Name, Arity },
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
Final =
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ remote, Final, Name, Arity }.
Line = ?line(Meta),
%% Dispatches
{ { 'fun', Line, { function,
{ atom, Line, Final },
{ atom, Line, Name },
{ integer, Line, Arity}
} }, S }.
%% 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();
{ ok, Receiver, Tree } ->
translate_expansion(Meta, Receiver, Tree, S)
end
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, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
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 };
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
true -> Callback(erlang);
false ->
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
{ error, noexpansion } ->
Callback();
{ ok, Receiver, Tree } ->
translate_expansion(Meta, Receiver, Tree, S)
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.
end;
dispatch_require(_Meta, Receiver, _Name, _Args, _E, Callback) ->
Callback(Receiver).
%% 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).
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),
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
Function = S#elixir_scope.function,
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);
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, S),
%% 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);
case Fun of
false ->
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
%% There is no local. Dispatch the import.
_ when Local == false ->
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
%% Dispatch to the local.
_ ->
case Result of
%% In case it is an import or the receive is the same as the
%% current module (happens in bootstraping/conflicts), resolve it.
{ Kind, Receiver } when Kind == import; Receiver == Module ->
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
%% There is a function and a match. This is a conflict.
{ _, Receiver } ->
Error = { macro_conflict, { Receiver, Name, Arity } },
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
%% There is a function and no match, invoke the local function.
false ->
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
end
elixir_locals:record_local(Tuple, Module, Function),
{ ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E) }
end.
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
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, 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) };
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 } ->
expand_require(Meta, Receiver, Tuple, Args, Module, S);
_ ->
{ error, noexpansion }
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, Module, S) ->
Function = S#elixir_scope.function,
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_with_context_counter(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) ->
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 };
@@ -266,7 +239,7 @@ 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.
@@ -286,34 +259,29 @@ is_import(Meta) ->
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].
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
@@ -333,14 +301,14 @@ 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)
?kernel:'__info__'(macros)
catch
error:undef -> []
end.
@@ -377,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 },
@@ -408,6 +375,7 @@ in_erlang_functions() ->
% { now, 0 },
{ round, 1 },
{ self, 0 },
{ send, 2 },
{ size, 1 },
{ spawn, 1 },
{ spawn, 3 },
+34 -23
View File
@@ -1,38 +1,49 @@
-module(elixir_env).
-include("elixir.hrl").
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2,
ex_to_scope/1, scope_to_ex/1]).
-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,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases,
macro_counter=MacroCounter,lexical_tracker=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,
macro_counter=MacroCounter,lexical_tracker=Lexical}.
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)}]
counter=[{'_',length(Vars)}]
}.
scope_to_ex({ 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,
macro_counter=MacroCounter,vars=Vars,lexical_tracker=Lexical} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Context, Requires, Aliases,
Functions, Macros, MacroAliases, MacroCounter, ContextModules,
[Pair || { Pair, _ } <- Vars], Lexical }.
%% SCOPE MERGING
ex_to_scope(Env) ->
env_to_scope(ex_to_env(Env)).
%% 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 -70
View File
@@ -1,59 +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_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) ->
case elixir_compiler:get_opt(internal) of
true -> atom_to_binary(Atom, utf8);
false -> 'Elixir.Inspect.Atom':inspect(Atom)
end.
-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().
@@ -82,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, []).
@@ -135,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;
@@ -179,34 +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_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) ->
@@ -215,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)
@@ -223,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_env:scope_to_ex({ ?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).
+30 -31
View File
@@ -7,54 +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 = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
fun() -> get_functions(Ref) end, S),
S#elixir_scope{functions=Functions}.
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 = calculate(Meta, Ref, Opts, S#elixir_scope.macros, Existing, S),
S#elixir_scope{macros=Macros}.
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, 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;
@@ -75,10 +76,9 @@ calculate(Meta, Key, Opts, Old, Existing, S) ->
Final = remove_internals(Set),
case Final of
[] ->
keydelete(Key, Old);
[] -> keydelete(Key, Old);
_ ->
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
ensure_no_special_form_conflict(Meta, E#elixir_env.file, Key, Final),
[{ Key, Final }|keydelete(Key, Old)]
end.
@@ -98,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) ->
@@ -135,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
@@ -194,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;
+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_env:scope_to_ex({ ?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_env:scope_to_ex({ ?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]);
+58 -60
View File
@@ -1,5 +1,5 @@
-module(elixir_module).
-export([compile/4, compile/5, data_table/1, docs_table/1,
-export([compile/4, data_table/1, docs_table/1,
format_error/1, eval_callbacks/5]).
-include("elixir.hrl").
@@ -19,31 +19,38 @@ docs_table(Module) ->
%% Compilation hook
compile(Module, Block, Vars, ExEnv) ->
#elixir_env{line=Line} = Env = elixir_env:ex_to_env(ExEnv),
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
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;
_ -> Env#elixir_env{lexical_tracker=nil, function=nil}
nil -> Env#elixir_env{module=Module, local=nil};
_ -> Env#elixir_env{lexical_tracker=nil, function=nil, module=Module, local=nil}
end,
compile(Line, Module, Block, Vars, elixir_env:env_to_scope_with_vars(LexEnv, Dict)).
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;
compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
S = RawS#elixir_scope{module=Module},
File = S#elixir_scope.file,
FileList = elixir_utils:characters_to_list(File),
compile(Module, _Block, _Vars, #elixir_env{line=Line,file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Module });
compile(Module, Block, Vars, ExEnv) ->
compile(Module, Block, Vars, elixir_env:ex_to_env(ExEnv)).
do_compile(Line, Module, Block, Vars, E) ->
File = E#elixir_env.file,
check_module_availability(Line, File, Module),
build(Line, File, Module, S#elixir_scope.lexical_tracker),
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_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),
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
@@ -53,31 +60,30 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
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 }).
end.
%% Hook that builds both attribute and functions and set up common hooks.
@@ -108,18 +114,16 @@ 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, S) ->
KV = [{ K, V } || { _, _, K, V } <- Vars],
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, KV, 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_env:scope_to_ex({ 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.
@@ -240,10 +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) ->
Env = elixir_env:scope_to_ex({ Line, S }),
eval_callbacks(Line, Module, after_compile, [Env, Binary], S),
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) ->
@@ -351,41 +355,41 @@ else_clause() ->
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
{ Binding, S } = elixir_scope:load_binding([], RawS),
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].
@@ -393,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)]).
+28 -57
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 when_op_eol. %% when
Right 40 type_op_eol. %% ::
Left 50 inc_op_eol. %% inlist, inbits
Right 60 default_op_eol. %% //
Left 70 tail_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'].
@@ -140,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' }.
@@ -158,12 +148,8 @@ 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 -> 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' }.
@@ -179,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' }.
@@ -315,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'.
@@ -335,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'.
@@ -353,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'.
@@ -578,7 +548,7 @@ extract_identifier(Other) -> Other.
%% Fn
build_fn(Op, Stab) ->
{ fn, meta(Op), [Stab] }.
{ fn, meta(Op), Stab }.
%% Access
@@ -617,35 +587,33 @@ string_parts(Parts) ->
string_part(Binary) when is_binary(Binary) ->
Binary;
string_part({ Line, Tokens }) ->
Form = string_tokens_parse(Line, Tokens),
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__', meta(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())
@@ -668,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) ->
+71 -67
View File
@@ -20,7 +20,7 @@ linify_with_context_counter(Line, Var, Exprs) when is_integer(Line) ->
do_linify(Line, line, Var, Exprs).
do_linify(Line, Key, { Receiver, Counter } = Var, { Left, Meta, Receiver })
when is_atom(Left), is_list(Meta) ->
when is_atom(Left), is_list(Meta), Left /= '_' ->
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver);
do_linify(Line, Key, { _, Counter } = Var, { Lexical, [_|_] = Meta, [_|_] = Args }) when ?lexical(Lexical) ->
@@ -42,17 +42,21 @@ do_tuple_linify(Line, Key, Var, Meta, Left, Right) ->
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 });
_ when Key == line ->
keystore(line, Meta, Line);
_ ->
case ?line(Meta) of
0 -> keystore(line, Meta, Line);
_ -> Meta
end
do_linify_meta(Line, line, Meta)
end.
%% Some expressions cannot be unquoted at compilation time.
@@ -145,10 +149,10 @@ escape(Expr, Unquote) ->
%% 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 = [ { '{}', [],
@@ -158,13 +162,13 @@ 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 }.
%% 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, _) ->
@@ -172,42 +176,42 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
%% Aliases
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, keydelete(counter, Meta), Annotation),
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
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,
NewMeta = case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, S) of
elixir_dispatch:find_import(Meta, Name, Arity, E) of
false ->
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
@@ -217,68 +221,68 @@ do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S)
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
end,
Annotated = annotate({ Name, NewMeta, ArgsOrAtom }, Q#elixir_quote.context, file(S, Q)),
do_quote_tuple(Annotated, 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, S) ->
Annotated = annotate(Tuple, Q#elixir_quote.context, file(S, Q)),
do_quote_tuple(Annotated, Q, S);
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) ->
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args,
Q#elixir_quote.context, file(S, Q)],
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
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 }.
file(#elixir_scope{file=File}, #elixir_quote{keep=true}) -> File;
file(#elixir_env{file=File}, #elixir_quote{keep=true}) -> File;
file(_, _) -> nil.
meta(Meta, #elixir_quote{keep=true}) ->
@@ -319,30 +323,30 @@ keynew(Key, Meta, Value) ->
%% 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),
{ 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_list(Meta, list, [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_list(Meta, Fun, Args) ->
{ { '.', Meta, [elixir_quote, Fun] }, Meta, Args }.
+74 -112
View File
@@ -1,146 +1,110 @@
%% Convenience functions used to manipulate scope and its variables.
-module(elixir_scope).
-export([translate_var/5,
build_erl_var/2, build_ex_var/2,
-export([translate_var/4, build_var/2,
load_binding/2, dump_binding/2,
umergev/2, umergec/2, umergea/2, merge_clause_vars/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,
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 }),
{ 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,
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
},
{ { var, Line, NewVar }, FS }
end;
_ when Exists ->
{ { var, Line, Current }, S }
end.
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
@@ -148,9 +112,7 @@ 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([{Key,Value}|T], Binding, Vars, Counter) ->
@@ -160,15 +122,15 @@ load_binding([{Key,Value}|T], Binding, Vars, Counter) ->
end,
InternalName = ?atom_concat(["_@", Counter]),
load_binding(T,
[{InternalName,Value}|Binding],
orddict:store(Actual, InternalName, Vars), Counter + 1);
orddict:store(InternalName, Value, Binding),
orddict:store(Actual, { InternalName, 0 }, Vars), Counter + 1);
load_binding([], Binding, Vars, Counter) ->
{ lists:reverse(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;
+167 -527
View File
@@ -1,297 +1,112 @@
%% 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]).
-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),
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) ->
translate({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
{ { 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_env:scope_to_ex({ ?line(Meta), S }),
{ ex_env_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, 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 },
Keep = lists:keyfind(location, 1, TKV) == { location, keep },
Line = proplists:get_value(line, TKV, false),
{ 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, keep=Keep, unquote=Unquote,
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ST),
translate_each(Quoted, ST);
translate_each({ quote, Meta, [_, _] }, S) ->
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
%% 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_each({'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),
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:match(Meta, RClauses, NS),
{ TClauses, TS } = elixir_clauses:clauses(Meta, RClauses, NS),
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
%% Try
translate_each({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
assert_no_match_or_guard_scope(Meta, 'try', S),
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_each(Do, S#elixir_scope{noname=true}),
{ TDo, SB } = elixir_translator:translate(Do, S),
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, mergec(S, SB)),
After = proplists:get_value('after', Clauses, nil),
{ TAfter, SA } = translate_each(After, umergea(S, SC)),
{ TAfter, SA } = translate(After, mergec(S, SC)),
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
Else = elixir_clauses:get_pairs(else, Clauses),
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, unblock(TAfter) }, SF };
%% Receive
translate_each({'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),
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:match(Meta, Do, S),
{ TClauses, SC } = elixir_clauses:clauses(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),
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 }
@@ -299,13 +114,12 @@ translate_each({'receive', Meta, [KV] }, S) when is_list(KV) ->
%% 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),
@@ -316,8 +130,8 @@ 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),
@@ -325,174 +139,131 @@ translate_each({ super, Meta, Args }, S) when is_list(Args) ->
%% 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() ->
case lists:keyfind(var, 1, Meta) of
{ var, true } ->
compile_error(Meta, S#elixir_scope.file, "expected var ~ts to expand to an existing "
"variable or be a part of a match", [Name]);
_ ->
translate_each({ Name, Meta, [] }, S)
end
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),
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
{ TLeft, SL } = translate(Left, S),
{ TRight, SR } = translate(Right, mergec(S, SL)),
Callback = fun() ->
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,
Line = ?line(Meta),
{ TArgs, SA } = translate_args(Args, umergec(S, SR)),
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, umergev(SL, umergev(SR, SA)) }
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.
ex_env_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).
%% Case
%% 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,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 } ->
{'cond',true} ->
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
_ ->
Clauses
@@ -500,197 +271,66 @@ rewrite_case_clauses([
rewrite_case_clauses(Clauses) ->
Clauses.
% Pack a list of expressions from a block.
pack({ 'block', _, Exprs }) -> Exprs;
pack(Expr) -> [Expr].
%% Opts
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_env:scope_to_ex({ ?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]).
%% 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, #elixir_scope{context_modules=Context} = 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,
%% Add the alias to context_modules if defined is true.
%% This is used by defmodule.
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, Old, TKV, S#elixir_scope.aliases,
S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker),
{ { atom, ?line(Meta), nil },
S#elixir_scope{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext} }.
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.
%% Do not allow expansion of the alias itself.
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) }.
%% __op__ helpers
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),
{ 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.
+38 -84
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_env:scope_to_ex({ ?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),
@@ -160,7 +121,7 @@ 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);
_ ->
@@ -244,13 +205,6 @@ 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) ->
{ erl(Meta, '=='), Meta, [
{ erl(Meta, element), Meta, [1, Var] },
+30 -52
View File
@@ -1,10 +1,8 @@
%% 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/4, returns_boolean/1,
file_type/1, file_type/2, relative_to_cwd/1]).
-include("elixir.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
@@ -175,9 +152,10 @@ convert_to_boolean(Line, Expr, Bool, S) when is_integer(Line) ->
%% and false checks in the same clause since
%% it makes dialyzer happy.
do_convert_to_boolean(Line, Expr, Bool, S) ->
Any = { var, Line, '_' },
{ Var, TS } = elixir_scope:build_erl_var(Line, S),
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
{ 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 },
+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
+15 -19
View File
@@ -143,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]
@@ -170,13 +165,22 @@ defmodule EnumTest.List do
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
assert Enum.reduce([], 1, fn(x, acc) -> x + acc end) == 1
assert Enum.reduce([1, 2, 3], 1, fn(x, acc) -> x + acc end) == 7
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
@@ -327,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
@@ -343,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
@@ -442,15 +446,15 @@ 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
@@ -521,14 +525,6 @@ defmodule EnumTest.Range do
refute Enum.empty?(range)
end
test :first do
range = 1..0
assert Enum.first(range) == 1
range = 1..2
assert Enum.first(range) == 1
end
test :each do
try do
range = 1..0
+25 -23
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,37 +60,37 @@ 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"
@@ -97,12 +98,13 @@ defmodule Kernel.ExceptionTest do
[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
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
test "defexception" do
defexception SampleError, message: nil do
# Check do block is properly inline
def exception(_), do: SampleError[message: "hello"]
+20 -20
View File
@@ -232,7 +232,7 @@ defmodule FileTest do
try do
File.touch!(dest)
assert (File.cp_r(src, dest) |> is_io_error?)
assert (File.cp_r(src, dest) |> io_error?)
after
File.rm_rf dest
end
@@ -271,7 +271,7 @@ defmodule FileTest do
try do
File.mkdir(dest)
File.write!(Path.join(dest, "a"), "hello")
assert (File.cp_r(src, dest) |> is_io_error?)
assert (File.cp_r(src, dest) |> io_error?)
after
File.rm_rf dest
end
@@ -382,7 +382,7 @@ defmodule FileTest do
assert src_mode == dest_mode
end
defp is_io_error?(result) do
defp io_error?(result) do
elem(result, 1) in [:enotdir, :eio, :enoent, :eisdir]
end
end
@@ -391,13 +391,13 @@ defmodule FileTest do
use ExUnit.Case
test :regular do
assert File.regular?(__FILE__)
assert File.regular?(String.to_char_list!(__FILE__))
refute File.regular?("#{__FILE__}.unknown")
assert File.regular?(__ENV__.file)
assert File.regular?(String.to_char_list!(__ENV__.file))
refute File.regular?("#{__ENV__.file}.unknown")
end
test :exists do
assert File.exists?(__FILE__)
assert File.exists?(__ENV__.file)
assert File.exists?(fixture_path)
assert File.exists?(fixture_path("file.txt"))
@@ -433,12 +433,12 @@ defmodule FileTest do
end
test :read_with_list do
assert { :ok, "FOO\n" } = File.read(Path.expand('../fixtures/file.txt', __FILE__))
assert { :error, :enoent } = File.read(Path.expand('../fixtures/missing.txt', __FILE__))
assert { :ok, "FOO\n" } = File.read(Path.expand('fixtures/file.txt', __DIR__))
assert { :error, :enoent } = File.read(Path.expand('fixtures/missing.txt', __DIR__))
end
test :read_with_utf8 do
assert { :ok, "Русский\n日\n" } = File.read(Path.expand('../fixtures/utf8.txt', __FILE__))
assert { :ok, "Русский\n日\n" } = File.read(Path.expand('fixtures/utf8.txt', __DIR__))
end
test :read! do
@@ -595,7 +595,7 @@ defmodule FileTest do
fixture = fixture_path("file.txt")
invalid = Path.join fixture, "test"
assert File.exists?(fixture)
assert (File.mkdir(invalid) |> is_io_error?)
assert (File.mkdir(invalid) |> io_error?)
refute File.exists?(invalid)
end
@@ -676,7 +676,7 @@ defmodule FileTest do
test :mkdir_p_with_invalid_path do
assert File.exists?(fixture_path("file.txt"))
invalid = Path.join fixture_path("file.txt"), "test/foo"
assert (File.mkdir(invalid) |> is_io_error?)
assert (File.mkdir(invalid) |> io_error?)
refute File.exists?(invalid)
end
@@ -700,7 +700,7 @@ defmodule FileTest do
end
end
defp is_io_error?(result) do
defp io_error?(result) do
{:error,errorcode} = result
errorcode in [:enotdir, :eio, :enoent, :eisdir]
end
@@ -749,7 +749,7 @@ defmodule FileTest do
end
test :rmdir_with_file do
assert (File.rmdir(fixture_path("file.txt")) |> is_io_error?)
assert (File.rmdir(fixture_path("file.txt")) |> io_error?)
end
test :rmdir! do
@@ -869,18 +869,18 @@ defmodule FileTest do
assert File.rm_rf!(fixture) == []
end
defp is_io_error?(result) do
defp io_error?(result) do
elem(result, 1) in [:enotdir, :eio, :enoent, :eisdir]
end
end
test :stat do
{:ok, info} = File.stat(__FILE__)
{:ok, info} = File.stat(__ENV__.file)
assert info.mtime
end
test :stat! do
assert File.stat!(__FILE__).mtime
assert File.stat!(__ENV__.file).mtime
end
test :stat_with_invalid_file do
@@ -1131,7 +1131,7 @@ defmodule FileTest do
end
test :invalid_cd do
assert(File.cd(fixture_path("file.txt")) |> is_io_error?)
assert(File.cd(fixture_path("file.txt")) |> io_error?)
end
test :invalid_cd! do
@@ -1182,7 +1182,7 @@ defmodule FileTest do
test :touch_with_failure do
fixture = fixture_path("file.txt/bar")
assert (File.touch(fixture) |> is_io_error?)
assert (File.touch(fixture) |> io_error?)
end
test :touch_with_success! do
@@ -1293,7 +1293,7 @@ defmodule FileTest do
{ { year - 1, month, day }, time }
end
defp is_io_error?(result) do
defp io_error?(result) do
{:error,errorcode} = result
errorcode in [:enotdir, :eio, :enoent, :eisdir]
end
@@ -0,0 +1,3 @@
defmodule Bat do
ThisModuleWillNeverBeAvailable[]
end
+3 -6
View File
@@ -6,6 +6,7 @@ defmodule FloatTest do
test :parse do
assert Float.parse("12") === {12.0, ""}
assert Float.parse("-12") === {-12.0, ""}
assert Float.parse("-0.1") === {-0.1, ""}
assert Float.parse("123456789") === {123456789.0, ""}
assert Float.parse("12.5") === {12.5, ""}
assert Float.parse("12.524235") === {12.524235, ""}
@@ -60,14 +61,10 @@ defmodule FloatTest do
test :round do
assert Float.round(5.5675, 3) === 5.568
assert Float.round(5.5675, 3, :down) === 5.567
assert Float.round(-5.5674, 3) === -5.567
assert Float.round(5.5, 3) === 5.5
assert Float.round(5.5e-10, 10) === 6.0e-10
assert Float.round(5.5e-10, 10, :down) === 5.0e-10
assert Float.round(5.5e-10, 8) === 1.0e-8
assert Float.round(5.5e-10, 8, :down) === 0.0
assert Float.round(5.5e-10, 8) === 0.0
assert Float.round(5.0, 0) === 5.0
assert Float.round(-1.3456, 3) === -1.345
assert Float.round(-1.3456, 3, :down) === -1.346
end
end
@@ -19,10 +19,9 @@ defmodule GenEvent.BehaviourTest do
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
test :using do
test "using defines callbacks" do
{ :ok, pid } = :gen_event.start_link
:gen_event.add_handler(pid, MyEventHandler, [])
@@ -32,5 +31,4 @@ defmodule GenEvent.BehaviourTest do
assert :gen_event.call(pid, MyEventHandler, :notifications) == [1, 2]
assert :gen_event.call(pid, MyEventHandler, :notifications) == []
end
end
@@ -0,0 +1,43 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule GenFSM.BehaviourTest do
use ExUnit.Case
setup_all do
:error_logger.tty(false)
:ok
end
teardown_all do
:error_logger.tty(true)
:ok
end
defmodule Sample do
use GenFSM.Behaviour
def init(args) do
{ :ok, :sample, args }
end
end
test "sync event stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
catch_exit(:gen_fsm.sync_send_all_state_event(pid, :unknown_request))
assert_receive { :EXIT, ^pid, {:bad_sync_event, :sample, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
test "event stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
:gen_fsm.send_all_state_event(pid, :unknown_request)
assert_receive { :EXIT, ^pid, {:bad_event, :sample, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
end
@@ -1,7 +1,17 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule GenServer.BehaviourTest do
use ExUnit.Case, async: true
use ExUnit.Case
setup_all do
:error_logger.tty(false)
:ok
end
teardown_all do
:error_logger.tty(true)
:ok
end
defmodule Sample do
use GenServer.Behaviour
@@ -29,10 +39,30 @@ defmodule GenServer.BehaviourTest do
end
end
test :using do
test "using defines callbacks" do
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
assert :gen_server.call(pid, :pop) == :hello
assert :gen_server.cast(pid, { :push, :world }) == :ok
assert :gen_server.call(pid, :pop) == :world
end
test "call stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
catch_exit(:gen_server.call(pid, :unknown_request))
assert_receive { :EXIT, ^pid, {:bad_call, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
test "cast stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
:gen_server.cast(pid, :unknown_request)
assert_receive { :EXIT, ^pid, {:bad_cast, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
end
+36 -11
View File
@@ -44,7 +44,6 @@ defmodule Inspect.AtomTest do
test :container do
assert inspect(:<<>>) == ":<<>>"
assert inspect(:[]) == ":[]"
assert inspect(:{}) == ":{}"
end
end
@@ -71,8 +70,22 @@ defmodule Inspect.BitStringTest do
assert inspect(" ゆんゆん") == "\" ゆんゆん\""
end
test :unprintable do
assert inspect(<<193>>) == "<<193>>"
test :opt_infer do
assert inspect(<<"eric", 193, "mj">>, binaries: :infer) == %s(<<101, 114, 105, 99, 193, 109, 106>>)
assert inspect(<<"eric">>, binaries: :infer) == %s("eric")
assert inspect(<<193>>, binaries: :infer) == %s(<<193>>)
end
test :opt_as_strings do
assert inspect(<<"eric", 193, "mj">>, binaries: :as_strings) == %s("eric\\301mj")
assert inspect(<<"eric">>, binaries: :as_strings) == %s("eric")
assert inspect(<<193>>, binaries: :as_strings) == %s("\\301")
end
test :opt_as_binaries do
assert inspect(<<"eric", 193, "mj">>, binaries: :as_binaries) == "<<101, 114, 105, 99, 193, 109, 106>>"
assert inspect(<<"eric">>, binaries: :as_binaries) == "<<101, 114, 105, 99>>"
assert inspect(<<193>>, binaries: :as_binaries) == "<<193>>"
end
test :unprintable_with_opts do
@@ -154,8 +167,8 @@ defmodule Inspect.TupleTest do
assert inspect({ 1, 2, 3, 4 }, limit: 3) == "{1, 2, 3, ...}"
end
test :with_raw do
assert inspect(Config.new, raw: true) == "{Inspect.TupleTest.Config, 1, []}"
test :with_records_false do
assert inspect(Config.new, records: false) == "{Inspect.TupleTest.Config, 1, []}"
end
end
@@ -181,16 +194,28 @@ defmodule Inspect.ListTest do
"[foo: [1, 2, 3, :bar],\n bazzz: :bat]"
end
test :keyword_with_raw do
assert inspect([a: 1], raw: true) == "[{:a, 1}]"
assert inspect([a: 1, b: 2], raw: true) == "[{:a, 1}, {:b, 2}]"
assert inspect([a: 1, a: 2, b: 2], raw: true) == "[{:a, 1}, {:a, 2}, {:b, 2}]"
end
test :non_keyword do
assert inspect([{ Regex, 1 }]) == "[{Regex, 1}]"
end
test :opt_infer do
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :infer) == "[101, 114, 105, 99, 0, 109, 106]"
assert inspect('eric', char_lists: :infer) == "'eric'"
assert inspect([0], char_lists: :infer) == "[0]"
end
test :opt_as_strings do
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :as_char_lists) == "'eric\\000mj'"
assert inspect('eric', char_lists: :as_char_lists) == "'eric'"
assert inspect([0], char_lists: :as_char_lists) == "'\\000'"
end
test :opt_as_lists do
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :as_lists) == "[101, 114, 105, 99, 0, 109, 106]"
assert inspect('eric', char_lists: :as_lists) == "[101, 114, 105, 99]"
assert inspect([0], char_lists: :as_lists) == "[0]"
end
test :non_printable do
assert inspect([{:b, 1}, {:a, 1}]) == "[b: 1, a: 1]"
end
@@ -1,7 +1,8 @@
Code.require_file "../test_helper.exs", __DIR__
Code.require_file "../../test_helper.exs", __DIR__
defmodule IEx.AnsiDocsTest do
use IEx.Case
defmodule IO.ANSI.DocsTest do
use ExUnit.Case, async: true
import ExUnit.CaptureIO
@colors [ enabled: true,
doc_code: "cyan,bright",
@@ -11,19 +12,16 @@ defmodule IEx.AnsiDocsTest do
doc_headings: "yellow,bright",
doc_title: "reverse,yellow,bright" ]
@opts [colors: @colors]
def format(str, use_ansi // true) do
cmd = "IEx.ANSIDocs.print(#{inspect str}, #{inspect use_ansi})"
capture_iex(cmd, @opts)
def format_heading(str) do
capture_io(fn -> IO.ANSI.Docs.print_heading(str, @colors) end) |> String.strip
end
test "non-ansi heading just uses an asterisk" do
assert capture_iex("IEx.ANSIDocs.print_heading(\"wibble\", false)", []) == "* wibble"
def format(str) do
capture_io(fn -> IO.ANSI.Docs.print(str, @colors) end) |> String.strip
end
test "ansi heading is formatted" do
result = capture_iex("IEx.ANSIDocs.print_heading(\"wibble\", true)", @opts)
test "heading is formatted" do
result = format_heading("wibble")
assert String.starts_with?(result, "\e[0m\n\e[7m\e[33m\e[1m")
assert String.ends_with?(result, "\e[0m\n\e[0m")
assert String.contains?(result, " wibble ")
+11 -11
View File
@@ -5,25 +5,25 @@ defmodule IOTest do
import ExUnit.CaptureIO
test :read_with_count do
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__), [:charlist])
assert 'FOO' == IO.read(file, 3)
assert File.close(file) == :ok
end
test :read_with_utf8_and_binary do
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
assert "Русский" == IO.read(file, 7)
assert File.close(file) == :ok
end
test :binread do
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__))
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
assert "Русский" == IO.binread(file, 14)
assert File.close(file) == :ok
end
test :getn do
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__))
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__))
assert "F" == IO.getn(file, "")
assert "O" == IO.getn(file, "")
assert "O" == IO.getn(file, "")
@@ -33,47 +33,47 @@ defmodule IOTest do
end
test :getn_with_count do
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__), [:charlist])
assert 'FOO' == IO.getn(file, "", 3)
assert File.close(file) == :ok
end
test :getn_with_utf8_and_binary do
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
assert "Русский" == IO.getn(file, "", 7)
assert File.close(file) == :ok
end
test :gets do
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__), [:charlist])
assert 'FOO\n' == IO.gets(file, "")
assert :eof == IO.gets(file, "")
assert File.close(file) == :ok
end
test :gets_with_utf8_and_binary do
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
assert "Русский\n" == IO.gets(file, "")
assert "日\n" == IO.gets(file, "")
assert File.close(file) == :ok
end
test :readline do
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__))
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__))
assert "FOO\n" == IO.read(file, :line)
assert :eof == IO.read(file, :line)
assert File.close(file) == :ok
end
test :readline_with_utf8_and_binary do
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
assert "Русский\n" == IO.read(file, :line)
assert "日\n" == IO.read(file, :line)
assert File.close(file) == :ok
end
test :binreadline do
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__))
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
assert "Русский\n" == IO.binread(file, :line)
assert "日\n" == IO.binread(file, :line)
assert File.close(file) == :ok
+20 -33
View File
@@ -17,27 +17,19 @@ bar """
"""
end
test :heredoc_with_extra do
assert 21 == __ENV__.line
assert "foo\nbar\nbar\n" == """ <> "bar\n"
foo
bar
"""
end
test :aligned_heredoc do
assert "foo\nbar\nbar\n" == """ <> "bar\n"
assert "foo\nbar\n" == """
foo
bar
"""
end
test :heredoc_with_interpolation do
assert "37\n" == """
assert "29\n" == """
#{__ENV__.line}
"""
assert "\n42\n" == """
assert "\n34\n" == """
#{__ENV__.line}
"""
@@ -107,44 +99,39 @@ bar
end
test :literal do
assert <<106, 111, 115, 101>> == << "jose" :: binary >>
assert <<106, 111, 115, 101>> == << "jose" :: bits >>
assert <<106, 111, 115, 101>> == << "jose" :: bitstring >>
assert <<106, 111, 115, 101>> == << "jose" :: bytes >>
assert <<106,111,115,195,169>> == << "josé" :: binary >>
assert <<106,111,115,195,169>> == << "josé" :: bits >>
assert <<106,111,115,195,169>> == << "josé" :: bitstring >>
assert <<106,111,115,195,169>> == << "josé" :: bytes >>
assert <<106, 111, 115, 101>> == << "jose" :: utf8 >>
assert <<106, 111, 115, 101>> == << 'jose' :: utf8 >>
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << "jose" :: utf16 >>
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << 'jose' :: utf16 >>
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << "jose" :: [utf16, little] >>
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << 'jose' :: [utf16, little] >>
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << "jose" :: utf32 >>
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << 'jose' :: utf32 >>
assert <<106,111,115,195,169>> == << "josé" :: utf8 >>
assert <<0,106,0,111,0,115,0,233>> == << "josé" :: utf16 >>
assert <<106,0,111,0,115,0,233,0>> == << "josé" :: [utf16, little] >>
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,233>> == << "josé" :: utf32 >>
end
test :literal_errors do
assert_raise ArgumentError, fn ->
assert_raise CompileError, fn ->
Code.eval_string(%s[<< "foo" :: integer >>])
end
assert_raise ArgumentError, fn ->
assert_raise CompileError, fn ->
Code.eval_string(%s[<< "foo" :: float >>])
end
assert_raise ArgumentError, fn ->
assert_raise CompileError, fn ->
Code.eval_string(%s[<< 'foo' :: binary >>])
end
assert_raise ArgumentError, fn ->
Code.eval_string(%s[<<1::size(4)>> <> "foo"])
end
end
@binary "new "
@charlist 'old '
@binary "new "
test :bitsyntax_with_expansion do
assert <<@binary, "world">> == "new world"
assert <<@charlist, "world">> == "old world"
end
test :bitsyntax_translation do
@@ -178,7 +165,7 @@ bar
sec_data = "another"
<< size(refb) :: refb_spec,
refb :: binary,
size(sec_data) :: [size(1) | signed_16],
size(sec_data) :: [size(1), signed_16],
sec_data :: binary >>
end
end
@@ -7,10 +7,6 @@ defmodule Kernel.CaseTest do
assert (case 1, do: (1 -> :ok; 2 -> :wrong)) == :ok
end
test :nested_case do
assert get_case == 2
end
test :nested_variables do
assert vars_case(400, 1) == { 400, 1 }
assert vars_case(401, 1) == { 400, -1 }
@@ -41,27 +37,6 @@ defmodule Kernel.CaseTest do
refute 1.0 in [1, 2, 3], "not in assertion"
end
defp get_case do
case internal do
:invalid ->
status = :fail
1 ->
case other_internal do
status ->
status
end
end
status
end
defp internal do
1
end
defp other_internal do
2
end
defp vars_case(x, vx) do
case x > 400 do
true ->
+36 -29
View File
@@ -5,7 +5,7 @@ import PathHelpers
defmodule Kernel.CLI.InitTest do
use ExUnit.Case, async: true
test :code_init do
test "handles code on initialization" do
assert elixir('-e "IO.puts [?3]"') == '3\n'
result = elixir('-e "IO.puts inspect(System.argv)" #{fixture_path("init_sample.exs")} -o 1 2 3')
@@ -16,7 +16,7 @@ end
defmodule Kernel.CLI.OptionParsingTest do
use ExUnit.Case, async: true
test :path do
test "properly parses paths" do
root = fixture_path("../../..") |> to_char_list
list = elixir('-pa "#{root}/*" -pz "#{root}/lib/*" -e "IO.inspect(:code.get_path, limit: :infinity)"')
{ path, _ } = Code.eval_string list, []
@@ -34,7 +34,7 @@ end
defmodule Kernel.CLI.AtExitTest do
use ExUnit.Case, async: true
test :at_exit do
test "invokes at_exit callbacks" do
assert elixir(fixture_path("at_exit.exs") |> to_char_list) ==
'goodbye cruel world with status 0\n'
end
@@ -43,7 +43,7 @@ end
defmodule Kernel.CLI.ErrorTest do
use ExUnit.Case, async: true
test :code_error do
test "properly format errors" do
assert :string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
assert :string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
@@ -52,24 +52,10 @@ defmodule Kernel.CLI.ErrorTest do
end
end
defmodule Kernel.CLI.SyntaxErrorTest do
use ExUnit.Case, async: true
defp check_output(elixir_cmd, expected_msg) do
o = elixir(elixir_cmd)
assert :string.str(o, expected_msg) == 1, "Expected this output: `#{expected_msg}`\nbut got this output: `#{o}`"
end
test :syntax_code_error do
check_output('-e "[1,2"', '** (TokenMissingError) nofile:1: missing terminator: ]')
check_output('-e "case 1 end"', %C"** (SyntaxError) nofile:1: unexpected token: end")
end
end
defmodule Kernel.CLI.CompileTest do
use ExUnit.Case, async: true
test :compile_code do
test "compiles code" do
fixture = fixture_path "compile_sample.ex"
assert elixirc('#{fixture} -o #{tmp_path}') == ''
assert File.regular?(tmp_path "Elixir.CompileSample.beam")
@@ -77,7 +63,7 @@ defmodule Kernel.CLI.CompileTest do
File.rm(tmp_path("Elixir.CompileSample.beam"))
end
test :compile_code_verbose do
test "compiles code with verbose mode" do
fixture = fixture_path "compile_sample.ex"
assert elixirc('#{fixture} -o #{tmp_path} --verbose') ==
'Compiled #{fixture}\n'
@@ -86,13 +72,12 @@ defmodule Kernel.CLI.CompileTest do
File.rm(tmp_path("Elixir.CompileSample.beam"))
end
test :possible_deadlock do
output = elixirc('#{fixture_path("parallel_deadlock")} -o #{tmp_path}')
foo = '* #{fixture_path "parallel_deadlock/foo.ex"} is missing module Bar'
bar = '* #{fixture_path "parallel_deadlock/bar.ex"} is missing module Foo'
assert :string.str(output, foo) > 0, "expected foo.ex to miss module Bar"
assert :string.str(output, bar) > 0, "expected bar.ex to miss module Foo"
assert :string.str(output, 'elixir_compiler') == 0, "expected elixir_compiler to not be in output"
test "fails on missing patterns" do
fixture = fixture_path "compile_sample.ex"
output = elixirc('#{fixture} non_existing.ex -o #{tmp_path}')
assert :string.str(output, 'non_existing.ex') > 0, "expected non_existing.ex to be mentionned"
assert :string.str(output, 'compile_sample.ex') == 0, "expected compile_sample.ex to not be mentionned"
refute File.exists?(tmp_path("Elixir.CompileSample.beam")) , "expected the sample to not be compiled"
end
end
@@ -100,14 +85,36 @@ defmodule Kernel.CLI.ParallelCompilerTest do
use ExUnit.Case
import ExUnit.CaptureIO
test :files do
test "compiles files solving dependencies" do
fixtures = [fixture_path("parallel_compiler/bar.ex"), fixture_path("parallel_compiler/foo.ex")]
assert capture_io(fn ->
assert [Bar, Foo] = Kernel.ParallelCompiler.files fixtures
end) =~ "message_from_foo"
end
test :warnings_as_errors do
test "does not hang on missing dependencies" do
fixtures = [fixture_path("parallel_compiler/bat.ex")]
assert capture_io(fn ->
assert_raise CompileError, fn ->
Kernel.ParallelCompiler.files fixtures
end
end) =~ "Compilation error"
end
test "handles possible deadlocks" do
fixtures = [fixture_path("parallel_deadlock/foo.ex"), fixture_path("parallel_deadlock/bar.ex")]
msg = capture_io(fn ->
assert_raise UndefinedFunctionError, fn ->
Kernel.ParallelCompiler.files fixtures
end
end)
assert msg =~ "* #{fixture_path "parallel_deadlock/foo.ex"} is missing module Bar"
assert msg =~ "* #{fixture_path "parallel_deadlock/bar.ex"} is missing module Foo"
end
test "warnings_as_errors" do
warnings_as_errors = Code.compiler_options[:warnings_as_errors]
try do
+53 -53
View File
@@ -17,6 +17,12 @@ defmodule Kernel.ErrorsTest do
'\end\nlol\nbarbecue'
end
test :invalid_or_reserved_codepoint do
assert_compile_fail ArgumentError,
"invalid or reserved unicode codepoint 55296",
'?\\x{D800}'
end
test :sigil_terminator do
assert_compile_fail TokenMissingError,
"nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)",
@@ -61,10 +67,10 @@ defmodule Kernel.ErrorsTest do
'+.foo'
end
test :syntax_error_on_op_ambiguity do
test :compile_error_on_op_ambiguity do
msg = "nofile:1: \"a -1\" looks like a function call but there is a variable named \"a\", " <>
"please use explicit parenthesis or even spaces"
assert_compile_fail SyntaxError, msg, 'a = 1; a -1'
assert_compile_fail CompileError, msg, 'a = 1; a -1'
max = 1
assert max == 1
@@ -119,7 +125,7 @@ defmodule Kernel.ErrorsTest do
test :invalid_match_pattern do
assert_compile_fail CompileError,
"nofile:2: invalid pattern in match clause",
"nofile:2: invalid expression in match",
'''
case true do
true && true -> true
@@ -170,9 +176,9 @@ defmodule Kernel.ErrorsTest do
end
test :unbound_expr do
assert_compile_fail SyntaxError,
"nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)",
'^x(1)'
assert_compile_fail CompileError,
"nofile:1: invalid argument for unary operator ^, expected an existing variable, got: ^x(1)",
'^x(1) = 1'
end
test :name_for_defmodule do
@@ -188,23 +194,23 @@ defmodule Kernel.ErrorsTest do
end
test :invalid_quote_args do
assert_compile_fail SyntaxError,
"nofile:1: invalid args for quote",
assert_compile_fail CompileError,
"nofile:1: invalid arguments for quote",
'quote 1'
end
test :invalid_calls do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: invalid call foo(1)(2)",
'foo(1)(2)'
assert_compile_fail SyntaxError,
"nofile:1: invalid remote call on 1",
assert_compile_fail CompileError,
"nofile:1: invalid call 1.foo()",
'1.foo'
end
test :unhandled_stab do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:3: unhandled operator ->",
'''
defmodule Mod do
@@ -217,8 +223,8 @@ defmodule Kernel.ErrorsTest do
test :undefined_non_local_function do
assert_compile_fail CompileError,
"nofile:1: function casea/2 undefined",
'casea foo, do: (bar -> baz)'
"nofile:1: undefined function casea/2",
'casea foo, do: 1'
end
test :invalid_fn_args do
@@ -277,7 +283,7 @@ defmodule Kernel.ErrorsTest do
end
test :function_definition_with_alias do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:2: function names should start with lowercase characters or underscore, invalid name Bar",
'''
defmodule ErrorsTest do
@@ -293,7 +299,7 @@ defmodule Kernel.ErrorsTest do
"nofile:3: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
'''
defmodule ErrorsTest do
import :erlang
import :erlang, warn: false
def foo, do: exit(:test)
end
'''
@@ -349,7 +355,7 @@ defmodule Kernel.ErrorsTest do
test :invalid_macro do
assert_compile_fail CompileError,
"nofile: tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: {:foo, :bar, :baz, :bat}",
"nofile: invalid quoted expression: {:foo, :bar, :baz, :bat}",
'''
defmodule ErrorsTest do
defmacrop oops do
@@ -385,7 +391,7 @@ defmodule Kernel.ErrorsTest do
assert_compile_fail ArgumentError,
"could not call eval_quoted on module Record " <>
"because it was already compiled",
'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__'
'Module.eval_quoted Record, quote(do: 1), [], file: __ENV__.file'
end
test :interpolation_error do
@@ -401,36 +407,36 @@ defmodule Kernel.ErrorsTest do
end
test :invalid_definition do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: invalid syntax in def 1.(hello)",
'defmodule ErrorsTest, do: (def 1.(hello), do: true)'
end
test :duplicated_bitstring_size do
assert_compile_fail SyntaxError,
"nofile:1: duplicated size definition for bitstring",
assert_compile_fail CompileError,
"nofile:1: duplicated size definition in bitstring",
'<<1 :: [size(12), size(13)]>>'
end
test :invalid_bitstring_specified do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: unknown bitstring specifier :atom",
'<<1 :: :atom>>'
assert_compile_fail SyntaxError,
"nofile:1: unknown bitstring specifier unknown",
assert_compile_fail CompileError,
"nofile:1: unknown bitstring specifier unknown()",
'<<1 :: unknown>>'
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: unknown bitstring specifier another(12)",
'<<1 :: another(12)>>'
assert_compile_fail SyntaxError,
"nofile:1: size in bitstring expects an integer or a variable as argument",
assert_compile_fail CompileError,
"nofile:1: size in bitstring expects an integer or a variable as argument, got: :a",
'<<1 :: size(:a)>>'
assert_compile_fail SyntaxError,
"nofile:1: unit in bitstring expects an integer as argument",
assert_compile_fail CompileError,
"nofile:1: unit in bitstring expects an integer as argument, got: :x",
'<<1 :: unit(:x)>>'
end
@@ -442,7 +448,7 @@ defmodule Kernel.ErrorsTest do
test :invalid_alias do
assert_compile_fail CompileError,
"nofile:1: invalid :as for alias, nested alias Sample.Lists not allowed",
"nofile:1: invalid value for keyword :as, expected an alias, got nested alias: Sample.Lists",
'alias :lists, as: Sample.Lists'
end
@@ -471,7 +477,7 @@ defmodule Kernel.ErrorsTest do
end
test :invalid_access_protocol_not_record do
assert_raise ArgumentError, "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1", fn ->
assert_raise ArgumentError, "cannot access module Kernel.ErrorsTest because it is not a record", fn ->
defmodule ErrorsTest do
def sample(Kernel.ErrorsTest[integer: 0]), do: true
end
@@ -487,33 +493,27 @@ defmodule Kernel.ErrorsTest do
end
test :invalid_access_protocol_invalid_keywords do
assert_raise ArgumentError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]", fn ->
assert_raise ArgumentError, "record Kernel.ErrorsTest.Config does not have the key: :foo", fn ->
defmodule ErrorsTest do
def sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true
end
end
end
test :invalid_access_protocol_invalid_keywords_outside_match do
assert_compile_fail ArgumentError,
"record Kernel.ErrorsTest.Config does not have the keys: [:foo]",
'Kernel.ErrorsTest.Config[foo: :bar]'
end
test :invalid_rescue_clause do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:4: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format",
'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
end
test :invalid_bc_return do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: a bit comprehension expects a bit string << >> to be returned",
'bc x inlist [1, 2, 3], do: x'
end
test :invalid_bc_inbits_gen do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators",
'bc x inbits "123", do: <<x>>'
end
@@ -525,7 +525,7 @@ defmodule Kernel.ErrorsTest do
end
test :fun_different_arities do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: cannot mix clauses with different arities in function definition",
'fn x -> x; x, y -> x + y end'
end
@@ -548,7 +548,7 @@ defmodule Kernel.ErrorsTest do
end
test :bodyless_function_with_guard do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:2: missing do keyword in def",
'''
defmodule ErrorsTest do
@@ -594,8 +594,8 @@ defmodule Kernel.ErrorsTest do
end
test :bad_unquoting do
assert_compile_fail SyntaxError,
"nofile: expected a valid quoted expression, got: {Range, 1, 3}",
assert_compile_fail CompileError,
"nofile: invalid quoted expression: {Range, 1, 3}",
'''
defmodule ErrorsTest do
def range(unquote(1..3)), do: :ok
@@ -605,41 +605,41 @@ defmodule Kernel.ErrorsTest do
test :macros_error_stacktrace do
assert [{:erlang, :+, [1, :foo], _}, {ErrorsTest, :sample, 1, _}|_] =
rescue_stacktrace(""")
rescue_stacktrace("""
defmodule ErrorsTest do
defmacro sample(num), do: num + :foo
def other, do: sample(1)
end
"""
""")
end
test :macros_function_clause_stacktrace do
assert [{__MODULE__, :sample, 1, _}|_] =
rescue_stacktrace(""")
rescue_stacktrace("""
defmodule ErrorsTest do
import Kernel.ErrorsTest
sample(1)
end
"""
""")
end
test :macros_interpreted_function_clause_stacktrace do
assert [{ErrorsTest, :sample, 1, _}|_] =
rescue_stacktrace(""")
rescue_stacktrace("""
defmodule ErrorsTest do
defmacro sample(0), do: 0
def other, do: sample(1)
end
"""
""")
end
test :macros_compiled_callback do
assert [{Kernel.ErrorsTest, :__before_compile__, [Macro.Env[module: ErrorsTest]], _}|_] =
rescue_stacktrace(""")
rescue_stacktrace("""
defmodule ErrorsTest do
Module.put_attribute(__MODULE__, :before_compile, Kernel.ErrorsTest)
end
"""
""")
end
defmacro sample(0), do: 0
@@ -0,0 +1,407 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule Kernel.ExpansionTarget do
defmacro seventeen, do: 17
end
defmodule Kernel.ExpansionTest do
use ExUnit.Case, async: true
## __block__
test "__block__: expands to nil when empty" do
assert expand(quote do: __block__()) == nil
end
test "__block__: expands to argument when arity is 1" do
assert expand(quote do: __block__(1)) == 1
end
test "__block__: is recursive to argument when arity is 1" do
assert expand(quote do: __block__(1, __block__(2))) == quote do: __block__(1, 2)
end
test "__block__: accumulates vars" do
assert expand(quote(do: (a = 1; a))) == quote do: (a = 1; a)
end
## alias
test "alias: expand args, defines alias and returns itself" do
alias true, as: True
input = quote do: (alias :hello, as: World, warn: True)
{ output, env } = expand_env(input, __ENV__)
assert output == quote do: (alias :hello, as: :"Elixir.World", warn: true)
assert env.aliases == [{:"Elixir.True", true}, {:"Elixir.World", :hello}]
end
## __aliases__
test "__aliases__: expands even if no alias" do
assert expand(quote do: World) == :"Elixir.World"
assert expand(quote do: Elixir.World) == :"Elixir.World"
end
test "__aliases__: expands with alias" do
alias Hello, as: World
assert expand_env(quote(do: World), __ENV__) |> elem(0) == :"Elixir.Hello"
end
test "__aliases__: expands with alias is recursive" do
alias Source, as: Hello
alias Hello, as: World
assert expand_env(quote(do: World), __ENV__) |> elem(0) == :"Elixir.Source"
end
test "__aliases__: expands to elixir_aliases on runtime" do
assert expand(quote do: hello.World) ==
quote do: :elixir_aliases.concat([hello(), :World])
end
## =
test "=: sets context to match" do
assert expand(quote do: __ENV__.context = :match) == quote do: :match = :match
end
test "=: defines vars" do
{ output, env } = expand_env(quote(do: a = 1), __ENV__)
assert output == quote(do: a = 1)
assert { :a, __MODULE__ } in env.vars
end
test "=: does not carry rhs imports" do
assert expand(quote(do: flatten([1,2,3]) = import List)) ==
quote(do: flatten([1,2,3]) = import :"Elixir.List", [])
end
test "=: does not define _" do
{ output, env } = expand_env(quote(do: _ = 1), __ENV__)
assert output == quote(do: _ = 1)
assert env.vars == []
end
## Pseudo vars
test "__MODULE__" do
assert expand(quote do: __MODULE__) == __MODULE__
end
test "__DIR__" do
assert expand(quote do: __DIR__) == __DIR__
end
test "__CALLER__" do
assert expand(quote do: __CALLER__) == quote do: __CALLER__
end
test "__ENV__" do
env = __ENV__
assert expand_env(quote(do: __ENV__), env) ==
{ { :{}, [], tuple_to_list(env.line(0)) }, env }
end
test "__ENV__.accessor" do
env = __ENV__
assert expand_env(quote(do: __ENV__.file), env) == { __ENV__.file, env }
assert expand_env(quote(do: __ENV__.unknown), env) ==
{ quote(do: unquote({ :{}, [], tuple_to_list(env.line(0)) }).unknown), env }
end
## Super
test "super: expand args" do
assert expand(quote do: super(a, b)) == quote do: super(a(), b())
end
## Vars
test "vars: expand to local call" do
{ output, env } = expand_env(quote(do: a), __ENV__)
assert output == quote(do: a())
assert env.vars == []
end
test "vars: forces variable to exist" do
assert expand(quote do: (var!(a) = 1; var!(a)))
message = %r"expected var a to expand to an existing variable or be a part of a match"
assert_raise CompileError, message, fn -> expand(quote do: var!(a)) end
message = %r"expected var a \(context Unknown\) to expand to an existing variable or be a part of a match"
assert_raise CompileError, message, fn -> expand(quote do: var!(a, Unknown)) end
end
test "^: expands args" do
assert expand(quote do: ^a = 1) == quote do: ^a = 1
end
test "^: raises outside match" do
assert_raise CompileError, %r"cannot use \^a outside of match clauses", fn ->
expand(quote do: ^a)
end
end
test "^: raises without var" do
assert_raise CompileError, %r"invalid argument for unary operator \^, expected an existing variable, got: \^1", fn ->
expand(quote do: ^1 = 1)
end
end
## Locals
test "locals: expands to remote calls" do
assert { {:., _, [Kernel, :=~] }, _, [{:a, _, []}, {:b, _, []}] } =
expand(quote do: a =~ b)
end
test "locals: expands to configured local" do
assert expand_env(quote(do: a), __ENV__.local(Hello)) |> elem(0) ==
quote(do: :"Elixir.Hello".a())
end
test "locals: custom imports" do
assert expand(quote do: (import Kernel.ExpansionTarget; seventeen)) ==
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; 17)
end
## Tuples
test "tuples: expanded as arguments" do
assert expand(quote(do: { a = 1, a })) == quote do: { a = 1, a() }
assert expand(quote(do: { b, a = 1, a })) == quote do: { b(), a = 1, a() }
end
## quote
test "quote: expanded to raw forms" do
assert expand(quote do: (quote do: hello)) == { :{}, [], [:hello, [], __MODULE__] }
end
## Anonymous calls
test "anonymous calls: expands base and args" do
assert expand(quote do: a.(b)) == quote do: a().(b())
end
test "anonymous calls: raises on atom base" do
assert_raise CompileError, %r"invalid function call :foo.()", fn ->
expand(quote do: :foo.(a))
end
end
## Remote calls
test "remote calls: expands to erlang" do
assert expand(quote do: Kernel.is_atom(a)) == quote do: :erlang.is_atom(a())
end
test "remote calls: expands macros" do
assert expand(quote do: Kernel.ExpansionTest.thirteen) == 13
end
test "remote calls: expands receiver and args" do
assert expand(quote do: a.is_atom(b)) == quote do: a().is_atom(b())
assert expand(quote do: (a = :foo).is_atom(a)) == quote do: (a = :foo).is_atom(a())
end
test "remote calls: modules must be required for macros" do
assert expand(quote do: (require Kernel.ExpansionTarget; Kernel.ExpansionTarget.seventeen)) ==
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; 17)
end
test "remote calls: raises when not required" do
msg = %r"you must require Kernel\.ExpansionTarget before invoking the macro Kernel\.ExpansionTarget\.seventeen/0"
assert_raise CompileError, msg, fn ->
expand(quote do: Kernel.ExpansionTarget.seventeen)
end
end
## Comprehensions
test "lc: variables inside comprehensions do not leak" do
assert expand(quote do: (lc(a inlist b, do: c = 1); c)) ==
quote do: (lc(a inlist b(), do: c = 1); c())
end
test "bc: variables inside comprehensions do not leak" do
assert expand(quote do: (bc(a inbits b, do: c = 1); c)) ==
quote do: (bc(a inbits b(), do: c = 1); c())
end
## Capture
test "&: keeps locals" do
assert expand(quote do: &unknown/2) ==
{:&, [], [{:/, [], [{:unknown,[],nil}, 2]}]}
assert expand(quote do: &unknown(&1, &2)) ==
{:&, [], [{:/, [], [{:unknown,[],nil}, 2]}]}
end
test "&: expands remotes" do
assert expand(quote do: &List.flatten/2) ==
quote do: :erlang.make_fun(:"Elixir.List", :flatten, 2)
assert expand(quote do: &Kernel.is_atom/1) ==
quote do: :erlang.make_fun(:erlang, :is_atom, 1)
end
test "&: expands macros" do
assert expand(quote do: (require Kernel.ExpansionTarget; &Kernel.ExpansionTarget.seventeen/0)) ==
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; fn -> 17 end)
end
## fn
test "fn: expands each clause" do
assert expand(quote do: fn x -> x; _ -> x end) ==
quote do: fn x -> x; _ -> x() end
end
test "fn: does not share lexical in between clauses" do
assert expand(quote do: fn 1 -> import List; 2 -> flatten([1,2,3]) end) ==
quote do: fn 1 -> import :"Elixir.List", []; 2 -> flatten([1,2,3]) end
end
test "fn: expands guards" do
assert expand(quote do: fn x when x when __ENV__.context -> true end) ==
quote do: fn x when x when :guard -> true end
end
test "fn: does not leak vars" do
assert expand(quote do: (fn x -> x end; x)) ==
quote do: (fn x -> x end; x())
end
## Case
test "case: expands each clause" do
assert expand_and_clean(quote do: (case w do x -> x; _ -> x end)) ==
quote do: (case w() do x -> x; _ -> x() end)
end
test "case: does not share lexical in between clauses" do
assert expand_and_clean(quote do: (case w do 1 -> import List; 2 -> flatten([1,2,3]) end)) ==
quote do: (case w() do 1 -> import :"Elixir.List", []; 2 -> flatten([1,2,3]) end)
end
test "case: expands guards" do
assert expand_and_clean(quote do: (case w do x when x when __ENV__.context -> true end)) ==
quote do: (case w() do x when x when :guard -> true end)
end
test "case: does not leaks vars on head" do
assert expand_and_clean(quote do: (case w do x -> x; y -> y end; :erlang.+(x, y))) ==
quote do: (case w() do x -> x; y -> y end; :erlang.+(x(), y()))
end
test "case: leaks vars" do
assert expand_and_clean(quote do: (case w do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
quote do: (case w() do x -> x = x; y -> y = y end; :erlang.+(x, y))
end
## Receive
test "receive: expands each clause" do
assert expand_and_clean(quote do: (receive do x -> x; _ -> x end)) ==
quote do: (receive do x -> x; _ -> x() end)
end
test "receive: does not share lexical in between clauses" do
assert expand_and_clean(quote do: (receive do 1 -> import List; 2 -> flatten([1,2,3]) end)) ==
quote do: (receive do 1 -> import :"Elixir.List", []; 2 -> flatten([1,2,3]) end)
end
test "receive: expands guards" do
assert expand_and_clean(quote do: (receive do x when x when __ENV__.context -> true end)) ==
quote do: (receive do x when x when :guard -> true end)
end
test "receive: does not leaks clause vars" do
assert expand_and_clean(quote do: (receive do x -> x; y -> y end; :erlang.+(x, y))) ==
quote do: (receive do x -> x; y -> y end; :erlang.+(x(), y()))
end
test "receive: leaks vars" do
assert expand_and_clean(quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))
end
test "receive: leaks vars on after" do
assert expand_and_clean(quote do: (receive do x -> x = x after y -> y; w = y end; :erlang.+(x, w))) ==
quote do: (receive do x -> x = x after y() -> y(); w = y() end; :erlang.+(x, w))
end
## Try
test "try: expands do" do
assert expand(quote do: (try do x = y end; x)) ==
quote do: (try do x = y() end; x())
end
test "try: expands catch" do
assert expand(quote do: (try do x catch x, y -> z = :erlang.+(x, y) end; z)) ==
quote do: (try do x() catch x, y -> z = :erlang.+(x, y) end; z())
end
test "try: expands after" do
assert expand(quote do: (try do x after z = y end; z)) ==
quote do: (try do x() after z = y() end; z())
end
test "try: expands else" do
assert expand(quote do: (try do x else z -> z end; z)) ==
quote do: (try do x() else z -> z end; z())
end
test "try: expands rescue" do
assert expand(quote do: (try do x rescue x -> x; Error -> x end; x)) ==
quote do: (try do x() rescue unquote(:in)(x, _) -> x; unquote(:in)(_, [:"Elixir.Error"]) -> x() end; x())
end
## Binaries
test "bitstrings: expands modifiers" do
assert expand(quote do: (import Kernel.ExpansionTarget; << x :: seventeen >>)) ==
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; << x() :: [unquote(:size)(17)] >>)
assert expand(quote do: (import Kernel.ExpansionTarget; << seventeen :: seventeen, x :: size(seventeen) >> = 1)) ==
quote do: (import :"Elixir.Kernel.ExpansionTarget", [];
<< seventeen :: [unquote(:size)(17)], x :: [unquote(:size)(seventeen)] >> = 1)
end
test "bitstrings: expands modifiers args" do
assert expand(quote do: (require Kernel.ExpansionTarget; << x :: size(Kernel.ExpansionTarget.seventeen) >>)) ==
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; << x() :: [unquote(:size)(17)] >>)
end
## Invalid
test "handles invalid expressions" do
assert_raise CompileError, %r"invalid quoted expression: {1, 2, 3}", fn ->
expand(quote do: unquote({ 1, 2, 3 }))
end
end
## Helpers
defmacro thirteen do
13
end
defp expand_and_clean(expr) do
expand_env(expr, __ENV__) |> elem(0) |> Macro.update_meta(&Keyword.drop(&1, [:export]))
end
defp expand(expr) do
expand_env(expr, __ENV__) |> elem(0)
end
defp expand_env(expr, env) do
{ expr, env } = :elixir_exp.expand(expr, :elixir_env.ex_to_env(env))
{ expr, set_elem(env, 0, Macro.Env) }
end
end
+5 -5
View File
@@ -85,8 +85,8 @@ defmodule Kernel.FnTest do
end
test "capture and partially apply on call" do
assert (&(&1.file)).(__ENV__) == __FILE__
assert (&(&1.file(&2))).(__ENV__, "Hello").file == "Hello"
assert (&(&1.module)).(__ENV__) == __MODULE__
assert (&(&1.module(&2))).(__ENV__, Hello).module == Hello
end
test "capture block like" do
@@ -110,21 +110,21 @@ defmodule Kernel.FnTest do
end
test "failure on block" do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: invalid args for &, block expressions " <>
"are not allowed, got: (\n 1\n 2\n)",
"&(1;2)"
end
test "failure on other types" do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: 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: :foo",
"&:foo"
end
test "failure when no captures" do
assert_compile_fail SyntaxError,
assert_compile_fail CompileError,
"nofile:1: 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: foo()",
"&foo()"
@@ -9,7 +9,6 @@ defmodule Kernel.ImportTest do
end
end
test "import all" do
import :lists
assert flatten([1, [2], 3]) == [1, 2, 3]
@@ -132,8 +131,11 @@ defmodule Kernel.ImportTest do
try do
import :lists
flatten([1, [2], 3])
flunk
catch
_ -> flatten([:a, [:b], :c])
_, _ ->
# Buggy local duplicate is untouched
assert duplicate([1], 2) == [1]
end
# Buggy local duplicate is untouched

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