Compare commits

...
440 Commits
Author SHA1 Message Date
José Valim b70fc122f8 Release v0.11.2 2013-11-14 12:41:45 +01:00
José Valim 0d5bf3a904 Merge pull request #1876 from alco/before-compile-doc
Fix the example for @before_compile hook
2013-11-13 05:33:23 -08:00
Alexei Sholik 66019c36ca Fix the example for @before_compile hook 2013-11-13 15:10:18 +02:00
José Valim a73235a2de Merge pull request #1875 from pragdave/iex-c-error
Catch attempts to compile missing files in iex and give slightly tidier error messages
2013-11-12 23:54:26 -08:00
pragdave 718b111797 Catch attempts to compile missing files in iex and give slightly tidier error messages 2013-11-12 23:26:58 -06:00
José Valim fd57b5de4f Ensure build per environment in umbrella children do not clash 2013-11-13 00:11:30 +01:00
José Valim bb9569db0c Merge pull request #1874 from jwarwick/success2
Updated mix.new success wording
2013-11-12 15:10:29 -08:00
John Warwick 74c798585b Updated mix.new success wording 2013-11-12 17:54:55 -05:00
José Valim cfdcf685cb Do not access compile_path for umbrellas 2013-11-12 23:26:47 +01:00
José Valim 572726cf65 Remove unecessary apply optimization 2013-11-12 14:29:17 +01:00
José Valim 1885841df6 Update CHANGELOG 2013-11-12 14:07:56 +01:00
José Valim 80a466af35 Merge pull request #1871 from tonini/mix-iex-task
Add mix iex that simply fails and tell users to use iex -S mix
2013-11-12 05:02:26 -08:00
José Valim 83db533700 Merge pull request #1872 from elixir-lang/jv-_build
Compile projects to _build
2013-11-12 05:02:00 -08:00
José Valim 49fe9b05a0 Copy dev build if the environments match 2013-11-12 12:56:43 +01:00
José Valim c8425edb6d Do not clean whole build on clean, only libs 2013-11-12 11:25:41 +01:00
José Valim b9cb17ed39 deps.loadpaths no longer loads the dependencies, it relies simply on builds 2013-11-12 11:02:59 +01:00
José Valim 5d48abb9ab Ensure we can clean deps that no longer exists 2013-11-12 10:55:37 +01:00
José Valim 1403350be0 Prune tasks when build per environment is on 2013-11-12 10:36:37 +01:00
José Valim df997f836b Allow builds to be customized per environment 2013-11-12 10:14:25 +01:00
Samuel Tonini 4544ab59cf Add mix iex that simply fails and tell users to use iex -S mix 2013-11-12 10:09:44 +01:00
José Valim 7614b6c1d0 Rename _build to _build/shared 2013-11-12 09:57:01 +01:00
José Valim 1da1f8c203 Update new applications .gitignore 2013-11-12 09:51:15 +01:00
José Valim 0addc221e0 Improve coverage for builds and rebar integration 2013-11-12 09:50:30 +01:00
Eric Meadows-Jonsson f6a2046850 Keep debug info for unicode files
Dialyzer stops processing when it finds a .beam file without debug info.
Only increases file size with ~120kb in total.
2013-11-11 21:21:44 +01:00
José Valim c345e51772 Ensure rebar dependencies are stored in the proper directory 2013-11-11 20:15:31 +01:00
José Valim 75e22706fc Share _build path when running commands from umbrella 2013-11-11 18:47:58 +01:00
José Valim eac9929328 Move rebar and other deps to _build 2013-11-11 18:14:16 +01:00
José Valim e1aead5ba3 Ensure archives work with new _build setup 2013-11-11 15:29:18 +01:00
José Valim 6f5d4d04fd Sinplify mechanism for checking and handling locks
In particular, there is no need to pass :lockfile around deps.
We also remove the umbrella handling from the lock.
2013-11-11 13:54:54 +01:00
José Valim 13eab25e6d Rename fetched to loaded and add a manifest_path 2013-11-11 13:40:05 +01:00
José Valim 2854fec36a Move mix dependencies to _build 2013-11-11 13:19:10 +01:00
José Valim 48208066b7 Merge pull request #1867 from christhekeele/fix-macro-env-doc-typo
s/wether/whether
2013-11-11 00:18:33 -08:00
Chris Keele 4914c4982d s/wether/whether 2013-11-10 18:06:23 -06:00
José Valim 14a562f260 Compile not ok dependencies on deps.get and deps.update 2013-11-10 21:13:24 +01:00
José Valim dd91696184 Do not override builtin type 2013-11-10 20:15:44 +01:00
José Valim 5c55f0fe91 Move mix configs to app environment and split TasksServer 2013-11-10 20:03:13 +01:00
José Valim a6474aecd4 Extract Mix.ProjectStack from Mix.Server 2013-11-10 19:05:40 +01:00
José Valim 5844e29443 Ensure app key is required when getting the compile_path 2013-11-10 15:33:34 +01:00
José Valim 38f7d709d1 Merge pull request #1866 from parroty/fix
Add :++, :-- and :** to Macro#binary_op_props
2013-11-10 05:38:14 -08:00
parroty 4931629b51 Add :++, :-- and :** to Macro#binary_op_props 2013-11-10 22:25:42 +09:00
José Valim 343d9ef042 Remove :compile_path from config and move it to Mix.Project 2013-11-10 13:02:20 +01:00
José Valim 703c7dfd21 Ensure Mix properly recurs rebar subdirs, closes #1862 2013-11-10 11:38:34 +01:00
José Valim 082af1c417 Reduce the amount of fixture files in mix 2013-11-10 11:37:17 +01:00
José Valim dc9caf4260 Merge pull request #1863 from philss/remove-mix-test-warning
Remove unused variables from mix test file
2013-11-08 23:22:03 -08:00
Philip Sampaio f7fd253918 Remove unused variables from mix test file 2013-11-09 01:42:18 -02:00
José Valim e6be0628ce Start v0.11.2-dev 2013-11-07 17:18:23 +01:00
José Valim 7159da5780 Release v0.11.1 2013-11-07 16:54:19 +01:00
José Valim 5dab337685 Ensure Mix.Shell.IO do not puke on :eof 2013-11-06 21:24:28 +01:00
José Valim 12f113f6f8 Merge pull request #1860 from jwarwick/warnings
Fixed warning message.
2013-11-06 11:18:04 -08:00
John Warwick c1367548dd fix typo in changelog 2013-11-06 14:11:51 -05:00
John Warwick e40375a65f underscorize unused argument 2013-11-06 14:11:30 -05:00
José Valim a6bcb014ec Merge pull request #1859 from josephwilk/var_doc
Improving docs on var!
2013-11-06 09:29:34 -08:00
josephwilk d3a526ebf0 Improving docs on var!
Remove implementation details about variable name.
Try and make it clear it is just unquoting a var.
2013-11-06 18:25:04 +01:00
José Valim eb8fba694c Skip remote call on dict behaviour size 2013-11-06 16:21:49 +01:00
José Valim 0d3aa7fd66 Improve some Kernel error messages 2013-11-06 16:14:31 +01:00
José Valim ca7f1fee5a Remove optional parameter on __using__ 2013-11-06 15:36:21 +01:00
José Valim 55ecdba77c Make test that changes global state sync 2013-11-06 15:00:00 +01:00
José Valim 53f5473b62 Update CHANGELOG 2013-11-06 12:35:17 +01:00
José Valim 4eb9cd3f59 Improve docs and error messages 2013-11-06 12:31:55 +01:00
Eric Meadows-Jonsson bf9e190561 Optional deps should be kept for the project that specifies them 2013-11-05 22:11:02 +01:00
José Valim 0f6459e50d Merge pull request #1855 from alco/elixirappsrc
Mark the autogenerated lib/src/elixir.app.src as such
2013-11-05 12:34:13 -08:00
José Valim 30b24b7e2d Merge pull request #1856 from ToJans/patch-1
Allow compilation in R17A
2013-11-05 12:33:30 -08:00
José Valim 7aa5a89396 Add support for optional dependencies 2013-11-05 21:14:27 +01:00
José Valim 3ede5ff7f9 Store only app names in Mix.Dep.deps 2013-11-05 21:14:27 +01:00
José Valim 2f2f2e0a58 Dependencies convergence properly relies on requirements
Previously Mix required dependencies to have exactly the same
requirements but now we just check all requirements satisfy the
current version.
2013-11-05 21:14:27 +01:00
Eric Meadows-Jonsson d20190c99a Allow lower case escapes in URI
See RFC 2396 2.4.1
Closes #1857
2013-11-05 19:07:20 +01:00
Tom Janssens 57b5319ae8 Allow compilation in R17A
Allow elixir to be compiled using R17A as well
2013-11-05 15:15:51 +01:00
Alexei Sholik ecd95a8929 Mark the autogenerated lib/src/elixir.app.src as such 2013-11-05 15:12:56 +02:00
José Valim f65ef1dd67 Remove duplication in deps.compile 2013-11-05 10:32:10 +01:00
José Valim 356b74db01 Do not allow duplicated projects to be pushed onto the stack
Closes #1846
2013-11-05 10:15:51 +01:00
José Valim 94579848cc Move unicode from priv/ to unicode/ 2013-11-05 08:17:33 +01:00
José Valim e0c312bdc7 Add a test case ensuring managers are not set eagerly 2013-11-04 20:33:53 +01:00
José Valim 523b7f1f4d Do not fetch the manager before the repo is available 2013-11-04 20:13:05 +01:00
José Valim d436c9728a No need to support @recursive :both anymore 2013-11-04 18:24:28 +01:00
José Valim a917c6c0f8 Add a workaround for Mix deps.get and deps.umbrella what was not loading path deps
This will be properly fixed in a couple days once we move
to _build directories.
2013-11-04 18:00:06 +01:00
José Valim 8c57450421 Check the elixir req for dependencies too
Also streamline the workflow for deps.get and deps.update.
2013-11-04 17:46:28 +01:00
José Valim e999642a8e Improve syntax error on invalid calls
Closes #1847
2013-11-04 12:32:17 +01:00
José Valim 773d4b0a2e Improve error message for overriden deps 2013-11-04 12:22:59 +01:00
José Valim a79bdd5cdd Revert "Fix status code in mix test"
This reverts commit 4fc3fc11de.

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

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

Also via cli `elixir -e`:

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

Undefined function calls behave like:

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

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

      defmacro read do
        quote do
          a
        end
      end
    end

    require Hello
    Hello.write
    Hello.read

Although this is convenient, it has many issues:

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

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

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

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

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

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

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

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

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

Thanks to @ericmj for the initial suggestion.
2013-10-10 11:55:28 +02:00
José Valim e17df44ef3 Merge pull request #1754 from pminten/exunit-case-doc-fix
Fix indentation on doc example
2013-10-09 07:28:15 -07:00
Peter Minten 5aa2340c7d Fix indentation on doc example 2013-10-09 16:14:37 +02:00
José Valim 65b0bde5e8 Fix typos (tks @ericmj) 2013-10-09 11:41:28 +02:00
José Valim 260b7387df Use attributes so we don't force protocols to be loaded 2013-10-09 10:13:34 +02:00
José Valim d13fe163e3 Merge pull request #1753 from mururu/rm-warning
Remove unused warning
2013-10-08 23:48:03 -07:00
Yuki Ito 3a834fcd2c Remove unused warning 2013-10-09 08:55:18 +09:00
José Valim f0af35d585 Provide a wrapper in CLI for easy stacktrace pruning
As suggested by @pragdave
2013-10-08 21:12:42 +02:00
José Valim f510d4c3df Normalize all stacktrace under the same name and improve IEx errors
Kudos to @pragdave for suggesting some those improvements and
providing an initial implementation in pull request #1752.
2013-10-08 20:47:21 +02:00
José Valim 4eee44fbae Read fallback_to_any from @only and @except for backwards compat 2013-10-08 16:21:00 +02:00
José Valim 0c883e1aa2 Ensure custom types can be prioritized 2013-10-08 16:09:41 +02:00
José Valim 2297f34179 Falling back to any in protocols needs to be explicit 2013-10-08 15:12:42 +02:00
José Valim a2c1f362c2 Implement protocol consolidation 2013-10-08 13:29:53 +02:00
José Valim 8575319c0e Improve iex typespec helpers output on bad data 2013-10-08 12:47:36 +02:00
José Valim 4c67f3daa0 Improve mix compile.elixir code comments 2013-10-08 10:29:32 +02:00
José Valim 3cf9774b92 Improve warn on invalid expressions 2013-10-07 20:16:54 +02:00
José Valim e35ddb6781 Update CHANGELOG 2013-10-07 19:01:57 +02:00
José Valim a0b8067571 Upgrade clustering from legacy to extended 2013-10-07 18:28:24 +02:00
José Valim 5f6fa9be11 Update Unicode database to 6.3.0 2013-10-07 18:13:41 +02:00
José Valim 81c6ac2548 Use GraphemeBreakProperty as in Unicode Annex #29 2013-10-07 17:59:25 +02:00
José Valim 47ab0c7a3a Deprecate @only and @except 2013-10-07 14:14:43 +02:00
José Valim 436cdde499 Fix bug where nested defmodule was shadowing alias hygiene 2013-10-07 13:57:07 +02:00
José Valim 7c7c3eb3c2 Clean up protocol implementation setting the ground for consolidation 2013-10-07 12:23:13 +02:00
José Valim 5cb06e9640 Allow a module to force its debug_info to be compiled 2013-10-07 10:16:02 +02:00
José Valim a7142e2f6d Remove unused types from formatter 2013-10-07 08:02:56 +02:00
José Valim 36331f0f0b No need to filter raise from stacktraces 2013-10-07 08:02:40 +02:00
José Valim 4997bc1e10 Remove deprecated and unused code branches 2013-10-04 21:39:55 +02:00
José Valim 0f14e5001d Improve Enum docs and optimize ops with negative indexes 2013-10-04 13:43:15 +02:00
José Valim d0f410ccd0 Improve record docs 2013-10-04 13:42:59 +02:00
José Valim cb023c4934 Bump to 0.10.4-dev 2013-10-02 20:58:53 +02:00
José Valim 6a77057910 Release v0.10.3 2013-10-02 20:53:56 +02:00
José Valim e2be2579c5 Ensure both strings and atoms go through the same lookup 2013-10-02 20:43:54 +02:00
José Valim f9ec6cfb5f Merge pull request #1740 from yrashk/record-string-fields
Allow for string names when instantiating or updating records
2013-10-02 11:32:52 -07:00
Yurii Rashkovskii cc1d7c5823 Disallow mixed use of strings and atoms in record initialization/update
This allows provides better overall performance to these procedures.
2013-10-02 12:37:26 +02:00
Yurii Rashkovskii 7649a5dba4 Allow for string names when instantiating or updating records
This provides a great added benefit of allowing to import untrusted
dictionary-like structures without having to sanitize keys before
converting them to atoms, without the risk of running out of atoms.

The performance for atom field names remains the same, string names
are somewhat slower (as expected)
2013-10-01 15:52:24 +02:00
José Valim d221e9a161 Allow apps to be selected in umbrella projects
Closes #1731
Closes #1730
2013-09-30 08:59:13 +02:00
José Valim 4638831103 Update CHANGELOG 2013-09-29 22:07:54 +02:00
José Valim 60b2928678 Ensure &() works on blocks with one expressoin, closes #1704 2013-09-29 21:53:04 +02:00
José Valim 3f0c583045 Deprecate Regex.captures and String.valid_codepoint? 2013-09-29 21:47:12 +02:00
José Valim 0ce2fbb3e4 Move verify! to an is_doc guard in algebra 2013-09-29 19:32:08 +02:00
José Valim 04c5c80353 Merge pull request #1733 from meh/inspect-raise
Add validity checking for Inspect.Algebra documents
2013-09-29 06:11:28 -07:00
José Valim 1cb8f3a654 Merge pull request #1734 from madlep/fix_dict_behaviour_equals
`Dict.size` incorrectly returning `true` when left dict is subset of right dict
2013-09-29 06:11:18 -07:00
meh 7b025dd982 Add validity checking for Inspect.Algebra documents 2013-09-29 14:13:13 +02:00
Julian Doherty (madlep) eff51e4045 explicitly use Dict.size rather than just size in equal?
Fixes bug where `true` was incorrectly returned for `Dict.equal?` when
the 1st argument was a subset of the 2nd argument.

e.g:
```elixir
iex> h1 = HashDict.new(foo: 1, bar: 2)
iex> h2 = HashDict.new(foo: 1, bar: 2, baz: 3)
iex> Dict.equal?(h1, h2)
true # OOPS
iex> Dict.equal?(h2, h1)
false # works as expected other way round
```

The code to check equality was checking size (incorrectly), and then
checking to see if every element in the first dict was present in the
2nd.

However, `size` was being used instead of `Dict.size`. This ended up
calling `Kernel.size` instead - and would always return `true`
regardless of whever the actual size of the `Dict` arguments were the
same or not.
2013-09-29 22:12:57 +10:00
José Valim ea5df228ed String module now depends on Regex module 2013-09-29 13:39:45 +02:00
José Valim 769b69ad69 Ensure String.split/2 works with unicode 2013-09-29 13:35:17 +02:00
Bruce Williams 0e725fb906 Support String.split with an empty binary as the pattern.
As discussed in #1688, currently the following fails:

    iex> String.split "abc", ""
    ** (ArgumentError) argument error
        binary.erl:242: :binary.split/3

These changes add support for an empty binary pattern, piggybacking on
the empty regular expression split (vs using `String.graphemes`,
notably) so that the function continues to support the `:global`
option.

Users should probably be using `String.graphemes` for splitting, but
this keeps the API consistent while fixing this exception.

Closes #1688.

Conflicts:
	lib/elixir/lib/string.ex
2013-09-29 13:30:55 +02:00
José Valim 4b317a75d8 Save typespecs attributes with proper line numbers, closes #1613 2013-09-29 13:15:33 +02:00
José Valim 2977a46865 Improve guards on IEx.Helpers, closes #1535 2013-09-29 13:01:58 +02:00
José Valim d92b2e6831 Avoid calling Kernel:raise from erl 2013-09-25 11:34:37 +02:00
José Valim 0a7f6e7cd2 Merge pull request #1725 from alco/assert_receive
Add a tricky pattern example for assert_receive
2013-09-25 02:23:11 -07:00
Yurii Rashkovskii 8bd43ed677 Merge pull request #1726 from yrashk/raise-macro
Kernel.raise should be a macro to avoid messing up the stack trace
2013-09-25 02:22:36 -07:00
Yurii Rashkovskii bcf8563d38 Kernel.raise should be a macro to avoid messing up the stack trace 2013-09-25 11:20:42 +02:00
Alexei Sholik 352379112a Add a tricky pattern example for assert_receive 2013-09-25 12:07:56 +03:00
José Valim f8c66c8516 Remove unecessary vars cache 2013-09-24 13:21:58 +02:00
José Valim f090045e99 Merge pull request #1720 from edgurgel/patch-1
Minor english correction
2013-09-23 02:42:58 -07:00
Eduardo Gurgel 4256daf6a7 Minor english correction
[ci skip]
2013-09-22 16:46:58 -03:00
José Valim 8c77ada8d2 Merge pull request #1716 from ericmj/mix-deps-compile-false
Fix `compile: false` dependency option
2013-09-17 14:44:36 -07:00
Eric Meadows-Jonsson 63ed270c68 Fix compile: false dependency option 2013-09-17 23:35:02 +02:00
José Valim acdc55bd90 Get rid of the r helper since it causes conflicts related to modules dependencies 2013-09-17 12:35:45 -03:00
José Valim d2bfd10299 Merge pull request #1715 from ericmj/mix-deps-order
Preserve correct order when deps are overriding
2013-09-16 15:19:40 -07:00
Eric Meadows-Jonsson b2e32e4ccd Don't compile deps too many times 2013-09-16 23:42:59 +02:00
Eric Meadows-Jonsson 0c80905f63 Preserve correct order when deps are overriding 2013-09-16 23:42:47 +02:00
José Valim 429fc2094b Merge pull request #1693 from pminten/stream-generate
Add Stream.unfold
2013-09-15 10:12:45 -07:00
José Valim e8c3ed2881 Merge pull request #1714 from pminten/mix-new-default-sup
Make --sup the default for mix
2013-09-15 10:11:23 -07:00
Peter Minten 3d583e7cdc Make --sup the default for mix
Closes #1706
2013-09-15 12:00:02 +02:00
Peter Minten 7b1b0d93d8 Add Stream.unfold
This generates a possibly finite stream of values.
2013-09-15 11:20:56 +02:00
José Valim 9839bfa4ad Deprecate previous partial application syntax 2013-09-14 09:56:40 -03:00
José Valim 0b640d985c Document the dot as a special form
We include common examples, quoted expressions, and more.
2013-09-13 20:53:03 -03:00
José Valim b161d80b4c IEx now respects signals sent from user_drv, closes #1566 2013-09-12 19:33:44 -03:00
José Valim 748b70d796 Slightly tidy up IEx.Server 2013-09-12 11:44:05 -03:00
José Valim 0267f312b1 Fix handling of nested do expressions in EEx, closes #1710 2013-09-12 10:59:48 -03:00
José Valim 8a45ca8905 Merge pull request #1711 from osaut/docs_regex
An example for Regex.compile and correction of small typos in Regex docs.
2013-09-12 06:44:14 -07:00
Olivier Saut 05c3e98f64 Correction of small typos in Regex docs. 2013-09-12 15:38:48 +02:00
José Valim 212acf437a Provide better exceptions for invalid unquote args, closes #1680 2013-09-11 12:19:41 -03:00
José Valim fc0af0542b Improve doctest stripping, closes #1678 2013-09-11 11:34:12 -03:00
José Valim 14272c98b4 Improve invalid match and guard errors, closes #1673 2013-09-11 11:21:10 -03:00
José Valim 42ffd31e0e Record docs adjustments 2013-09-11 10:32:45 -03:00
Eric Meadows-Jonsson 7aeb1b6930 Minor style fix in Enum.take_every/2 2013-09-11 15:01:13 +02:00
José Valim f3f64cdba2 Move to capture syntax 2013-09-10 23:33:53 -03:00
José Valim 5b8a30be6b Update CHANGELOG 2013-09-10 18:51:14 -03:00
José Valim e90b877575 Provide better error message for mix -e, closes #1639 2013-09-10 18:45:58 -03:00
José Valim 3d2ad9ac77 Adjust code to style used in the codebase 2013-09-10 18:43:53 -03:00
José Valim 881bdd5534 Merge pull request #1635 from Raynes/takenth
Add a take_every function
2013-09-10 14:21:10 -07:00
José Valim c3bdfc9ba7 No need to mark typedoc as persisted if we are manually persisting it 2013-09-10 12:46:22 -03:00
José Valim 262e8b5f22 Avoid duplicatation by making the typedoc signature always an empty list 2013-09-10 12:31:20 -03:00
José Valim 2f5afcaab9 Merge pull request #1637 from pminten/typedoc-rebased
Typedoc rebased
2013-09-10 08:01:18 -07:00
José Valim 4e42b75e17 Merge pull request #1614 from alexrp/master
Move Mix.Version to Version in kernel.
2013-09-10 05:51:10 -07:00
alexrp c6f117d60f Move Mix.Version to Version in kernel. 2013-09-10 14:42:04 +02:00
José Valim 80c0ff84fb Merge pull request #1700 from Irio/master
List types in docs giving the idea of aliases
2013-09-09 13:24:55 -07:00
José Valim 59b4b5b9bc Merge pull request #1702 from ericmj/io-docs
Update IO docs and clarify bin* functions
2013-09-09 07:44:08 -07:00
José Valim 2945939f3e Merge pull request #1703 from ericmj/quote-docs
Update quote docs
2013-09-09 07:43:51 -07:00
Eric Meadows-Jonsson 3d9aa3e978 Update quote docs
:binding => :bind_quoted.
2013-09-09 16:35:48 +02:00
Eric Meadows-Jonsson 860fcc6eaa Update IO docs and clarify bin* functions 2013-09-09 16:27:30 +02:00
José Valim 73f40cb10e Merge pull request #1698 from pminten/rename-iterator-error
Rename File.IteratorError -> IO.StreamError
2013-09-09 05:50:34 -07:00
José Valim 8b56252e1b Merge pull request #1699 from alg/multiple-heredocs-handling
Handling of multiple heredocs on the same line
2013-09-09 05:38:39 -07:00
Irio Irineu Musskopf Junior b8a0717873 List types in docs giving the idea of aliases
The order was changed to follow to explanation in the next paragraph.
2013-09-07 16:05:43 -03:00
Aleksey Gureiev 8dbfcba237 added handling of multiple heredocs on the same line 2013-09-07 17:59:40 +03:00
Peter Minten 5bb4f43e9d Rename File.IteratorError -> IO.StreamError
Iterators were replaced with streams a while ago
2013-09-07 16:38:00 +02:00
José Valim 808f8981b3 Merge pull request #1689 from bruce/string-regex-empty-split-docs
Improve String.split and Regex.split docs for empty patterns.
2013-09-07 03:43:55 -07:00
Bruce Williams 73049ac355 Improve String.split and Regex.split docs
Include examples showing that splitting with an empty pattern results
in a final (trim-able) empty string as discussed in #1688.
2013-09-06 15:46:23 -07:00
José Valim d77ccf9415 Improve inspect docs 2013-09-06 12:02:04 -03:00
José Valim 9e644dfba1 Document issues and improve IEx booting process 2013-09-05 18:18:01 -03:00
José Valim e00eedec45 Fix bug on Regex.scan/3 when capturing groups and the regex has no groups 2013-09-05 13:13:57 -03:00
José Valim 4016864db2 Improve IEx out of bounds message 2013-09-04 09:34:08 -03:00
José Valim 38c7edd203 Merge pull request #1685 from ericmj/lc-docs
Make clear behaviour of assignments in comprehensions
2013-09-04 04:32:23 -07:00
Eric Meadows-Jonsson fe3ab0cb77 Make clear behaviour of assignments in comprehensions 2013-09-04 12:18:44 +02:00
José Valim 9f81ac89e5 Remove previously deprecated code 2013-09-03 15:37:02 -03:00
José Valim cd4c1976c9 Update CHANGELOG and move to new style 2013-09-03 15:23:48 -03:00
José Valim 87be134a5b Bump version 2013-09-03 15:14:56 -03:00
Peter Minten a5ea8a63bf Use typedoc attribute to store typedocs.
This requires a bit of a workaround because `typedoc` is also used to store the
doc of the next type to be defined. We store typedocs in the docs table when
types are defined and later filter them out and put them into `typedoc`.
2013-08-22 16:30:24 +02:00
Peter Minten 742258340c Remove unneeded empty lines in the typedoc tests. 2013-08-22 16:30:24 +02:00
Peter Minten 1be3335f0b Add a @typedoc attribute to document types
Also a new `Kernel.Typespec.beam_typedocs` function to get the typedoc
information.
2013-08-22 16:30:24 +02:00
Anthony Grimes 307198c750 Add a take_every function. 2013-08-22 00:18:50 -07:00
266 changed files with 11333 additions and 6030 deletions
+162 -53
View File
@@ -1,6 +1,115 @@
# v0.11.2 (2013-11-14)
* Enhancements
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
* [Mix] Support `build_per_environment: true` in project configuration that manages a separete build per environment, useful when you have per-environment behaviour/compilation
* Backwards incompatible changes
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
# v0.11.1 (2013-11-07)
* Enhancements
* [Mix] Improve dependency convergence by explicitly checking each requirement instead of expecting all requirements to be equal
* [Mix] Support optional dependencies with `optional: true`. Optional dependencies are downloaded for the current project but they are automatically skipped when such project is used as a dependency
* Bug fixes
* [Kernel] Set compilation status per ParallelCompiler and not globally
* [Mix] Ensure Mix does not load previous dependencies versions before `deps.get`/`deps.update`
* [Mix] Ensure umbrella apps are sorted before running recursive commands
* [Mix] Ensure umbrella apps run in the same environment as the parent project
* [Mix] Ensure dependency tree is topsorted before compiling
* [Mix] Raise error when duplicated projects are pushed into the stack
* [URI] Allow lowercase escapes in URI
* Backwards incompatible changes
* [Mix] Setting `:load_paths` in your project configuration is deprecated
# v0.11.0 (2013-11-02)
* Enhancements
* [Code] Eval now returns variables from other contexts
* [Dict] Document and enforce all dicts use the match operator (`===`) when checking for keys
* [Enum] Add `Enum.slice/2` with a range
* [Enum] Document and enforce `Enum.member?/2` to use the match operator (`===`)
* [IEx] Split `IEx.Evaluator` from `IEx.Server` to allow custom evaluators
* [IEx] Add support for `IEx.pry` which halts a given process for inspection
* [IO] Add specs and allow some IO APIs to receive any data that implements `String.Chars`
* [Kernel] Improve stacktraces on command line interfaces
* [Kernel] Sigils can now handle balanced tokens as in `%s(f(o)o)`
* [Kernel] Emit warnings when an alias is not used
* [Macro] Add `Macro.pipe/3` and `Macro.unpipe/1` for building pipelines
* [Mix] Allow umbrella children to share dependencies between them
* [Mix] Allow mix to be escriptize'd
* [Mix] Speed mix projects compilation by relying on more manifests information
* [Protocol] Protocols now provide `impl_for/1` and `impl_for!/1` functions which receive a structure and returns its respective implementation, otherwise returns nil or an error
* [Set] Document and enforce all sets use the match operator (`===`) when checking for keys
* [String] Update to Unicode 6.3.0
* [String] Add `String.slice/2` with a range
* Bug fixes
* [Exception] Ensure `defexception` fields can be set dynamically
* [Kernel] Guarantee aliases hygiene is respected when the current module name is not known upfront
* [Kernel] `Kernel.access/2` no longer flattens lists
* [Mix] Ensure cyclic dependencies are properly handled
* [String] Implement the extended grapheme cluster algorithm for `String` operations
* Deprecations
* [Kernel] `pid_to_list/1`, `list_to_pid/1`, `binary_to_atom/2`, `binary_to_existing_atom/2` and `atom_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
* [Kernel] `insert_elem/3` and `delete_elem/2` are deprecated in favor of `Tuple.insert_at/3` and `Tuple.delete_at/2`
* [Kernel] Use of `in` inside matches (as in `x in [1,2,3] -> x`) is deprecated in favor of the guard syntax (`x when x in [1,2,3]`)
* [Macro] `Macro.expand_all/2` is deprecated
* [Protocol] `@only` and `@except` in protocols are now deprecated
* [Protocol] Protocols no longer fallback to `Any` out of the box (this functionality needs to be explicitly enabled by setting `@fallback_to_any` to true)
* [String] `String.to_integer/1` and `String.to_float/1` are deprecated in favor of `Integer.parse/1` and `Float.parse/1`
* Backwards incompatible changes
* [CLI] Reading `.elixirrc` has been dropped in favor of setting env vars
* [Kernel] `Kernel.access/2` now expects the second argument to be a compile time list
* [Kernel] `fn -> end` quoted expression is no longer wrapped in a `do` keyword
* [Kernel] Quoted variables from the same module must be explicitly shared. Previously, if a function returned `quote do: a = 1`, another function from the same module could access it as `quote do: a`. This has been fixed and the variables must be explicitly shared with `var!(a, __MODULE__)`
* [Mix] Umbrella apps now treat children apps as dependencies. This means all dependencies will be checked out in the umbrela `deps` directory. On upgrade, child apps need to point to the umbrella project by setting `deps_path: "../../deps_path", lockfile: "../../mix.lock"` in their project config
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
# v0.10.3 (2013-10-02)
* Enhancements
* [Enum] Add `Enum.take_every/2`
* [IEx] IEx now respects signals sent from the Ctrl+G menu
* [Kernel] Allow documentation for types with `@typedoc`
* [Mix] Allow apps to be selected in umbrella projects
* [Record] Generated record functions `new` and `update` also take options with strings as keys
* [Stream] Add `Stream.unfold/1`
* Bug fixes
* [Dict] Fix a bug when a HashDict was marked as equal when one was actually a subset of the other
* [EEx] Solve issue where `do` blocks inside templates were not properly aligned
* [ExUnit] Improve checks and have better error reports on poorly aligned doctests
* [Kernel] Fix handling of multiple heredocs on the same line
* [Kernel] Provide better error messages for match, guard and quoting errors
* [Kernel] Make `Kernel.raise/2` a macro to avoid messing up stacktraces
* [Kernel] Ensure `&()` works on quoted blocks with only one expression
* [Mix] Address an issue where a dependency was not compiled in the proper order when specified in different projects
* [Mix] Ensure `compile: false` is a valid mechanism for disabling the compilation of dependencies
* [Regex] Fix bug on `Regex.scan/3` when capturing groups and the regex has no groups
* [String] Fix a bug with `String.split/2` when given an empty pattern
* [Typespec] Guarantee typespecs error reports point to the proper line
* Deprecations
* [Kernel] The previous partial application syntax (without the `&` operator) has now been deprecated
* [Regex] `Regex.captures/3` is deprecated in favor of `Regex.named_captures/3`
* [String] `String.valid_codepoint?/1` is deprecated in favor of pattern matching with `<<_ :: utf8 >>`
* Backwards incompatible changes
* [IEx] The `r/0` helper has been removed as it caused surprising behaviour when many modules with dependencies were accumulated
* [Mix] `Mix.Version` was renamed to `Version`
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
# v0.10.2 (2013-09-03)
* enhancements
* Enhancements
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
@@ -24,7 +133,7 @@
* [String] Add `String.ljust/2`, `String.rjust/2`, `String.ljust/3` and `String.rjust/3`
* [URI] `URI.parse/1` supports IPv6 addresses
* bug fix
* Bug fixes
* [Behaviour] Do not compile behaviour docs if docs are disabled on compilation
* [ExUnit] Doctests no longer eat too much space and provides detailed reports for poorly indented lines
* [File] Fix a bug where `File.touch(file, datetime)` was not setting the proper datetime when the file did not exist
@@ -42,7 +151,7 @@
* [String] `String.slice/3` still returns the proper result when there is no length to be extracted
* [System] `System.get_env/0` now returns a list of tuples as previously advertised
* deprecations
* Deprecations
* [Dict] `Dict.update/3` is deprecated in favor of `Dict.update!/3`
* [Enum] `Enum.min/2` and `Enum.max/2` are deprecated in favor of `Enum.min_by/2` and `Enum.max_by/2`
* [Enum] `Enum.join/2` and `Enum.map_join/3` with a char list are deprecated
@@ -55,7 +164,7 @@
* [Macro] `Macro.unescape_binary/1` and `Macro.unescape_binary/2` are deprecated in favor of `Macro.unescape_string/1` and `Macro.unescape_string/2`
* [Mix] `:umbrella` option for umbrella paths has been deprecated in favor of `:in_umbrella`
* backwards incompatible changes
* Backwards incompatible changes
* [IO] IO functions now only accept iolists as arguments
* [Kernel] `Binary.Chars` was renamed to `String.Chars`
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
@@ -63,7 +172,7 @@
# v0.10.1 (2013-08-03)
* enhancements
* Enhancements
* [Behaviour] Add support for `defmacrocallback/1`
* [Enum] Add `Enum.shuffle/1`
* [ExUnit] The `:trace` option now also reports run time for each test
@@ -86,7 +195,7 @@
* [String] Add `Regex.scan/3` now supports capturing groups
* [String] Add `String.reverse/1`
* bug fix
* Bug fixes
* [Behaviour] Ensure callbacks are stored in the definition order
* [CLI] Speed up boot time on Elixir .bat files
* [IEx] Reduce cases where IEx parser can get stuck
@@ -101,19 +210,19 @@
* [OptionParser] Ensure `:keep` and `:integer`|`:float` can be given together as options
* [OptionParser] Ensure `--no-flag` sets `:flag` to false when `:flag` is a registered boolean switch
* deprecations
* Deprecations
* [Kernel] `function(Mod.fun/arity)` and `function(fun/arity)` are deprecated in favor of `&Mod.fun/arity` and `&fun/arity`
* [Kernel] `function/3` is deprecated in favor of `Module.function/3`
* [Kernel] `Kernel.ParallelCompiler` now receives a set of callbacks instead of a single one
* [Mix] `:test_coverage` option now expect keywords arguments and the `--cover` flag is now treated as a boolean
* backwards incompatible changes
* Backwards incompatible changes
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
# v0.10.0 (2013-07-15)
* enhancements
* Enhancements
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
* [HashDict] Optimize `HashDict` to store pairs in a cons cell reducing storage per key by half
* [Kernel] Add pretty printing support for inspect
@@ -126,7 +235,7 @@
* [Mix] Projects can now define an `:elixir` key to outline supported Elixir versions
* [Typespec] Improve error messages to contain file, line and the typespec itself
* bug fix
* Bug fixes
* [CLI] Elixir can now run on Unix directories with `:` in its path
* [Kernel] `match?/2` does not leak variables to outer scope
* [Kernel] Keep `head|tail` format when splicing at the tail
@@ -135,7 +244,7 @@
* [Mix] Empty projects no longer leave empty ebin files on `mix compile`
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
* deprecations
* Deprecations
* [Enum] Receiving the index of iteration in `Enum.map/2` and `Enum.each/2` is deprecated in favor of `Stream.with_index/1`
* [File] `File.iterator/1` and `File.biniterator/1` are deprecated in favor of `IO.stream/1` and `IO.binstream/1`
* [File] `File.iterator!/2` and `File.biniterator!/2` are deprecated in favor of `File.stream!/2` and `File.binstream!/2`
@@ -144,7 +253,7 @@
* [Mix] Deprecate `mix run EXPR` in favor of `mix run -e EXPR`
* [Record] `Record.__index__/2` deprecated in favor of `Record.__record__(:index, key)`
* backwards incompatible changes
* Backwards incompatible changes
* [Kernel] The `Binary.Inspect` protocol has been renamed to `Inspect`
* [Kernel] Tighten up the grammar rules regarding parentheses omission, previously the examples below would compile but now they raise an error message:
@@ -157,7 +266,7 @@
# v0.9.3 (2013-06-23)
* enhancements
* Enhancements
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
* [Kernel] Add `--warnings-as-errors` to Elixir's compiler options
* [Kernel] Print warnings to stderr
@@ -171,7 +280,7 @@
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
* [Record] Allow types to be given to `defrecordp`
* bug fix
* Bug fixes
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
* [Kernel] Improve error messages on default clauses clash
* [Kernel] `__MODULE__.Foo` now returns `Foo` when outside of a Module
@@ -180,12 +289,12 @@
* [Kernel] Allow `(x, y) when z` in function clauses and try expressions
* [Mix] Mix now properly evaluates rebar scripts
* deprecations
* Deprecations
* [Code] `Code.string_to_ast/1` has been deprecated in favor of `Code.string_to_quoted/1`
* [Macro] `Macro.to_binary/1` has been deprecated in favor of `Macro.to_string/1`
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
* backwards incompatible changes
* Backwards incompatible changes
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
@@ -195,7 +304,7 @@
# v0.9.2 (2013-06-13)
* enhancements
* Enhancements
* [ExUnit] `capture_io` now captures prompt by default
* [Mix] Automatically import git dependencies from Rebar
* [Mix] Support for dependencies directly from the umbrella application
@@ -203,7 +312,7 @@
* [String] Add `String.contains?`
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
* bug fix
* Bug fixes
* [HashDict] Ensure HashDict uses exact match throughout its implementation
* [IEx] Do not interpret ANSI codes in IEx results
* [IEx] Ensure `--cookie` is set before accessing remote shell
@@ -211,21 +320,21 @@
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
* [Mix] Start the application by default on escripts
* deprecations
* Deprecations
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
* [Kernel] `super` no longer supports implicit arguments
* backwards incompatible changes
* Backwards incompatible changes
* [Kernel] The `=~` operator now returns true or false instead of an index
# v0.9.1 (2013-05-30)
* enhancements
* Enhancements
* [IEx] Limit the number of entries kept in history and allow it to be configured
* [Kernel] Add `String.start_with?` and `String.end_with?`
* [Typespec] Allow keywords, e.g. `[foo: integer, bar: boolean | module]`, in typespecs
* bug fix
* Bug fixes
* [Dict] `Enum.to_list` and `Dict.to_list` now return the same results for dicts
* [IEx] Enable shell customization via the `IEx.Options` module
* [Kernel] Fix a bug where `unquote_splicing` did not work on the left side of a stab op
@@ -236,12 +345,12 @@
# v0.9.0 (2013-05-23)
* enhancements
* Enhancements
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
* [IEx] Add support for .iex files that are loaded during shell's boot process
* [IEx] Add `import_file/1` helper
* backwards incompatible changes
* Backwards incompatible changes
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
* [Kernel] Erlang R15 is no longer supported
@@ -249,7 +358,7 @@
# v0.8.3 (2013-05-22)
* enhancements
* Enhancements
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
* [CLI] Support `--hidden` and `--cookie` flags for distributed Erlang
* [Enum] Add `Enum.to_list/1`, `Enum.member?/2`, `Enum.uniq/2`, `Enum.max/1`, `Enum.max/2`, `Enum.min/1` and `Enum.min/2`
@@ -272,7 +381,7 @@
* [Mix] Load beam files available at `MIX_PATH` on CLI usage
* [String] Add `String.valid?` and `String.valid_character?`
* bug fix
* Bug fixes
* [ExUnit] Handle exit messages from in ExUnit
* [ExUnit] Failures on ExUnit's setup_all now invalidates all tests
* [Kernel] Ensure we don't splice keyword args unecessarily
@@ -284,7 +393,7 @@
* [Regex] Fix badmatch with `Regex.captures(%r/(.)/g, "cat")`
* [URI] Downcase host and scheme and URIs
* deprecations
* Deprecations
* [Code] `Code.eval` is deprecated in favor of `Code.eval_string`
* [Exception] `Exception.format_entry` is deprecated in favor of `Exception.format_stacktrace_entry`
* [ExUnit] `assert left inlist right` is deprecated in favor of `assert left in right`
@@ -293,7 +402,7 @@
* [Kernel] `var_context` in quote was deprecated in favor of `context`
* [Kernel] `Enum.at!` and `Dict.get!` is deprecated in favor of `Enum.fetch!` and `Dict.fetch!`
* backwards incompatible changes
* Backwards incompatible changes
* [Dict] `List.Dict` was moved to `ListDict`
* [IO] `IO.gets`, `IO.getn` and friends now return binaries when reading from stdio
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
@@ -301,7 +410,7 @@
# v0.8.2 (2013-04-20)
* enhancements
* Enhancements
* [ExUnit] Use ANSI escape codes in CLI output
* [ExUnit] Include suite run time on CLI results
* [ExUnit] Add support to doctests, allowing test cases to be generated from code samples
@@ -314,7 +423,7 @@
* [Mix] Support `--cover` on mix test and `test_coverage` on Mixfiles
* [Record] Each record now provides `Record.options` with the options supported by its `new` and `update` functions
* bug fix
* Bug fixes
* [Binary] inspect no longer escapes standalone hash `#`
* [IEx] The `h` helper can now retrieve docs for special forms
* [Kernel] Record optimizations were not being triggered in functions inside the record module
@@ -322,11 +431,11 @@
* [Kernel] Fix a bug where nested records could not use the Record[] syntax
* [Path] Fix a bug on `Path.expand` when expanding paths starting with `~`
* deprecations
* Deprecations
* [Kernel] `setelem/3` is deprecated in favor of `set_elem/3`
* [Kernel] `function(:is_atom, 1)` is deprecated in favor of `function(is_atom/1)`
* backwards incompatible changes
* Backwards incompatible changes
* [Kernel] `unquote` now only applies to the closest quote. If your code contains a quote that contains another quote that calls unquote, it will no longer work. Use `Macro.escape` instead and pass your quoted contents up in steps, for example:
quote do
@@ -341,7 +450,7 @@
# v0.8.1 (2013-02-17)
* enhancements
* Enhancements
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
* [ExUnit] Add support to ExUnit.CaseTemplate to share callbacks in between test cases
* [IO] Add `IO.ANSI` to make it easy to write ANSI escape codes
@@ -354,13 +463,13 @@
* [OptionParser] Switches are now overridden by default but can be kept in order if chosen
* [Typespec] Better error reporting for invalid typespecs
* bug fix
* Bug fixes
* [Mix] Allow Mix projects to be generated with just one letter
* backwards incompatible changes
* Backwards incompatible changes
* [Kernel] `before_compile` and `after_compile` callbacks now receive the environment as first argument instead of the module
* deprecations
* Deprecations
* [ExUnit] Explicitly defined test/setup/teardown functions are deprecated
* [Kernel] Tidy up and clean `quote` API
* [Kernel] Old `:local.(args)` syntax is deprecated
@@ -368,7 +477,7 @@
# v0.8.0 (2013-01-28)
* enhancements
* Enhancements
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
* [Binary] Support `\a` escape character in binaries
* [Binary] Support syntax shortcut for specifying size in bit syntax
@@ -391,16 +500,16 @@
* [String] Add `String.capitalize` and `String.slice`
* [System] Add `System.tmp_dir`, `System.cwd` and `System.user_home`
* bug fix
* Bug fixes
* [Kernel] `import` with `only` accepts functions starting with underscore
* [String] `String.first` and `String.last` return nil for empty binaries
* [String] `String.rstrip` and `String.lstrip` now verify if argument is a binary
* [Typespec] Support `...` inside typespec's lists
* backwards incompatible changes
* Backwards incompatible changes
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
* deprecations
* Deprecations
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `ListDict`
* [File] Deprecate path related functions in favor of the module `Path`
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
@@ -410,7 +519,7 @@
# v0.7.2 (2012-12-04)
* enhancements
* Enhancements
* [CLI] `--debug-info` is now true by default
* [ExUnit] Make ExUnit exit happen in two steps allowing developers to add custom `at_exit` hooks
* [IEx] Many improvements to helpers functions `h/1`, `s/1` and others
@@ -423,12 +532,12 @@
* [Record] Record accessors are now optimized and can be up to 6x faster in some cases
* [String] Support `\xXX` and `\x{HEX}` escape sequences in strings, char lists and regexes
* bug fix
* Bug fixes
* [Bootstrap] Compiling Elixir source no longer fails if environment variables contain utf-8 entries
* [IEx] IEx will now wait for all command line options to be processed before starting
* [Kernel] Ensure proper stacktraces when showing deprecations
* deprecations
* Deprecations
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
* [Record] Default-based generated functions are deprecated
@@ -436,7 +545,7 @@
# v0.7.1 (2012-11-18)
* enhancements
* Enhancements
* [IEx] Only show documented functions and also show docs for default generated functions
* [IO] Add `IO.binread`, `IO.binwrite` and `IO.binreadline` to handle raw binary file operations
* [ExUnit] Add support for user configuration at `HOME/.ex_unit.exs`
@@ -454,20 +563,20 @@
* [Typespec] Define a default type `t` for protocols and records
* [Typespec] Add support for the access protocol in typespecs
* bug fix
* Bug fixes
* [Kernel] Fix an issue where variables inside clauses remained unassigned
* [Kernel] Ensure `defoverridable` functions can be referred in many clauses
* [Kernel] Allow keywords as function names when following a dot (useful when integrating with erlang libraries)
* [File] File is opened by default on binary mode instead of utf-8
* deprecations
* Deprecations
* [Behaviour] `defcallback/1` is deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
* [Enum] `Enum.times` is deprecated in favor of using ranges
* [System] `halt` moved to `System` module
# v0.7.0 (2012-10-20)
* enhancements
* Enhancements
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
* [Binary] Add a Dict binary that converts its keys to binaries on insertion
* [Binary] Optimize `Binary.Inspect` and improve inspect for floats
@@ -493,7 +602,7 @@
* [Regex] Add `:return` option to `Regex.run` and `Regex.scan`
* [String] Add a String module responsible for handling UTf-8 binaries
* bug fix
* Bug fixes
* [File] `File.cp` and `File.cp_r` now preserves the file's mode
* [IEx] Fix a bug where printing to `:stdio` on `IEx` was causing it to hang
* [Macro] Fix a bug where quoted expressions were not behaving the same as their non-quoted counterparts
@@ -501,12 +610,12 @@
* [Mix] Mix now exits with status 1 in case of failures
* [Protocol] Avoid false positives on protocol dispatch (a bug caused the dispatch to be triggered to an invalid protocol)
* backwards incompatible changes
* Backwards incompatible changes
* [ExUnit] `setup` and `teardown` callbacks now receives the test name as second argument
* [Kernel] Raw function definition with `def/4`, `defp/4`, `defmacro/4`, `defmacrop/4` now evaluates all arguments. The previous behaviour was accidental and did not properly evaluate all arguments
* [Kernel] Change tuple-related (`elem` and `setelem`), Enum functions (`find_index`, `nth!` and `times`) and List functions (List.key*) to zero-index
* deprecations
* Deprecations
* [Code] `Code.require_file` and `Code.load_file` now expect the full name as argument
* [Enum] `List.reverse/1` and `List.zip/2` were moved to `Enum`
* [GenServer] Rename `GenServer.Behavior` to `GenServer.Behaviour`
@@ -516,14 +625,14 @@
# v0.6.0 (2012-08-01)
* incompatible changes
* Backwards incompatible changes
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
* [Kernel] loop and recur are removed in favor of recursion with named functions
* [Module] Removed data functions in favor of unifying the attributes API
* deprecations
* Deprecations
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
* [File] `File.read_info` is deprecated in favor of `File.stat`
@@ -535,7 +644,7 @@
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
* enhancements
* Enhancements
* [Enum] Enhance Enum protocol to support `Enum.count`
* [Enum] Optimize functions when a list is given as collection
* [Enum] Add `find_index`, `nth!` and others
+10 -5
View File
@@ -19,11 +19,14 @@ INSTALL_PROGRAM = $(INSTALL) -m755
define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
$(Q) rm -rf lib/$(1)/_build
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@ rm -rf lib/$(1)/ebin
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
test_$(1): $(1)
@@ -42,7 +45,9 @@ compile: lib/elixir/src/elixir.app.src erlang elixir
lib/elixir/src/elixir.app.src: src/elixir.app.src
$(Q) rm -rf lib/elixir/src/elixir.app.src
$(Q) cp src/elixir.app.src lib/elixir/src/elixir.app.src
$(Q) echo "%% This file is automatically generated from <project_root>/src/elixir.app.src" \
>lib/elixir/src/elixir.app.src
$(Q) cat src/elixir.app.src >>lib/elixir/src/elixir.app.src
erlang:
$(Q) cd lib/elixir && $(REBAR) compile
@@ -65,10 +70,10 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
$(Q) cd lib/elixir && $(REBAR) compile
unicode: $(UNICODE)
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
$(UNICODE): lib/elixir/unicode/unicode.ex lib/elixir/unicode/UnicodeData.txt lib/elixir/unicode/GraphemeBreakProperty.txt
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
+4 -2
View File
@@ -10,11 +10,13 @@ If you want to contribute to Elixir or run it from source, clone this repository
$ git clone https://github.com/elixir-lang/elixir.git
$ cd elixir
$ make test
$ make clean test
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
+2 -2
View File
@@ -10,7 +10,7 @@ This document simply outlines the release process:
4) Commit changes above with title "Release vVERSION"
5) Push master and create tag from master branch
5) Push master and create tag vVERSION from master branch
6) Release new docs with `make release_docs`, move docs to `docs/stable`
@@ -20,7 +20,7 @@ This document simply outlines the release process:
9) Merge master into stable branch and push it
10) After release, bump versions and add `-dev` back
10) After release, bump versions, add `-dev` back and commit
11) `make release_docs` once again and push `elixir-lang.github.com`
+1 -1
View File
@@ -1 +1 @@
0.10.2
0.11.2
-2
View File
@@ -73,8 +73,6 @@ done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
+1 -1
View File
@@ -38,4 +38,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-nouser -s Elixir.IEx.CLI" +iex "$@"
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/usr/bin/env elixir
Mix.start
Mix.CLI.run
Mix.CLI.main
+3 -3
View File
@@ -31,7 +31,7 @@ defmodule EEx.Compiler do
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
buffer = state.engine.handle_expr(buffer, mark, contents)
generate_buffer(t, buffer, scope, state.dict([]))
generate_buffer(t, buffer, scope, state)
end
defp generate_buffer([{ :middle_expr, line, _, chars }|t], buffer, [current|scope], state) do
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
else
key = length(state.dict)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict([{key, buffer}|&1]) }
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
end
end
@@ -108,7 +108,7 @@ defmodule EEx.Compiler do
end
defp insert_quotes(list, dict) when is_list(list) do
Enum.map list, insert_quotes(&1, dict)
Enum.map list, &insert_quotes(&1, dict)
end
defp insert_quotes(other, _dict) do
+2 -2
View File
@@ -103,7 +103,7 @@ defmodule EEx.Tokenizer do
# token and, if so, it is not followed by an "end"
# token. If this is the case, we are on a start expr.
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
{ :ok, tokens } ->
{ :ok, _line, tokens } ->
tokens = Enum.reverse(tokens)
fn_index = fn_index(tokens)
@@ -136,7 +136,7 @@ defmodule EEx.Tokenizer do
end
defp end_index(tokens) do
Enum.find_index(tokens, match?({ :end, _ }, &1)) || :infinity
Enum.find_index(tokens, &match?({ :end, _ }, &1)) || :infinity
end
defp check_spaces(string, token) do
+15
View File
@@ -264,6 +264,21 @@ foo
assert_eval expected, string
end
test "evaluates nested do expressions" do
string = """
<% y = ["a", "b", "c"] %>
<%= cond do %>
<% "a" in y -> %>
Good
<% true -> %>
<% if true do %>true<% else %>false<% end %>
Bad
<% end %>
"""
assert_eval "\n\n Good\n \n", string
end
test "unicode" do
template = """
• <%= "•" %> •
+10 -2
View File
@@ -17,7 +17,6 @@
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
list_vars=nil, %% a list of vars passed down to Macro.Env
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
@@ -26,6 +25,8 @@
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
@@ -36,7 +37,7 @@
}).
-record(elixir_quote, {
line=0,
line=nil,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
@@ -46,6 +47,13 @@
escape=false
}).
-record(elixir_tokenizer, {
file,
terminators=[],
check_terminators=true,
existing_atoms_only=false
}).
%% Introspection
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
+4 -9
View File
@@ -11,8 +11,6 @@ defprotocol Access do
following built-in types: keywords, records and functions.
"""
@only [List, Record, Atom]
@doc """
Receives the element being accessed and the access item.
"""
@@ -22,6 +20,7 @@ end
defimpl Access, for: List do
@doc """
Access the given key in a tuple list.
The key is found via the `===` operator.
## Examples
@@ -34,14 +33,10 @@ defimpl Access, for: List do
"★☆"
"""
def access(dict, key)
def access([{ key, value }|_], key), do: value
def access([{ _, _ }|t], key), do: access(t, key)
def access([], _key), do: nil
def access(list, key) do
case :lists.keyfind(key, 1, list) do
{ ^key, value } -> value
false -> nil
end
end
end
defimpl Access, for: Atom do
+20 -20
View File
@@ -1,40 +1,40 @@
defmodule Application.Behaviour do
@moduledoc """
This module is a convenience to define application module callbacks.
Default callbacks for applications.
In Erlang/OTP, an application is a component that can be started
and stopped as a unit, and which can be re-used in other systems
as well.
and stopped as a unit, and which can be reused in other systems.
The first step to achieve this is to define an application specification.
The first step in creating an application is to define an application specification.
For example, if your application is named `:my_app`, an app specification
should exist at `ebin/my_app.app`. This file is usually defined by
build tools like Mix.
Then, with the app specification in hands, we must also define an
application module callback that controls how to start and stop
such applications. This module is about defining such callbacks.
With the app specification in hand, we must define
application module callbacks that control how to start and stop
instances of the application. This module is about defining such callbacks.
There are two callbacks required to be implemented:
There are two callbacks which must be implemented:
1. `start(type, args)` - It must return `{ :ok, pid }` or
1. `start(type, args)` - must return `{ :ok, pid }` or
`{ :ok, pid, state }`, where `pid` is the process identifier
of the supervisor tree root;
of the supervisor tree root and `state` is application defined
state information;
2. `stop(state)` receives the state returned by `start` and should
do any necessary cleaning up. Notice that shutting down the supervisor
2. `stop(state)` - receives the `state` returned by `start` and should
do any necessary clean up. Notice that shutting down the supervisor
is automatically handled by the VM;
When using this module, it simply tags the module behaviour as
`:application` and defines a default `stop/1` callback. The `start/2`
still needs to be defined by the user.
When using this module, it tags the module behaviour as
`:application` and provides a default `stop/1` callback. The `start/2` callback
still needs to be implemented by the user.
You can learn more about the `:application` module, the application
specification and the application module callbacks below:
specification and the application module callbacks from these sources:
http://www.erlang.org/doc/man/application.html
http://www.erlang.org/doc/design_principles/applications.html
http://learnyousomeerlang.com/building-otp-applications
* http://www.erlang.org/doc/man/application.html
* http://www.erlang.org/doc/design_principles/applications.html
* http://learnyousomeerlang.com/building-otp-applications
## Example
@@ -102,4 +102,4 @@ defmodule Application.Behaviour do
defoverridable [stop: 1]
end
end
end
end
+12 -11
View File
@@ -1,12 +1,12 @@
defmodule Behaviour do
@moduledoc """
A convenience module for defining behaviours.
Utilities for defining behaviour intefaces.
Behaviours can be referenced by other modules
in order to ensure they implement the proper
callbacks.
to ensure they implement required callbacks.
For example, you can specify the `URI.Parser`
behaviour as follow:
behaviour as follows:
defmodule URI.Parser do
use Behaviour
@@ -18,7 +18,7 @@ defmodule Behaviour do
defcallback default_port() :: integer
end
And then a specific module may use it as:
And then a module may use it as:
defmodule URI.HTTP do
@behaviour URI.Parser
@@ -26,24 +26,25 @@ defmodule Behaviour do
def parse(info), do: info
end
In case the behaviour changes or URI.HTTP does
If the behaviour changes or `URI.HTTP` does
not implement one of the callbacks, a warning
will be raised.
## Implementation
Behaviours since Erlang R15 must be defined via
Since Erlang R15, behaviours must be defined via
`@callback` attributes. `defcallback` is a simple
mechanism that defines the `@callback` attribute
according to the type specification and also allows
docs and defines a custom function signature.
according to the given type specification. `defcallback` allows
documentsion to be created for the callback and defines
a custom function signature.
The callbacks and their documentation can be retrieved
via the `__behaviour__` callback function.
"""
@doc """
Defines a callback according to the given type specification.
Define a function callback according to the given type specification.
"""
defmacro defcallback({ :::, _, [fun, return] }) do
do_defcallback(fun, return, __CALLER__)
@@ -54,7 +55,7 @@ defmodule Behaviour do
end
@doc """
Defines a macro callback according to the given type specification.
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return] }) do
do_defmacrocallback(fun, return, __CALLER__)
+75 -43
View File
@@ -6,24 +6,24 @@ defmodule Code do
end
@moduledoc """
The Code module is responsible for managing code compilation,
code evaluation and code loading.
Utilities for managing code compilation, code evaluation and code loading.
It complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
to add behavior which is specific to Elixir.
"""
@doc """
Returns all loaded files.
List all loaded files.
"""
def loaded_files do
:elixir_code_server.call :loaded
end
@doc """
Removes the given files from the loaded files list.
The modules defined in the file are not removed,
Remove files from the loaded files list.
The modules defined in the file are not removed;
calling this function only removes them from the list,
allowing them to be required again.
"""
@@ -32,7 +32,8 @@ defmodule Code do
end
@doc """
Appends a path to the Erlang VM code path.
Append a path to the Erlang VM code path.
The path is expanded with `Path.expand/1` before being appended.
"""
def append_path(path) do
@@ -40,7 +41,8 @@ defmodule Code do
end
@doc """
Prepends a path to the Erlang VM code path.
Prepend a path to the Erlang VM code path.
The path is expanded with `Path.expand/1` before being prepended.
"""
def prepend_path(path) do
@@ -48,7 +50,8 @@ defmodule Code do
end
@doc """
Deletes a path from the Erlang VM code path.
Delete a path from the Erlang VM code path.
The path is expanded with `Path.expand/1` before being deleted.
"""
def delete_path(path) do
@@ -56,9 +59,12 @@ defmodule Code do
end
@doc """
Evaluates the contents given by `string`. The second argument is
a keyword list of variable bindings, followed by a keyword list of
environment options. Those options can be:
Evaluate the contents given by `string`.
The `binding` argument is a keyword list of variable bindings.
The `opts` argument is a keyword list of environment options.
Those options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line on which the script starts
@@ -83,9 +89,12 @@ defmodule Code do
etc.
Returns a tuple of the form `{ value, binding }`,
where `value` is the the value returned from evaluating `string`; `binding`
is a keyword list with the value of all variable bindings after evaluating
`string`. If an error occurs while evaluating `string` an exception will be raised.
where `value` is the the value returned from evaluating `string`.
If an error occurs while evaluating `string` an exception will be raised.
`binding` is a keyword list with the value of all variable bindings
after evaluating `string`. The binding key is usually an atom, but it
may be a tuple for variables defined in a different context.
## Examples
@@ -95,7 +104,7 @@ defmodule Code do
iex> Code.eval_string("c = a + b", [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2, c: 3]}
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
{3, [a: 3, b: 2]}
For convenience, you can pass `__ENV__` as the `opts` argument and
@@ -123,7 +132,7 @@ defmodule Code do
end
@doc """
Evaluates the quoted contents.
Evaluate the quoted contents.
See `eval_string/3` for a description of arguments and return values.
@@ -166,10 +175,10 @@ defmodule Code do
end
defp validate_requires(kind, requires) do
valid = is_list(requires) and Enum.all?(requires, is_atom(&1))
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
end
end
@@ -179,7 +188,7 @@ defmodule Code do
end)
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, module }]"
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, module }]"
end
end
@@ -191,12 +200,14 @@ defmodule Code do
end)
unless valid do
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
end
end
@doc """
Converts the given string to its quoted form. Returns `{ :ok, quoted_form }`
Convert the given string to its quoted form.
Returns `{ :ok, quoted_form }`
if it succeeds, `{ :error, { line, error, token } }` otherwise.
## Options
@@ -209,10 +220,10 @@ defmodule Code do
* `:existing_atoms_only` - When `true`, raises an error
when non-existing atoms are found by the tokenizer.
## Macro.to_string/1
## Macro.to_string/2
The opposite of converting a string to its quoted form is
`Macro.to_string/1`, which converts a quoted form to a string/binary
`Macro.to_string/2`, which converts a quoted form to a string/binary
representation.
"""
def string_to_quoted(string, opts // []) when is_list(opts) do
@@ -227,7 +238,9 @@ defmodule Code do
end
@doc """
Converts the given string to its quoted form. It returns the ast if it succeeds,
Convert the given string to its quoted form.
It returns the ast if it succeeds,
raises an exception otherwise. The exception is a `TokenMissingError`
in case a token is missing (usually because the expression is incomplete),
`SyntaxError` otherwise.
@@ -246,7 +259,9 @@ defmodule Code do
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
@doc """
Loads the given `file`. Accepts `relative_to` as an argument to tell where
Load the given file.
Accepts `relative_to` as an argument to tell where
the file is located. If the file was already required/loaded, loads it again.
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
module defined in the file.
@@ -266,9 +281,11 @@ defmodule Code do
end
@doc """
Requires the given `file`. Accepts `relative_to` as an argument to tell where
Require the given `file`.
Accepts `relative_to` as an argument to tell where
the file is located. The return value is the same as that of `load_file/2`. If
the file was already required/loaded, doesn't do anything and returns nil.
the file was already required/loaded, doesn't do anything and returns `nil`.
Notice that if `require_file` is invoked by different processes concurrently,
the first process to invoke `require_file` acquires a lock and the remaining
@@ -294,7 +311,8 @@ defmodule Code do
end
@doc """
Loads the compilation options from the code server.
Load the compilation options from the code server.
Check `compiler_options/1` for more information.
"""
def compiler_options do
@@ -302,8 +320,9 @@ defmodule Code do
end
@doc """
Sets compilation options. These options are global
since they are stored by Elixir's Code Server.
Set compilation options.
These options are global since they are stored by Elixir's Code Server.
Available options are:
@@ -312,7 +331,7 @@ defmodule Code do
* `:debug_info` - when `true`, retain debug information in the compiled module.
This allows a developer to reconstruct the original source
code, for such reasons, `false` by default;
code, `false` by default;
* `:ignore_module_conflict` - when `true`, override modules that were already defined
without raising errors, `false` by default;
@@ -325,7 +344,9 @@ defmodule Code do
end
@doc """
Compiles the given string and returns a list of tuples where
Compile the given string.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
@@ -336,7 +357,9 @@ defmodule Code do
end
@doc """
Compiles the quoted expression and returns a list of tuples where
Compile the quoted expression.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
"""
@@ -345,8 +368,10 @@ defmodule Code do
end
@doc """
Ensures the given module is loaded. If the module is already
loaded, it works as no-op. If the module was not yet loaded,
Ensure the given module is loaded.
If the module is already
loaded, this works as no-op. If the module was not yet loaded,
it tries to load it.
If it succeeds loading the module, it returns
@@ -362,7 +387,7 @@ defmodule Code do
modules need to be loaded upfront or explicitly.
Therefore, this function is used to check if a module is loaded
before using it and allows one to react accordingly. For example, the `URI`
before using it and allows one to react accordingly. For example, the `URI`
module uses this function to check if a specific parser exists for a given
URI scheme.
@@ -387,17 +412,21 @@ defmodule Code do
end
@doc """
Similar to `ensure_loaded/1`, but returns a boolean in case
it could be ensured or not.
Ensure the given module is loaded.
Similar to `ensure_loaded/1`, but returns `true` if the module
is already loaded or was successfully loaded. Returns `false` otherwise.
"""
def ensure_loaded?(module) do
match?({ :module, ^module }, ensure_loaded(module))
end
@doc """
Ensures the given module is compiled and loaded. If the module
Ensure the given module is compiled and loaded.
If the module
is already loaded, it works as no-op. If the module was not
loaded yet, it checks if it needs to be compiled first and
loaded yet, it checks if it needs to be compiled first and
then tries to load it.
If it succeeds loading the module, it returns
@@ -425,8 +454,11 @@ defmodule Code do
end
@doc """
Similar to `ensure_compiled/1`, but returns a boolean in case
it could be ensured or not.
Ensure the given module is compiled and loaded.
Similar to `ensure_compiled/1`, but returns `true` if the module
is already loaded or was successfully loaded and compiled.
Returns `false` otherwise.
"""
def ensure_compiled?(module) do
match?({ :module, ^module }, ensure_compiled(module))
+8 -3
View File
@@ -31,6 +31,11 @@ defmodule Dict do
IO.puts "#{k}: #{v}"
end)
## Match
Dictionaries are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
"""
use Behaviour
@@ -420,7 +425,7 @@ defmodule Dict do
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included.
included.
Any non-member keys are ignored.
## Examples
@@ -458,8 +463,8 @@ defmodule Dict do
end
@doc """
Check if two dicts are equal. If the dicts are of different types, they are
first converted to lists.
Check if two dicts are equal using `===`. If the dicts are
of different types, they are first converted to lists.
## Examples
+18 -18
View File
@@ -2,7 +2,7 @@ defmodule Dict.Behaviour do
@moduledoc """
This module makes it easier to create your own `Dict` compliant
module, by providing default implementations for some required functions.
Usage:
defmodule MyDict do
@@ -13,16 +13,16 @@ defmodule Dict.Behaviour do
# override default implementations if needed
end
The client module must contain following functions: `size/1`, `fetch/2`,
The client module must contain following functions: `size/1`, `fetch/2`,
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
the Dict behaviour, so no extra functions are actually required.
Based on these functions, `Dict.Behaviour` generates default implementations
Based on these functions, `Dict.Behaviour` generates default implementations
for other functions such as `drop`, `take`, etc. All of the functions are
defined as overridable, so you can provide your own implementation if
needed.
If you implement `new/0` and `new/1` functions, you can also test your custom
If you implement `new/0` and `new/1` functions, you can also test your custom
module via `Dict` doctests:
defmodule MyDict do
@@ -38,11 +38,10 @@ defmodule Dict.Behaviour do
end
"""
defmacro __using__(_) do
quote do
@behaviour Dict
def get(dict, key, default // nil) do
case fetch(dict, key) do
{ :ok, value } -> value
@@ -58,29 +57,29 @@ defmodule Dict.Behaviour do
end
end
defoverridable fetch!: 2
def has_key?(dict, key) do
match? { :ok, _ }, fetch(dict, key)
end
defoverridable has_key?: 2
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
end
defoverridable put_new: 3
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
end
defoverridable drop: 2
def take(dict, keys) do
take(dict, keys, new)
end
defoverridable take: 2
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
@@ -88,20 +87,21 @@ defmodule Dict.Behaviour do
:error -> take(dict, keys, acc)
end
end
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
defoverridable to_list: 1
def keys(dict), do: reduce(dict, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
defoverridable keys: 1
def values(dict), do: reduce(dict, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
defoverridable values: 1
def equal?(dict1, dict2) do
import Kernel, except: [size: 1]
case size(dict1) == size(dict2) do
false -> false
true ->
true ->
try do
reduce(dict1, nil, fn({ k, v }, _acc) ->
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
@@ -113,7 +113,7 @@ defmodule Dict.Behaviour do
end
end
defoverridable equal?: 2
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
@@ -122,4 +122,4 @@ defmodule Dict.Behaviour do
defoverridable merge: 2, merge: 3
end
end
end
end
+128 -68
View File
@@ -15,8 +15,6 @@ defprotocol Enumerable do
element in the collection and cons'ing the element with the accumulated list.
"""
@only [List, Record, Function]
@doc """
This function performs the reducing operation on a given collection. It
returns the accumulated value of applying the given function `fun` on every
@@ -40,6 +38,7 @@ defprotocol Enumerable do
@doc """
The function is used to check if a value exists within the collection.
Membership should be tested with the match (`===`) operator.
"""
def member?(collection, value)
@@ -89,6 +88,10 @@ defmodule Enum do
@doc """
Checks if `value` exists within the `collection`.
Membership is tested with the match (`===`) operator although
enumerables like ranges may include floats inside the given
range.
## Examples
iex> Enum.member?(1..10, 5)
@@ -212,8 +215,6 @@ defmodule Enum do
Finds the element at the given index (zero-based).
Returns `default` if index is out of bounds.
Expects an ordered collection.
## Examples
iex> Enum.at([2, 4, 6], 0)
@@ -283,7 +284,11 @@ defmodule Enum do
@doc """
Drops the first `count` items from `collection`.
Expects an ordered collection.
A negative value `count` can be given, which means
the collection is enumerated once to retrieve the
proper index and the remaining of the calculation
is done from the end.
## Examples
@@ -309,13 +314,11 @@ defmodule Enum do
end
def drop(collection, count) when count < 0 do
{ list, count } = iterate_and_count(collection, count)
drop(list, count)
do_take_reverse(reverse(collection), abs(count), [])
end
@doc """
Drops items at the beginning of `collection` while `fun` returns `true`.
Expects an ordered collection.
## Examples
@@ -386,7 +389,9 @@ defmodule Enum do
Finds the element at the given index (zero-based).
Returns `{ :ok, element }` if found, otherwise `:error`.
Expects an ordered collection.
A negative index can be passed, which means the collection is
iterated once and the index is counted from the end (i.e.
`-1` fetches the last element).
## Examples
@@ -418,8 +423,7 @@ defmodule Enum do
end
def fetch(collection, n) when n < 0 do
{ list, count } = iterate_and_count_oob(collection, n)
if count >= 0, do: fetch(list, count), else: :error
fetch(reverse(collection), abs(n + 1))
end
@doc """
@@ -473,7 +477,7 @@ defmodule Enum do
## Examples
iex> Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2)
iex> Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2))
[4]
"""
@@ -556,8 +560,6 @@ defmodule Enum do
Similar to `find/3`, but returns the index (zero-based)
of the element instead of the element itself.
Expects an ordered collection.
## Examples
iex> Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
@@ -648,14 +650,9 @@ defmodule Enum do
"""
@spec join(t) :: String.t
@spec join(t, String.t | char_list) :: String.t | char_list
@spec join(t, String.t) :: String.t
def join(collection, joiner // "")
def join(collection, joiner) when is_list(joiner) do
IO.write "Enum.join/2 with a char list is deprecated, please use do an explicit conversion instead\n#{Exception.format_stacktrace}"
:unicode.characters_to_list join(collection, :unicode.characters_to_binary(joiner))
end
def join(collection, joiner) when is_binary(joiner) do
Enumerable.reduce(collection, "", fn
entry, "" -> to_string(entry)
@@ -708,14 +705,9 @@ defmodule Enum do
"""
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t | char_list, (element -> any)) :: String.t | char_list
@spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner // "", mapper)
def map_join(collection, joiner, mapper) when is_list(joiner) do
IO.write "Enum.map_join/3 with a char list is deprecated, please use do an explicit conversion instead\n#{Exception.format_stacktrace}"
:unicode.characters_to_list map_join(collection, :unicode.characters_to_binary(joiner), mapper)
end
def map_join(collection, joiner, mapper) when is_binary(joiner) do
Enumerable.reduce(collection, "", fn
entry, "" -> to_string(mapper, entry)
@@ -902,8 +894,8 @@ defmodule Enum do
end
def sort(collection) do
fun = &1 <= &2
Enumerable.reduce(collection, [], sort_reducer(&1, &2, fun)) |>
fun = &(&1 <= &2)
Enumerable.reduce(collection, [], &sort_reducer(&1, &2, fun)) |>
sort_terminator(fun)
end
@@ -922,7 +914,7 @@ defmodule Enum do
end
def sort(collection, fun) do
Enumerable.reduce(collection, [], sort_reducer(&1, &2, fun)) |>
Enumerable.reduce(collection, [], &sort_reducer(&1, &2, fun)) |>
sort_terminator(fun)
end
@@ -969,8 +961,7 @@ defmodule Enum do
end
def split(collection, count) when count < 0 do
{ list, count } = iterate_and_count(collection, count)
split(list, count)
do_split_reverse(reverse(collection), abs(count), [])
end
@doc """
@@ -998,8 +989,12 @@ defmodule Enum do
end
@doc """
Takes the first `count` items from the collection. Expects an ordered
collection.
Takes the first `count` items from the collection.
A negative value `count` can be passed, which means
the collection is enumerated once to retrieve the
proper index and the remaining of the calculation
is done from the end.
## Examples
@@ -1037,13 +1032,36 @@ defmodule Enum do
end
def take(collection, count) when count < 0 do
{ list, count } = iterate_and_count(collection, count)
take(list, count)
do_drop(reverse(collection), abs(count)) |> :lists.reverse
end
@doc """
Returns a collection of every `nth` items in the collection,
starting with the first.
## Examples
iex> Enum.take_every(1..10, 2)
[1, 3, 5, 7, 9]
"""
@spec take_every(t, integer) :: list
def take_every(_collection, 0), do: []
def take_every(collection, nth) do
res = Enumerable.reduce(collection, nil, fn
x, { acc, ^nth } -> { [x | acc], 1 }
_, { acc, count } -> { acc, count + 1 }
x, nil -> { [x], 1 }
end)
case res do
nil -> []
{ list, _ } -> :lists.reverse(list)
end
end
@doc """
Takes the items at the beginning of `collection` while `fun` returns `true`.
Expects an ordered collection.
## Examples
@@ -1169,12 +1187,6 @@ defmodule Enum do
reduce(collection, &Kernel.max(&1, &2))
end
@doc false
def max(coll, fun) do
IO.write "Enum.max/2 is deprecated, please use Enum.max_by/2 instead\n#{Exception.format_stacktrace}"
max_by(coll, fun)
end
@doc """
Returns the maximum value as calculated by the given function.
Raises `EmptyError` if the collection is empty.
@@ -1229,12 +1241,6 @@ defmodule Enum do
reduce(collection, &Kernel.min(&1, &2))
end
@doc false
def min(coll, fun) do
IO.write "Enum.min/2 is deprecated, please use Enum.min_by/2 instead\n#{Exception.format_stacktrace}"
min_by(coll, fun)
end
@doc """
Returns the minimum value as calculated by the given function.
Raises `EmptyError` if the collection is empty.
@@ -1320,7 +1326,7 @@ defmodule Enum do
"""
@spec chunks(t, non_neg_integer, non_neg_integer) :: [list]
@spec chunks(t, non_neg_integer, non_neg_integer, list) :: [list]
@spec chunks(t, non_neg_integer, non_neg_integer, list | nil) :: [list]
def chunks(coll, n, step, pad // nil) when n > 0 and step > 0 do
{ acc, buffer, i } =
Enumerable.reduce(coll, { [], [], 0 }, fn
@@ -1371,12 +1377,17 @@ defmodule Enum do
@doc """
Returns a subset list of the given collection. Dropping elements
until element position `start`, then taking `count` elements.
Expects an ordered collection.
## Examples
iex> Enum.slice(1..100, 5, 10)
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
"""
@spec slice(t, integer, integer) :: list
@spec slice(t, integer, non_neg_integer) :: list
def slice(coll, start, count) when start < 0 do
{ list, new_start } = iterate_and_count_oob(coll, start)
{ list, new_start } = iterate_and_count(coll, start)
if new_start >= 0, do: slice(list, new_start, count)
end
@@ -1408,6 +1419,41 @@ defmodule Enum do
:enum_slice -> []
end
@doc """
Returns a subset list of the given collection. Dropping elements
until element position `range.first`, then taking elements until element
position `range.last` (inclusive).
Positions are calculated by adding the number of items in the collection to
negative positions (so position -3 in a collection with count 5 becomes
position 2).
The first position (after adding count to negative positions) must be smaller
or equal to the last position.
## Examples
iex> Enum.slice(1..100, 5..10)
[6, 7, 8, 9, 10, 11]
"""
def slice(coll, first..last) when first >= 0 and last >= 0 do
# Simple case, which works on infinite collections
if last - first >= 0 do
slice(coll, first, last - first + 1)
end
end
def slice(coll, first..last) do
{ list, count } = iterate_and_count(coll, 0)
corr_first = if first >= 0, do: first, else: first + count
corr_last = if last >= 0, do: last, else: last + count
length = corr_last - corr_first + 1
if corr_first >= 0 and length > 0 do
slice(list, corr_first, length)
end
end
## Helpers
@compile { :inline, chunks_n: 5, chunks_step: 4, to_string: 2 }
@@ -1430,23 +1476,12 @@ defmodule Enum do
{ acc, buffer, i }
end
defp iterate_and_count_oob(collection, count) do
{ list, total_items } = do_iterate_and_count(collection)
{ list, total_items - abs(count) }
defp iterate_and_count(collection, count) when is_list(collection) do
{ collection, length(collection) - abs(count) }
end
defp iterate_and_count(collection, count) do
{ list, total_items } = do_iterate_and_count(collection)
{ list, Kernel.max(0, total_items - abs(count)) }
end
defp do_iterate_and_count(collection) when is_list(collection) do
{ collection, length(collection) }
end
defp do_iterate_and_count(collection) do
reducer = fn(x, acc) -> { x, acc + 1 } end
map_reduce(collection, 0, reducer)
map_reduce(collection, -abs(count), fn(x, acc) -> { x, acc + 1 } end)
end
defp to_string(mapper, entry) do
@@ -1686,6 +1721,18 @@ defmodule Enum do
{ :lists.reverse(acc), [] }
end
defp do_split_reverse([h|t], counter, acc) when counter > 0 do
do_split_reverse(t, counter - 1, [h|acc])
end
defp do_split_reverse(list, 0, acc) do
{ :lists.reverse(list), acc }
end
defp do_split_reverse([], _, acc) do
{ [], acc }
end
## split_while
defp do_split_while([h|t], fun, acc) do
@@ -1714,6 +1761,18 @@ defmodule Enum do
[]
end
defp do_take_reverse([h|t], counter, acc) when counter > 0 do
do_take_reverse(t, counter - 1, [h|acc])
end
defp do_take_reverse(_list, 0, acc) do
acc
end
defp do_take_reverse([], _, acc) do
acc
end
## take_while
defp do_take_while([h|t], fun) do
@@ -1796,8 +1855,9 @@ defimpl Enumerable, for: Function do
end
def member?(function, value) do
function.(false, fn(entry, _) ->
if entry === value, do: throw(:function_member?), else: false
function.(false, fn
^value, _ -> throw(:function_member?)
_, _ -> false
end)
catch
:function_member? -> true
+16 -2
View File
@@ -39,6 +39,12 @@ defexception CaseClauseError, [actual: nil] do
end
end
defexception TryClauseError, [actual: nil] do
def message(exception) do
"no try clause matching: #{inspect(exception.actual)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
@@ -146,6 +152,10 @@ defmodule Exception do
CaseClauseError[actual: actual]
end
def normalize({ :try_clause, actual }) do
TryClauseError[actual: actual]
end
def normalize(:undef) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
@@ -266,6 +276,8 @@ defmodule Exception do
"Foo.bar/1"
iex> Exception.format_mfa Foo, :bar, []
"Foo.bar()"
iex> Exception.format_mfa nil, :bar, []
"nil.bar()"
"""
def format_mfa(module, fun, arity) do
@@ -277,12 +289,14 @@ defmodule Exception do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect module}.#{fun}/#{arity}"
"#{format_module module}#{fun}/#{arity}"
end
end
defp format_module(mod), do: "#{inspect mod}."
@doc """
Formats the given file and line as shown in stacktraces.
If any of the values are nil, they are omitted.
+4 -11
View File
@@ -47,13 +47,6 @@ defexception File.CopyError, [reason: nil, action: "", source: nil, destination:
end
end
defexception File.IteratorError, reason: nil do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
"error during file iteration: #{formatted}"
end
end
defmodule File do
@moduledoc """
This module contains functions to manipulate files.
@@ -488,7 +481,7 @@ defmodule File do
case F.list_dir(src) do
{ :ok, files } ->
case mkdir(dest) do
success in [:ok, { :error, :eexist }] ->
success when success in [:ok, { :error, :eexist }] ->
Enum.reduce(files, [dest|acc], fn(x, acc) ->
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
end)
@@ -945,7 +938,7 @@ defmodule File do
"""
def ls(path // ".") do
case F.list_dir(path) do
{ :ok, file_list } -> { :ok, Enum.map(file_list, to_string(&1)) }
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
{ :error, _ } = error -> error
end
end
@@ -980,7 +973,7 @@ defmodule File do
of bytes given by `line_or_bytes`.
The returned stream will fail for the same reasons as `File.open!`.
Note that the file is opened only when streaming begins.
Note that the file is opened only and every time streaming begins.
"""
def stream!(file, mode // [], line_or_bytes // :line) do
fn(acc, fun) ->
@@ -999,7 +992,7 @@ defmodule File do
of bytes given by `line_or_bytes`.
The returned stream will fail for the same reasons as `File.open!`.
Note that the file is opened only when streaming begins.
Note that the file is opened only and every time streaming begins.
"""
def binstream!(file, mode // [], line_or_bytes // :line) do
fn(fun, acc) ->
+83
View File
@@ -0,0 +1,83 @@
defmodule Float do
@moduledoc """
Functions for working with floating point numbers.
"""
@doc """
Parses a binary into a float.
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
Otherwise `:error`.
## Examples
iex> Float.parse("34")
{34.0,""}
iex> Float.parse("34.25")
{34.25,""}
iex> Float.parse("56.5xyz")
{56.5,"xyz"}
iex> Float.parse("pi")
:error
"""
@spec parse(binary) :: { float, binary } | :error
def parse(binary) when is_binary(binary) do
case Integer.parse binary do
:error -> :error
{ integer_part, after_integer } -> parse 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)
end
defp parse(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
end
defp parse(<< ?e, after_e :: binary >>, float, decimal, int) do
case Integer.parse after_e do
:error ->
# Note we rebuild the binary here instead of breaking it apart at
# the function clause because the current approach copies a binary
# just on this branch. If we broke it apart in the function clause,
# the copy would happen when calling Integer.parse/1.
{ floatify(int, float, decimal), << ?e, after_e :: binary >> }
{ exponential, after_exponential } ->
{ floatify(int, float, decimal, exponential), after_exponential }
end
end
defp parse(bitstring, float, decimal, int) do
{ floatify(int, float, decimal), bitstring }
end
defp floatify(int, float, decimal, exponential // 0) do
multiplier = if int < 0, do: -1.0, else: 1.0
# Try to ensure the minimum amount of rounding errors
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
# Try avoiding stuff like this:
# iex(1)> 0.0001 * 75
# 0.007500000000000001
# Due to IEEE 754 floating point standard
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
final_decimal_places = decimal - exponential
if final_decimal_places > 0 do
decimal_power_round = :math.pow(10, final_decimal_places)
trunc(result * decimal_power_round) / decimal_power_round
else
result
end
end
end
+3 -3
View File
@@ -60,7 +60,7 @@ defmodule GenFSM.Behaviour do
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, state_data.update_coins(&1 + 1) }
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
@@ -84,7 +84,7 @@ defmodule GenFSM.Behaviour do
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, state_data.update_coins(&1 + 1) }
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
@@ -186,7 +186,7 @@ defmodule GenFSM.Behaviour do
@doc false
def handle_sync_event(_event, from, state_name, state_data) do
{ :reply, :default_implementation, state_name, state_data }
{ :reply, :ok, state_name, state_data }
end
@doc false
+2 -8
View File
@@ -107,12 +107,6 @@ defmodule HashDict do
dict
end
@doc false
def update(dict, key, fun) when is_function(fun, 1) do
IO.write "HashDict.update/3 is deprecated, please use HashDict.update!/3 instead\n#{Exception.format_stacktrace}"
update!(dict, key, fun)
end
def update!(dict, key, fun) when is_function(fun, 1) do
case dict_put(dict, key, { :update, nil, fun }) do
{ dict, 0 } ->
@@ -159,13 +153,13 @@ defmodule HashDict do
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
dict_fold dict1, dict2, fn [k|v1], acc ->
update(acc, k, v1, callback.(k, v1, &1))
update(acc, k, v1, &callback.(k, v1, &1))
end
end
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
dict_fold dict2, dict1, fn [k|v2], acc ->
update(acc, k, v2, callback.(k, &1, v2))
update(acc, k, v2, &callback.(k, &1, v2))
end
end
+1 -8
View File
@@ -103,7 +103,7 @@ defmodule HashSet do
end
def to_list(set) do
set_fold(set, [], [&1|&2]) |> :lists.reverse
set_fold(set, [], &[&1|&2]) |> :lists.reverse
end
def put(set, member) do
@@ -476,13 +476,6 @@ defmodule HashSet do
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
@doc false
def inspect_depth(trie(depth: d)), do: d
@doc false
def inspect_contract(trie(contract_on: c)), do: c
@doc false
def inspect_expand(trie(expand_on: e)), do: e
end
defimpl Enumerable, for: HashSet do
+24 -15
View File
@@ -267,9 +267,9 @@ defimpl Inspect, for: List do
:io_lib.printable_list(thing) ->
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
keyword?(thing) && not opts.raw ->
surround_many("[", thing, "]", opts.limit, keyword(&1, opts))
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
true ->
surround_many("[", thing, "]", opts.limit, Kernel.inspect(&1, opts))
surround_many("[", thing, "]", opts.limit, &Kernel.inspect(&1, opts))
end
end
@@ -317,7 +317,7 @@ defimpl Inspect, for: Tuple do
def inspect(tuple, opts) do
unless opts.raw do
record_inspect(tuple, opts)
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, Kernel.inspect(&1, opts))
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &Kernel.inspect(&1, opts))
end
## Helpers
@@ -331,7 +331,7 @@ defimpl Inspect, for: Tuple do
else
surround_record(name, fields, tail, opts)
end
end || surround_many("{", [name|tail], "}", opts.limit, Kernel.inspect(&1, opts))
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
end
defp record_fields(name) do
@@ -351,22 +351,35 @@ defimpl Inspect, for: Tuple do
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, keyword(&1, opts))
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, &keyword(&1, opts))
)
end
defp keyword({ k, v }, opts) do
concat(
atom_to_binary(k, :utf8) <> ": ",
atom_to_binary(k) <> ": ",
Kernel.inspect(v, opts)
)
end
end
defimpl Inspect, for: Number do
defimpl Inspect, for: Integer do
@doc """
Represents the number as a string.
Represents the integer as a string.
## Examples
iex> inspect(1)
"1"
"""
def inspect(thing, _opts) do
integer_to_binary(thing)
end
end
defimpl Inspect, for: Float do
@doc """
Floats are represented using the shortened, correctly rounded string
that converts to float when read back with `binary_to_float/1`. This
is done via the Erlang implementation of _Printing Floating-Point
@@ -375,14 +388,10 @@ defimpl Inspect, for: Number do
## Examples
iex> inspect(1)
"1"
iex> inspect(1.0)
"1.0"
"""
def inspect(thing, _opts) when is_integer(thing) do
integer_to_binary(thing)
end
def inspect(thing, _opts) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
@@ -446,7 +455,7 @@ end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> iolist_to_binary(pid_to_list(pid))
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
end
end
+44 -10
View File
@@ -82,6 +82,28 @@ defmodule Inspect.Algebra do
defrecordp :doc_break, str: " " :: binary
defrecordp :doc_group, doc: :doc_nil :: t
defmacrop is_doc(doc) do
if __CALLER__.in_guard? do
do_is_doc(doc)
else
var = quote do: doc
quote do
unquote(var) = unquote(doc)
unquote(do_is_doc(var))
end
end
end
defp do_is_doc(doc) do
quote do
unquote(doc) |> is_binary or
unquote(doc) |> is_integer or
unquote(doc) == :doc_nil or
(unquote(doc) |> is_tuple and
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
end
end
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
@@ -107,13 +129,17 @@ defmodule Inspect.Algebra do
"""
@spec concat(t, t) :: doc_cons_t
def concat(x, y), do: doc_cons(left: x, right: y)
def concat(x, y) when is_doc(x) and is_doc(y) do
doc_cons(left: x, right: y)
end
@doc """
Concatenates a list of documents.
"""
@spec concat([t]) :: doc_cons_t
def concat(docs), do: folddoc(docs, concat(&1, &2))
def concat(docs) do
folddoc(docs, &concat(&1, &2))
end
@doc """
Nests document entity `x` positions deep. Nesting will be
@@ -127,8 +153,13 @@ defmodule Inspect.Algebra do
"""
@spec nest(t, non_neg_integer) :: doc_nest_t
def nest(x, 0), do: x
def nest(x, i) when is_integer(i), do: doc_nest(indent: i, doc: x)
def nest(x, 0) when is_doc(x) do
x
end
def nest(x, i) when is_doc(x) and is_integer(i) do
doc_nest(indent: i, doc: x)
end
@doc %S"""
Document entity representing a break. This break can
@@ -198,7 +229,9 @@ defmodule Inspect.Algebra do
"""
@spec group(t) :: doc_group_t
def group(d), do: doc_group(doc: d)
def group(d) when is_doc(d) do
doc_group(doc: d)
end
@doc """
Inserts a mandatory single space between two document entities.
@@ -240,7 +273,7 @@ defmodule Inspect.Algebra do
"A!B"
"""
@spec folddoc([any], ((any, [t]) -> t)) :: t
@spec folddoc([t], ((t, t) -> t)) :: t
def folddoc([], _), do: empty
def folddoc([doc], _), do: doc
def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))
@@ -273,11 +306,11 @@ defmodule Inspect.Algebra do
## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, integer_to_binary(&1))
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 5)
"[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, integer_to_binary(&1))
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"
@@ -357,7 +390,7 @@ defmodule Inspect.Algebra do
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
@doc false
@spec format(integer, integer, [{ integer, mode, t }]) :: atom | tuple
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: atom | tuple
def format(_, _, []), do: :s_nil
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
@@ -367,7 +400,8 @@ defmodule Inspect.Algebra do
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
k = k + byte_size(s)
if fits?(w - k, t) do
if w == :infinity or fits?(w - k, t) do
s_text(str: s, sdoc: format(w, k, t))
else
s_line(indent: i, sdoc: format(w, i, t))
+55 -4
View File
@@ -3,17 +3,68 @@ defmodule Integer do
Functions for working with integers.
"""
import Bitwise
@doc """
Returns true if `n` is an odd number, otherwise false.
Determines if an integer is odd.
Returns `true` if `n` is an odd number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
"""
defmacro odd?(n) do
quote do: rem(unquote(n), 2) != 0
quote do: (unquote(n) &&& 1) == 1
end
@doc """
Returns true if `n` is an even number, otherwise false.
Determines if an integer is even.
Returns `true` if `n` is an even number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
"""
defmacro even?(n) do
quote do: rem(unquote(n), 2) == 0
quote do: (unquote(n) &&& 1) == 0
end
@doc """
Converts a binary to an integer.
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
Otherwise `:error`.
## Examples
iex> Integer.parse("34")
{34,""}
iex> Integer.parse("34.5")
{34,".5"}
iex> Integer.parse("three")
:error
"""
@spec parse(binary) :: { integer, binary } | :error
def parse(<< ?-, bin :: binary >>) do
case do_parse(bin) do
:error -> :error
{ number, remainder } -> { -number, remainder }
end
end
def parse(<< ?+, bin :: binary >>) do
do_parse(bin)
end
def parse(bin) when is_binary(bin) do
do_parse(bin)
end
defp do_parse(<< char, bin :: binary >>) when char in ?0..?9, do: do_parse(bin, char - ?0)
defp do_parse(_), do: :error
defp do_parse(<< char, rest :: binary >>, acc) when char in ?0..?9 do
do_parse rest, 10 * acc + (char - ?0)
end
defp do_parse(bitstring, acc) do
{ acc, bitstring }
end
end
+56 -43
View File
@@ -1,28 +1,32 @@
defexception IO.StreamError, reason: nil do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
"error during streaming: #{formatted}"
end
end
defmodule IO do
@moduledoc """
Module responsible for doing IO. Many functions in this
module expects an IO device and an io data encoded in UTF-8.
Functions handling IO.
Many functions in this module expects an IO device as argument.
An IO device must be a pid or an atom representing a process.
For convenience, Elixir provides `:stdio` and `:stderr` as
shortcuts to Erlang's `:standard_io` and `:standard_error`.
An io data can be:
The majority of the functions expect data encoded in UTF-8
and will do a conversion to string, via the `String.Chars`
protocol (as shown in typespecs).
* A list of integers representing a string. Any unicode
character must be represented with one entry in the list,
this entry being an integer with the codepoint value;
* A binary in which unicode characters are represented
with many bytes (Elixir's default representation);
* A list of binaries or a list of char lists (as described above);
* If none of the above, `to_string` is invoked on the
given argument;
The functions starting with `bin*` expects iodata as arguments,
i.e. iolists or binaries with no particular encoding.
"""
@type device :: atom | pid
@type chardata :: char_list | String.Chars.t
@type nodata :: { :error, term } | :eof
import :erlang, only: [group_leader: 0]
defmacrop is_iolist(data) do
@@ -43,6 +47,7 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
def read(device // group_leader, chars_or_line)
def read(device, :line) do
@@ -65,6 +70,7 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
def binread(device // group_leader, chars_or_line)
def binread(device, :line) do
@@ -98,8 +104,9 @@ defmodule IO do
#=> "error"
"""
def write(device // group_leader(), item) when is_iolist(item) do
:io.put_chars map_dev(device), item
@spec write(device, chardata) :: :ok
def write(device // group_leader(), item) do
:io.put_chars map_dev(device), to_chardata(item)
end
@doc """
@@ -108,6 +115,7 @@ defmodule IO do
Check `write/2` for more information.
"""
@spec binwrite(device, iodata) :: :ok | { :error, term }
def binwrite(device // group_leader(), item) when is_iolist(item) do
:file.write map_dev(device), item
end
@@ -117,9 +125,10 @@ defmodule IO do
but adds a newline at the end. The argument is expected
to be a chardata.
"""
def puts(device // group_leader(), item) when is_iolist(item) do
@spec puts(device, chardata) :: :ok
def puts(device // group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [item, ?\n]
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@doc """
@@ -135,6 +144,7 @@ defmodule IO do
IO.inspect Process.list
"""
@spec inspect(term, Keyword.t) :: term
def inspect(item, opts // []) do
inspect group_leader(), item, opts
end
@@ -142,6 +152,7 @@ defmodule IO do
@doc """
Inspects the item with options using the given device.
"""
@spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
@@ -160,7 +171,7 @@ defmodule IO do
Gets a number of bytes from the io device. If the
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
It returns:
* `data` - The input characters.
@@ -171,6 +182,8 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec getn(chardata, pos_integer) :: chardata | nodata
@spec getn(device, chardata) :: chardata | nodata
def getn(prompt, count // 1)
def getn(prompt, count) when is_integer(count) do
@@ -187,8 +200,9 @@ defmodule IO do
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
"""
@spec getn(device, chardata, pos_integer) :: chardata | nodata
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), prompt, count)
:io.get_chars(map_dev(device), to_chardata(prompt), count)
end
@doc """
@@ -203,50 +217,46 @@ defmodule IO do
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec gets(device, chardata) :: chardata | nodata
def gets(device // group_leader(), prompt) do
:io.get_line(map_dev(device), prompt)
:io.get_line(map_dev(device), to_chardata(prompt))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line if :line is given or by a given
iterated line by line if `:line` is given or by a given
number of codepoints.
This reads the io as utf-8. Check out
This reads the IO as utf-8. Check out
`IO.binstream/2` to handle the IO as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
## Examples
Here is an example on how we mimic an echo server
from the command line:
Enum.each IO.stream(:stdio, :line), IO.write(&1)
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
"""
def stream(device, line_or_bytes) do
stream(map_dev(device), line_or_bytes, &1, &2)
end
@doc false
def stream(device) do
IO.write "IO.stream(device) is deprecated, please use IO.stream(device, :line) instead\n#{Exception.format_stacktrace}"
stream(device, :line)
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
fn(acc, f) -> stream(map_dev(device), line_or_codepoints, acc, f) end
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line.
iterated line by line or by a number of bytes. This
reads the IO as a raw binary.
This reads the io as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
binstream(map_dev(device), line_or_bytes, &1, &2)
end
@doc false
def binstream(device) do
IO.write "IO.binstream(device) is deprecated, please use IO.binstream(device, :line) instead\n#{Exception.format_stacktrace}"
binstream(device, :line)
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
end
@doc false
@@ -255,7 +265,7 @@ defmodule IO do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
raise IO.StreamError, reason: reason
data ->
stream(device, what, fun.(data, acc), fun)
end
@@ -267,7 +277,7 @@ defmodule IO do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
raise IO.StreamError, reason: reason
data ->
binstream(device, what, fun.(data, acc), fun)
end
@@ -277,4 +287,7 @@ defmodule IO do
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other), do: other
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
end
+18 -17
View File
@@ -16,9 +16,9 @@ end
defmodule IO.ANSI do
@moduledoc """
This module provides functionality to render ANSI escape sequences
Functionality to render ANSI escape sequences
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
in the text used to control formatting, color, and other output options
in text used to control formatting, color, and other output options
on video text terminals.
"""
@@ -28,7 +28,7 @@ defmodule IO.ANSI do
Checks whether the default I/O device is a terminal or a file.
Used to identify whether printing ANSI escape sequences will likely
be printed as intended.
be displayed as intended.
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
@@ -144,18 +144,19 @@ defmodule IO.ANSI do
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
multiple sequences).
It will also append a %{reset} to the string. If you don't want this
behaviour, use `escape_fragment/1` and `escape_fragment/2`.
It will also append a `%{reset}` to the string. If you don't want this
behaviour, use `escape_fragment/2`.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When false, no ANSI codes will emitted.
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/0` function)
of handling these sequences (using `terminal?/1` function)
## Example
## Examples
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes\e[0m"
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes\e[0m"
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit // terminal?) do
@@ -174,16 +175,16 @@ defmodule IO.ANSI do
multiple sequences).
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When false, no ANSI codes will emitted.
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/0` function)
of handling these sequences (using `terminal?/1` function)
## Example
## Examples
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
+326 -321
View File
File diff suppressed because it is too large Load Diff
+46 -42
View File
@@ -15,12 +15,12 @@ defmodule Kernel.CLI do
System.argv(argv)
run fn ->
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
command_errors = lc { :error, msg } inlist command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
if errors != [] do
Enum.each(errors, IO.puts(:stderr, &1))
Enum.each(errors, &IO.puts(:stderr, &1))
System.halt(1)
end
end, config.halt
@@ -77,7 +77,7 @@ defmodule Kernel.CLI do
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
{ [h|hs], _ } when h == hd(list) ->
new_config = config.update_errors ["#{h} : Unknown option" | &1]
new_config = config.update_errors &["#{h} : Unknown option" | &1]
callback.(hs, new_config)
{ new_list, new_config } ->
callback.(new_list, new_config)
@@ -104,6 +104,10 @@ defmodule Kernel.CLI do
prune_stacktrace(t)
end
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
[]
end
defp prune_stacktrace([h|t]) do
[h|prune_stacktrace(t)]
end
@@ -120,17 +124,17 @@ defmodule Kernel.CLI do
end
defp process_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(["-pz", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
process_shared t, config
end
defp process_shared(["--app", h|t], config) do
process_shared t, config.update_commands [{:app, h}|&1]
process_shared t, config.update_commands &[{:app, h}|&1]
end
defp process_shared(["--no-halt"|t], config) do
@@ -138,15 +142,15 @@ defmodule Kernel.CLI do
end
defp process_shared(["-e", h|t], config) do
process_shared t, config.update_commands [{:eval, h}|&1]
process_shared t, config.update_commands &[{:eval, h}|&1]
end
defp process_shared(["-r", h|t], config) do
process_shared t, config.update_commands [{:require, h}|&1]
process_shared t, config.update_commands &[{:require, h}|&1]
end
defp process_shared(["-pr", h|t], config) do
process_shared t, config.update_commands [{:parallel_require, h}|&1]
process_shared t, config.update_commands &[{:parallel_require, h}|&1]
end
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
@@ -176,15 +180,15 @@ defmodule Kernel.CLI do
end
defp process_argv(["-S", h|t], config) do
{ config.update_commands([{:script, h}|&1]), t }
{ config.update_commands(&[{:script, h}|&1]), t }
end
defp process_argv([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, process_argv(&1, &2)
shared_option? list, config, &process_argv(&1, &2)
_ ->
{ config.update_commands([{:file, h}|&1]), t }
{ config.update_commands(&[{:file, h}|&1]), t }
end
end
@@ -203,19 +207,19 @@ defmodule Kernel.CLI do
end
defp process_compiler(["--no-docs"|t], config) do
process_compiler t, config.update_compiler_options([{:docs, false}|&1])
process_compiler t, config.update_compiler_options(&[{:docs, false}|&1])
end
defp process_compiler(["--no-debug-info"|t], config) do
process_compiler t, config.update_compiler_options([{:debug_info, false}|&1])
process_compiler t, config.update_compiler_options(&[{:debug_info, false}|&1])
end
defp process_compiler(["--ignore-module-conflict"|t], config) do
process_compiler t, config.update_compiler_options([{:ignore_module_conflict, true}|&1])
process_compiler t, config.update_compiler_options(&[{:ignore_module_conflict, true}|&1])
end
defp process_compiler(["--warnings-as-errors"|t], config) do
process_compiler t, config.update_compiler_options([{:warnings_as_errors, true}|&1])
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
end
defp process_compiler(["--verbose"|t], config) do
@@ -225,15 +229,15 @@ defmodule Kernel.CLI do
defp process_compiler([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, process_compiler(&1, &2)
shared_option? list, config, &process_compiler(&1, &2)
_ ->
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.update_compile [pattern|&1]
process_compiler t, config.update_compile &[pattern|&1]
end
end
defp process_compiler([], config) do
{ config.update_commands([{:compile, config.compile}|&1]), [] }
{ config.update_commands(&[{:compile, config.compile}|&1]), [] }
end
# Process iex options
@@ -253,15 +257,15 @@ defmodule Kernel.CLI do
end
defp process_iex(["-S", h|t], config) do
{ config.update_commands([{:script, h}|&1]), t }
{ config.update_commands(&[{:script, h}|&1]), t }
end
defp process_iex([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, process_iex(&1, &2)
shared_option? list, config, &process_iex(&1, &2)
_ ->
{ config.update_commands([{:file, h}|&1]), t }
{ config.update_commands(&[{:file, h}|&1]), t }
end
end
@@ -273,16 +277,14 @@ defmodule Kernel.CLI do
defp process_command({:cookie, h}, _config) do
if Node.alive? do
Node.set_cookie(binary_to_atom(h))
:ok
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
else
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
end
end
defp process_command({:eval, expr}, _config) when is_binary(expr) do
Code.eval_string(expr, [])
:ok
wrapper fn -> Code.eval_string(expr, []) end
end
defp process_command({:app, app}, _config) when is_binary(app) do
@@ -296,8 +298,7 @@ defmodule Kernel.CLI do
defp process_command({:script, file}, _config) when is_binary(file) do
if exec = find_elixir_executable(file) do
Code.require_file(exec)
:ok
wrapper fn -> Code.require_file(exec) end
else
{ :error, "-S : Could not find executable #{file}" }
end
@@ -305,8 +306,7 @@ defmodule Kernel.CLI do
defp process_command({:file, file}, _config) when is_binary(file) do
if :filelib.is_regular(file) do
Code.require_file(file)
:ok
wrapper fn -> Code.require_file(file) end
else
{ :error, "No file named #{file}" }
end
@@ -315,11 +315,10 @@ defmodule Kernel.CLI do
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, :filelib.is_regular(&1)
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
Enum.map files, Code.require_file(&1)
:ok
wrapper fn -> Enum.map files, &Code.require_file(&1) end
else
{ :error, "-r : No files matched pattern #{pattern}" }
end
@@ -328,11 +327,10 @@ defmodule Kernel.CLI do
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, :filelib.is_regular(&1)
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
Kernel.ParallelRequire.files(files)
:ok
wrapper fn -> Kernel.ParallelRequire.files(files) end
else
{ :error, "-pr : No files matched pattern #{pattern}" }
end
@@ -341,20 +339,26 @@ 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.map patterns, &Path.wildcard(&1)
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, :filelib.is_regular(&1)
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
:ok
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
else
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
end
end
defp wrapper(fun) do
fun.()
:ok
end
defp find_elixir_executable(file) do
if exec = System.find_executable(file) do
# If we are on Windows, the executable is going to be
+175
View File
@@ -0,0 +1,175 @@
# This is a module Elixir responsible for tracking
# the usage of aliases, imports and requires in the Elixir scope.
#
# The implementation simply stores dispatch information in an
# ETS table and then consults this table once compilation is done.
#
# Note that since this is required for bootstrap, we can't use
# any of the `GenServer.Behaviour` conveniences.
defmodule Kernel.LexicalTracker do
@timeout 30_000
@behavior :gen_server
@import 2
@alias 3
@doc """
Returns all remotes linked to in this lexical scope.
"""
def remotes(arg) do
# If the module is compiled from a function, its lexical
# scope may be long gone, so it has no associated PID.
if pid = to_pid(arg) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
else
[]
end
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__lexical_tracker)
val
end
# Internal API
# Starts the tracker and returns its pid.
@doc false
def start_link do
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
pid
end
@doc false
def stop(pid) do
:gen_server.cast(pid, :stop)
end
@doc false
def add_import(pid, module, line, warn) do
:gen_server.cast(pid, { :add_import, module, line, warn })
end
@doc false
def add_alias(pid, module, line, warn) do
:gen_server.cast(pid, { :add_alias, module, line, warn })
end
@doc false
def remote_dispatch(pid, module) do
:gen_server.cast(pid, { :remote_dispatch, module })
end
@doc false
def import_dispatch(pid, module) do
:gen_server.cast(pid, { :import_dispatch, module })
end
@doc false
def alias_dispatch(pid, module) do
:gen_server.cast(pid, { :alias_dispatch, module })
end
@doc false
def collect_unused_imports(pid) do
unused(pid, @import)
end
@doc false
def collect_unused_aliases(pid) do
unused(pid, @alias)
end
defp unused(pid, pos) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.foldl(fn
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
[{ module, :erlang.element(pos, tuple) }|acc]
_, acc ->
acc
end, [], ets) |> Enum.sort
end
# Callbacks
def init([]) do
{ :ok, :ets.new(:lexical, [:protected]) }
end
def handle_call(:ets, _from, d) do
{ :reply, d, d }
end
def handle_call(_request, _from, d) do
{ :noreply, d }
end
def handle_cast({ :remote_dispatch, module }, d) do
add_module(d, module)
{ :noreply, d }
end
def handle_cast({ :import_dispatch, module }, d) do
add_dispatch(d, module, @import)
{ :noreply, d }
end
def handle_cast({ :alias_dispatch, module }, d) do
add_dispatch(d, module, @alias)
{ :noreply, d }
end
def handle_cast({ :add_import, module, line, warn }, d) do
add_directive(d, module, line, warn, @import)
{ :noreply, d }
end
def handle_cast({ :add_alias, module, line, warn }, d) do
add_directive(d, module, line, warn, @alias)
{ :noreply, d }
end
def handle_cast(:stop, d) do
{ :stop, :normal, d }
end
def handle_cast(_msg, d) do
{ :noreply, d }
end
def handle_info(_msg, d) do
{ :noreply, d }
end
def terminate(_reason, _d) do
:ok
end
def code_change(_old, d, _extra) do
{ :ok, d }
end
# Callbacks helpers
# In the table we keep imports and aliases.
# If the value is false, it was not imported/aliased
# If the value is true, it was imported/aliased
# If the value is a line, it was imported/aliased and has a pending warning
defp add_module(d, module) do
:ets.insert_new(d, { module, false, false })
end
defp add_dispatch(d, module, pos) do
:ets.update_element(d, module, { pos, true })
end
defp add_directive(d, module, line, warn, pos) do
add_module(d, module)
marker = if warn, do: line, else: true
:ets.update_element(d, module, { pos, marker })
end
end
+7 -20
View File
@@ -20,13 +20,8 @@ defmodule Kernel.ParallelCompiler do
* `:each_file` - for each file compiled, invokes the callback passing the file
* `:each_module` - for each module compiled, invokes the callback
passing the file, module and the module bytecode
* `:each_waiting` - every time a module waits for another module to be compiled,
this callback is invoked with the module we are waiting
The compiler doesn't care about the return values of the callbacks except
by `each_waiting`, which must return nil or a file as a hint where the source
could be found.
The compiler doesn't care about the return values of the callbacks.
Returns the modules generated by each compiled file.
"""
def files(files, callbacks // [])
@@ -47,14 +42,15 @@ defmodule Kernel.ParallelCompiler do
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
:elixir_code_server.cast(:reset_warnings)
compiler_pid = self()
:elixir_code_server.cast({ :reset_warnings, compiler_pid })
schedulers = max(:erlang.system_info(:schedulers_online), 2)
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with Kernel.CompilationError
case :elixir_code_server.call(:compilation_status) do
# compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call({ :compilation_status, compiler_pid }) do
:ok -> result
:error -> raise CompileError, [], []
end
@@ -118,27 +114,18 @@ defmodule Kernel.ParallelCompiler do
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, child, 0)
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :module_available, child, file, module, binary } ->
{ :module_available, child, ref, file, module, binary } ->
if callback = Keyword.get(callbacks, :each_module) do
callback.(file, module, binary)
end
# Release the module loader which is waiting for an ack
child <- { self, :ack }
child <- { ref, :ack }
new_waiting = release_waiting_processes(module, waiting)
new_result = [module|result]
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
{ :waiting, child, on } ->
# If one of the callbacks is each_waiting and we haven't seen
# the hinted file before, add it to the list to be processed.
if (callback = Keyword.get(callbacks, :each_waiting)) &&
(hint = callback.(on)) &&
not(hint in original) do
files = [hint|files]
original = [hint|original]
end
new_waiting = :orddict.store(child, on, waiting)
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
{ :failure, child, kind, reason, stacktrace } ->
+2 -2
View File
@@ -75,7 +75,7 @@ defmodule Kernel.RecordRewriter do
if :lists.member({ function, length(args) + 1 }, optimizable) do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, field == &1) do
if index = Enum.find_index(fields, &(field == &1)) do
optimize_record_accessor_call(line, res, kind, field, index, left, args)
end
nil ->
@@ -352,7 +352,7 @@ defmodule Kernel.RecordRewriter do
end
defp extract_vars({ :tuple, _, args }, vars) do
[Enum.map(args, extract_vars(&1, []))|vars]
[Enum.map(args, &extract_vars(&1, []))|vars]
end
defp extract_vars(_, vars) do
+353 -128
View File
@@ -9,8 +9,8 @@ defmodule Kernel.SpecialForms do
to define data structures, respectively tuples, lists and binaries.
This module also documents Elixir's pseudo variables (`__MODULE__`,
`__FILE__`, `__ENV__` and `__CALLER__`). Pseudo variables return
information about Elixir's compilation environment and can only
`__FILE__`, `__ENV__`, `__DIR__` and `__CALLER__`). Pseudo variables
return information about Elixir's compilation environment and can only
be read, never assigned to.
Finally, it also documents 3 special forms (`__block__`,
@@ -22,25 +22,20 @@ defmodule Kernel.SpecialForms do
@doc """
Defines a new tuple.
Only two item tuples are considered literals in Elixir.
Therefore all other tuples are represented in the AST
as a call to the special form `:{}`.
## Examples
iex> { 1, 2, 3 }
{ 1, 2, 3 }
iex> quote do: { 1, 2, 3 }
{ :{}, [], [1,2,3] }
"""
defmacro :{}.(args)
@doc """
Defines a new list.
## Examples
iex> [ 1, 2, 3 ]
[ 1, 2, 3 ]
"""
defmacro :[].(args)
@doc """
Defines a new bitstring.
@@ -80,12 +75,12 @@ defmodule Kernel.SpecialForms do
<<102, rest :: binary>>
The type can be integer, float, binary, bytes, bitstring,
bits, utf8, utf16 or utf32, e.g.:
The type can be integer, float, bitstring/bits, binary/bytes,
utf8, utf16 or utf32, e.g.:
<<102 :: float, rest :: binary>>
Integer can be any arbitrary precision integer. A float is an
An integer can be any arbitrary precision integer. A float is an
IEEE 754 binary32 or binary64 floating point number. A bitstring
is an arbitrary series of bits. A binary is a special case of
bitstring that has a total size divisible by 8.
@@ -179,7 +174,120 @@ defmodule Kernel.SpecialForms do
defmacro :<<>>.(args)
@doc """
`alias` is used to setup atom aliases, often useful with modules names.
Defines a remote call or an alias.
The dot (`.`) in Elixir can be used for remote calls:
iex> String.downcase("FOO")
"foo"
In this example above, we have used `.` to invoke `downcase` in the
`String` alias, passing "FOO" as argument. We can also use the dot
for creating aliases:
iex> Hello.World
Hello.World
This time, we have joined two aliases, defining the final alias
`Hello.World`.
## Syntax
The right side of `.` may be a word starting in upcase, which represents
an alias, a word starting with lowercase or underscore, any valid language
operator or any name wrapped in single- or double-quotes. Those are all valid
examples:
iex> Kernel.Sample
Kernel.Sample
iex> Kernel.length([1,2,3])
3
iex> Kernel.+(1, 2)
3
iex> Kernel."length"([1,2,3])
3
iex> Kernel.'+'(1, 2)
3
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
This choice was done so every time single- or double-quotes are used, we have
a remote call irregardless of the quote contents. This decision is also reflected
in the quoted expressions discussed below.
## Runtime (dynamic) behaviour
The result returned by `.` is always specified by the right-side:
iex> x = String
iex> x.downcase("FOO")
"foo"
iex> x.Sample
String.Sample
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
at runtime via `apply/3` and `Module.concat/2`:
iex> apply(Kernel, :+, [1,2])
3
iex> Module.concat(Kernel, Sample)
Kernel.Sample
## Quoted expression
When `.` is used, the quoted expression may take two distinct
forms. When the right side starts with a lowercase letter (or
underscore):
iex> quote do: String.downcase("FOO")
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
Notice we have an inner tuple, containing the atom `:.` representing
the dot as first element:
{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}
This tuple follows the general quoted expression structure in Elixir,
with the name as first argument, some keyword list as metadata as second,
and the number of arguments as third. In this case, the arguments is the
alias `String` and the atom `:downcase`. The second argument is **always**
an atom:
iex> quote do: String."downcase"("FOO")
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
The tuple containing `:.` is wrapped in another tuple, which actually
represents the function call, and has `"FOO"` as argument.
When the right side is an alias (i.e. starts with uppercase), we get instead:
iex> quote do: Hello.World
{:__aliases__, [alias: false], [:Hello, :World]}
We got into more details about aliases in the `__aliases__` special form
documentation.
## Unquoting
We can also use unquote to generate a remote call in a quoted expression:
iex> x = :downcase
iex> quote do: String.unquote(x)("FOO")
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
Similar to `Kernel."HELLO"`, `unquote(x)` will always generate a remote call,
independent of the value of `x`. To generate an alias via the quoted expression,
one needs to rely on `Module.concat/2`:
iex> x = Sample
iex> quote do: Module.concat(String, unquote(x))
{{:., [], [{:__aliases__, [alias: false], [:Module]}, :concat]}, [],
[{:__aliases__, [alias: false], [:String]}, Sample]}
"""
defmacro (:.).(left, right)
@doc """
`alias` is used to setup aliases, often useful with modules names.
## Examples
@@ -213,12 +321,23 @@ defmodule Kernel.SpecialForms do
`import`, `require` and `alias` are called directives and all
have lexical scope. This means you can set up aliases inside
specific functions and it won't affect the overall scope.
## Warnings
If you alias a module and you don't use the alias, Elixir is
going to issue a warning implying the alias is not being used.
In case the alias is generated automatically by a macro,
Elixir won't emit any warnings though, since the alias
was not explicitly defined.
Both warning behaviors could be changed by explicitily
setting the `:warn` option to true or false.
"""
defmacro alias(module, opts)
@doc """
`require` is used to require the presence of external
modules so macros can be invoked.
Requires a given module to be compiled and loaded.
## Examples
@@ -246,6 +365,8 @@ defmodule Kernel.SpecialForms do
defmacro require(module, opts)
@doc """
Imports function and macros from other modules.
`import` allows one to easily access functions or macros from
others modules without using the qualified name.
@@ -335,14 +456,16 @@ defmodule Kernel.SpecialForms do
defmacro import(module, opts)
@doc """
Returns the current environment information as a `Macro.Env`
record. In the environment you can access the current filename,
Returns the current environment information as a `Macro.Env[]` record.
In the environment you can access the current filename,
line numbers, set up aliases, the current function and others.
"""
defmacro __ENV__
@doc """
Returns the current module name as an atom or `nil` otherwise.
Although the module can be accessed in the `__ENV__`, this macro
is a convenient shortcut.
"""
@@ -350,6 +473,7 @@ defmodule Kernel.SpecialForms do
@doc """
Returns the current file name as a binary.
Although the file can be accessed in the `__ENV__`, this macro
is a convenient shortcut.
"""
@@ -360,8 +484,8 @@ defmodule Kernel.SpecialForms do
"""
defmacro __DIR__
@doc """
Allows you to get the representation of any expression.
@doc %S"""
Gets the representation of any expression.
## Examples
@@ -391,14 +515,14 @@ defmodule Kernel.SpecialForms do
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control what quote is
able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
* `:location` - When set to `:keep`, keeps the current line and file from quote.
Read the Stacktrace information section below for more information;
* `:hygiene` - Allows a developer to disable hygiene selectively;
* `:context` - Sets the resolution context;
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
`unquote` is automatically disabled;
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
`unquote` is automatically disabled;
## Macro literals
## Quote literals
Besides the tuple described above, Elixir has a few literals that
when quoted return themselves. They are:
@@ -407,40 +531,111 @@ defmodule Kernel.SpecialForms do
1 #=> Integers
2.0 #=> Floats
[1, 2] #=> Lists
"binaries" #=> Binaries
"strings" #=> Strings
{key, value} #=> Tuple with two elements
## Hygiene and context
## Quote and macros
Elixir macros are hygienic via means of deferred resolution.
This means variables, aliases and imports defined inside the
quoted refer to the context that defined the macro and not
the context where the macro is expanded.
`quote` is commonly used with macros for code generation. As an exercise,
let's define a macro that multiplies a number by itself (squared). Note
there is no reason to define such as a macro (and it would actually be
seen as a bad practice), but it is simple enough that allows us to focus
on the important aspects of quotes and macros:
For this mechanism to work, every quoted code is attached
to a context. Consider the following example:
defmodule ContextSample do
def hello do
quote do: world
defmodule Math do
defmacro squared(x) do
quote do
unquote(x) * unquote(x)
end
end
end
ContextSample.hello
#=> {:world,[],ContextSample}
We can invoke it as:
Notice how the third element of the returned tuple is the
module name. This means that the variable is associated to the
`ContextSample` module and only code generated by this module
will be able to access that particular `world` variable.
While this means macros from the same module could have
conflicting variables, it also allows different quotes from
the same module to access them.
import Math
IO.puts "Got #{squared(5)}"
The context can be disabled or changed by explicitly setting
the `context` option. All hygiene mechanisms are based on such
context and we are going to explore each of them in the following
subsections.
At first, there is nothing in this example that actually reveals it is a
macro. But what is happening is that, at compilation time, `squared(5)`
becomes `5 * 5`. We can see this behaviour in practice though
because our macro actually has a bug:
import Math
my_number = fn ->
IO.puts "Returning 5"
5
end
IO.puts "Got #{squared(my_number.())}"
The example above will print:
Returning 5
Returning 5
25
Notice how "Returning 5" was printed twice, instead of just once. This is
because a macro receives an expression and not a value (which is what we
would expect in a regular function). This means that:
squared(my_number.())
Actually expands to:
my_number.() * my_number.()
Which invokes the function twice, explaining why we get the printed value
twice! In the majority of the cases, this is actually unexpected behaviour,
and that's why one of the first things you need to keep in mind when it
comes to macros is to **not unquote the same value more than once**.
Let's fix our macro:
defmodule Math do
defmacro squared(x) do
quote do
x = unquote(x)
x * x
end
end
end
Now invoking `square(my_number.())` as before will print the value just
once.
In fact, this pattern is so common that most of the times you will want
to use the `bind_quoted` option with `quote`:
defmodule Math do
defmacro squared(x) do
quote bind_quoted: [x: x] do
x * x
end
end
end
`:bind_quoted` will translate to the same 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.
Before we finish this brief introduction, you will notice that, even though
we defined a variable `x` inside our quote:
quote do
x = unquote(x)
x * x
end
When we call:
import Math
squared(5)
x #=> ** (RuntimeError) undefined function or variable: x
We can see that `x` did not leak to the user context. This happens
because Elixir macros are hygienic, a topic we will discuss at length
in the next sections as well.
### Hygiene in variables
@@ -476,31 +671,47 @@ defmodule Kernel.SpecialForms do
NoHygiene.interference
a #=> 1
It is important to understand that quoted variables are scoped
to the module they are defined. That said, even if two modules
define the same quoted variable `a`, their values are going
to be independent:
Note that you cannot even access variables defined by the same
module unless you explicitly give it a context:
defmodule Hygiene1 do
defmacro var1 do
quote do: a = 1
defmodule Hygiene do
defmacro write do
quote do
a = 1
end
end
defmacro read do
quote do
a
end
end
end
defmodule Hygiene2 do
defmacro var2 do
quote do: a = 2
Hygiene.write
Hygiene.read
#=> ** (RuntimeError) undefined function or variable: a
For such, you can explicitly pass the current module scope as
argument:
defmodule ContextHygiene do
defmacro write do
quote do
var!(a, ContextHygiene) = 1
end
end
defmacro read do
quote do
var!(a, ContextHygiene)
end
end
end
Calling macros `var1` and `var2` are not going to change their
each other values for `a`. This is useful because quoted
variables from different modules cannot conflict. If you desire
to explicitly access a variable from another module, we can once
again use `var!` macro, but explicitly passing a second argument:
# Access the variable a from Hygiene1
quote do: var!(a, Hygiene1) = 2
ContextHygiene.write
ContextHygiene.read
#=> 1
Hygiene for variables can be disabled overall as:
@@ -557,7 +768,6 @@ defmodule Kernel.SpecialForms do
#=> "world"
end
## Hygiene in imports
Similar to aliases, imports in Elixir are hygienic. Consider the
@@ -609,35 +819,35 @@ defmodule Kernel.SpecialForms do
## Stacktrace information
One of Elixir's goals is to provide a proper stacktrace whenever there is an
exception. In order to work properly with macros, the default behavior
in quote is to not set a line. When a macro is invoked and the quoted
expressions is expanded, the call site line is inserted.
When defining functions via macros, developers have the option of
choosing if runtime errors will be reported from the caller or from
inside the quote. Let's see an example:
This is a good behavior for the majority of the cases, except if the macro
is defining new functions. Consider this example:
defmodule MyServer do
use GenServer.Behaviour
end
`GenServer.Behaviour` defines new functions in our `MyServer` module.
However, if there is an exception in any of these functions, we want
the stacktrace to point to the `GenServer.Behaviour` and not the line
that calls `use GenServer.Behaviour`. For this reason, there is an
option called `:location` that when set to `:keep` keeps the original
line and file lines instead of setting them to 0:
quote location: :keep do
def handle_call(request, _from, state) do
{ :reply, :undef, state }
# adder.ex
defmodule Adder do
@doc "Defines a function that adds two numbers"
defmacro defadd do
quote location: :keep do
def add(a, b), do: a + b
end
end
end
It is important to warn though that `location: :keep` evaluates the
code as if it was defined inside `GenServer.Behaviour` file, in
particular, the macro `__FILE__` and exceptions happening inside
the quote will always point to `GenServer.Behaviour` file.
# sample.ex
defmodule Sample do
import Adder
defadd
end
When using `location: :keep` and invalid arguments are given to
`Sample.add/2`, the stacktrace information will point to the file
and line inside the quote. Without `location: :keep`, the error is
reported to where `defadd` was invoked.
Note that `location: :keep` evaluates the code as if it was defined
inside the `Adder` file, in particular, the macro `__FILE__` and
exceptions happening inside the quote will always point to the file
where `Added` was defined.
## Binding and unquote fragments
@@ -697,8 +907,8 @@ defmodule Kernel.SpecialForms do
One solution for this problem is to disable unquoting in the
macro, however, doing that would make it impossible to inject
`kv` representation into the tree. That's when the `:bind_quoted`
option comes to the rescue. By using `:bind_quoted`, we can
automatically disable unquoting while still injecting the
option comes to the rescue (again!). By using `:bind_quoted`, we
can automatically disable unquoting while still injecting the
desired variables into the tree:
defmacro defkv(kv) do
@@ -709,26 +919,21 @@ defmodule Kernel.SpecialForms do
end
end
In fact, the `:binding` option is recommended every time one
desires to inject a value into the quote.
In fact, the `:bind_quoted` option is recommended every time
one desires to inject a value into the quote.
"""
defmacro quote(opts, block)
@doc """
When used inside quoting, marks that the variable should
not be hygienized. The argument can be either a variable
node (i.e. a tuple with three elements where the last
one is an atom) or an atom representing the variable name.
unquoted or an atom representing the variable name.
Check `quote/2` for more information.
"""
defmacro var!(var)
@doc """
Defines a variable in the given context.
If the context is `false`, it is not stored in any particular
context and the variable is not shared in between clauses.
Check `quote/2` for more information.
"""
defmacro var!(var, context)
@@ -820,6 +1025,9 @@ defmodule Kernel.SpecialForms do
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
Variable assignments in the `do` block of the comprehension
are not reflected outside of the block.
Note: Unlike Erlang, Elixir comprehension filters
never behave as guards when it comes to errors. Errors in
list comprehensions will always be raised. Consider this
@@ -837,10 +1045,11 @@ defmodule Kernel.SpecialForms do
defmacro lc(args)
@doc """
Defines a bit comprehension. It follows the same syntax as
a list comprehension but expects each element returned to
be a bitstring. For example, here is how to remove all
spaces from a string:
Defines a bit comprehension.
It follows the same syntax and behaviour as a list comprehension
but expects each element returned to be a bitstring. For example,
here is how to remove all spaces from a string:
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
"helloworld"
@@ -849,6 +1058,21 @@ defmodule Kernel.SpecialForms do
defmacro bc(args)
@doc """
Defines an anonymous function.
## Examples
iex> add = fn a, b -> a + b end
iex> add.(1, 2)
3
"""
name = :fn
defmacro unquote(name)(clauses)
@doc """
Internal special form for block expressions.
This is the special form used whenever we have a block
of expressions in Elixir. This special form is private
and should not be invoked directly:
@@ -860,7 +1084,7 @@ defmodule Kernel.SpecialForms do
defmacro __block__(args)
@doc """
Captures a call as an anonymous function.
Captures an anonymous function.
The most common format to capture a function is via module,
name and arity:
@@ -879,17 +1103,17 @@ defmodule Kernel.SpecialForms do
A capture also allows the captured functions to be partially
applied, for example:
iex> fun = &atom_to_binary(&1, :utf8)
iex> fun.(:hello)
"hello"
iex> fun = &is_record(&1, Range)
iex> fun.(1..3)
true
In the example above, we use &1 as a placeholder, generating
a function with one argument. We could also use `&2` and `&3`
to delimit more arguments:
iex> fun = &atom_to_binary(&1, &2)
iex> fun.(:hello, :utf8)
"hello"
iex> fun = &is_record(&1, &2)
iex> fun.(1..3, Range)
true
Since operators are calls, they are also supported, although
they require explicit parentheses around:
@@ -938,30 +1162,31 @@ defmodule Kernel.SpecialForms do
defmacro unquote(name)(expr)
@doc """
This is the special form used whenever we have to temporarily
change the scope information of a block. Used when `quote` is
invoked with `location: :keep` to execute a given block as if
it belonged to another file.
Internal special form for modifying scope information.
Used when `quote` is invoked with `location: :keep` to execute
a given block as if it belonged to another file.
quote location: :keep, do: 1
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
Check `quote/1` for more information.
Check `quote/2` for more information.
"""
defmacro __scope__(opts, args)
@doc """
This is the special form used to hold aliases information.
Internal special form to hold aliases information.
It is usually compiled to an atom:
quote do: Foo.Bar #=>
{ :__aliases__, [], [:Foo,:Bar] }
iex> quote do: Foo.Bar
{:__aliases__, [alias: false], [:Foo, :Bar]}
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
unambiguously identified by the operator `:.`. For example:
quote do: Foo.bar #=>
{{:.,[],[{:__aliases__,[],[:Foo]},:bar]},[],[]}
iex> quote do: Foo.bar
{{:., [], [{:__aliases__, [alias: false], [:Foo]}, :bar]}, [], []}
Whenever an expression iterator sees a `:.` as the tuple key,
it can be sure that it represents a call and the second argument
@@ -978,7 +1203,7 @@ defmodule Kernel.SpecialForms do
4) When the head element of aliases is not an atom, it is expanded at runtime:
quote do: some_var.Foo
{:__aliases__,[],[{:some_var,[],:quoted},:Bar]}
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
Since `some_var` is not available at compilation time, the compiler
expands such expression to:
+62 -16
View File
@@ -159,7 +159,7 @@ defmodule Kernel.Typespec do
@doc """
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
"""
def define_type(module, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
def define_type(caller, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
{ kind, export } =
case kind do
:type -> { :type, true }
@@ -167,12 +167,35 @@ defmodule Kernel.Typespec do
:opaque -> { :opaque, true }
end
module = caller.module
arity = length(vars)
Module.compile_typespec module, kind, type
if export, do:
Module.compile_typespec(module, :export_type, [{ name, length(vars) }])
if export do
Module.compile_typespec(module, :export_type, [{ name, arity }])
end
define_doc(caller, kind, name, arity, export)
type
end
defp define_doc(caller, kind, name, arity, export) do
module = caller.module
doc = Module.get_attribute(module, :typedoc)
if doc do
if export do
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
else
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n"
end
end
Module.delete_attribute(module, :typedoc)
end
@doc """
Defines a `spec` by receiving Erlang's typespec.
"""
@@ -193,7 +216,7 @@ defmodule Kernel.Typespec do
for modules being compiled.
"""
def defines_type?(module, name, arity) do
finder = match?({ ^name, _, vars } when length(vars) == arity, &1)
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
:lists.any(finder, Module.get_attribute(module, :type)) or
:lists.any(finder, Module.get_attribute(module, :opaque))
end
@@ -204,7 +227,7 @@ defmodule Kernel.Typespec do
"""
def defines_spec?(module, name, arity) do
tuple = { name, arity }
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :spec))
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :spec))
end
@doc """
@@ -213,7 +236,7 @@ defmodule Kernel.Typespec do
"""
def defines_callback?(module, name, arity) do
tuple = { name, arity }
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :callback))
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :callback))
end
@doc """
@@ -255,6 +278,26 @@ defmodule Kernel.Typespec do
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
end
@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
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
located by the runtime system.
"""
@spec beam_typedocs(module | binary) :: [tuple] | nil
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
List.flatten(type_docs)
_ ->
nil
end
end
@doc """
Returns all types available from the module's beam code.
@@ -264,7 +307,8 @@ defmodule Kernel.Typespec do
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
def beam_types(module) do
@spec beam_types(module | binary) :: [tuple] | nil
def beam_types(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
@@ -278,7 +322,7 @@ defmodule Kernel.Typespec do
end
end
_ ->
[]
nil
end
end
@@ -291,7 +335,8 @@ defmodule Kernel.Typespec do
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
def beam_specs(module) do
@spec beam_specs(module | binary) :: [tuple] | nil
def beam_specs(module) when is_atom(module) or is_binary(module) do
from_abstract_code(module, :spec)
end
@@ -301,10 +346,11 @@ defmodule Kernel.Typespec do
It is returned as a list of tuples where the first
element is spec name and arity and the second is the spec.
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
The module has to have a corresponding beam file on the disk
which can be located by the runtime system.
"""
def beam_callbacks(module) do
@spec beam_callbacks(module | binary) :: [tuple] | nil
def beam_callbacks(module) when is_atom(module) or is_binary(module) do
from_abstract_code(module, :callback)
end
@@ -312,8 +358,8 @@ defmodule Kernel.Typespec do
case abstract_code(module) do
{ :ok, abstract_code } ->
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
_ ->
[]
:error ->
nil
end
end
@@ -322,7 +368,7 @@ defmodule Kernel.Typespec do
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
{ :ok, abstract_code }
_ ->
[]
:error
end
end
@@ -368,7 +414,7 @@ defmodule Kernel.Typespec do
vars = lc { :var, _, _ } = var inlist args, do: var
type = { name, spec, vars }
define_type(caller.module, kind, type)
define_type(caller, kind, type)
end
@doc false
+146 -22
View File
@@ -4,13 +4,23 @@ defmodule Keyword do
of the tuple is an atom and the second element can be
any value.
A keyword may have duplicated keys, so it is not strictly
A keyword may have duplicated keys so it is not strictly
a dictionary. However most of the functions in this module
allows it to behave exactly as a dictionary. For example,
`Keyword.get` will get the first entry matching the given
key, regardless if duplicated entries exist. Similarly,
`Keyword.put` and `Keyword.delete` ensure all duplicated
entries for a given key are removed when invoked.
behaves exactly as a dictionary and mimic the API defined
by the `Dict` behaviour.
For example, `Keyword.get` will get the first entry matching
the given key, regardless if duplicated entries exist.
Similarly, `Keyword.put` and `Keyword.delete` ensure all
duplicated entries for a given key are removed when invoked.
A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without
removing duplicated keys, `get_values` returns all values for
a given key and `delete_first` deletes just one of the existing
entries.
Since a keyword list is simply a list, all the operations defined
in `Enum` and `List` can also be applied.
"""
@type key :: atom
@@ -23,16 +33,12 @@ defmodule Keyword do
duplicated entries.
"""
@spec from_enum(Enum.t) :: t
def from_enum(enum) when is_list(enum) do
enum
end
def from_enum(enum) do
Enum.map(enum, fn(x) -> x end)
Enum.to_list(enum)
end
@doc """
Checks if the given argument is a keywords list or not
Checks if the given argument is a keywords list or not.
"""
@spec keyword?(term) :: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
@@ -356,22 +362,16 @@ defmodule Keyword do
:lists.keymember(key, 1, keywords)
end
@doc false
def update(dict, key, fun) when is_function(fun, 1) do
IO.write "Keyword.update/3 is deprecated, please use Keyword.update!/3 instead\n#{Exception.format_stacktrace}"
update!(dict, key, fun)
end
@doc """
Updates the `key` with the given function. If the `key` does
not exist, raises `KeyError`.
## Examples
iex> Keyword.update!([a: 1], :a, &1 * 2)
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
[a: 2]
iex> Keyword.update!([a: 1], :b, &1 * 2)
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
** (KeyError) key not found: :b
"""
@@ -394,10 +394,10 @@ defmodule Keyword do
## Examples
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
[a: 2]
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
iex> Keyword.update([a: 1], :b, 11, &(&1 * 2))
[a: 1, b: 11]
"""
@@ -413,4 +413,128 @@ defmodule Keyword do
def update([], key, initial, _fun) when is_atom(key) do
[{key, initial}]
end
@doc """
Splits the given keywords in two given the given keys.
Duplicated keys are preserved in the split keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.split(d, [:a, :c, :e])
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.split(d, [:a, :c, :e])
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
"""
def split(dict, keys) do
acc = { [], [] }
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
case :lists.member(k, keys) do
true -> { [{k, v}|take], drop }
false -> { take, [{k, v}|drop] }
end
end
{ Enum.reverse(take), Enum.reverse(drop) }
end
@doc """
Takes the given keys from the dict.
Duplicated keys are preserved in the new keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.take(d, [:a, :c, :e])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.take(d, [:a, :c, :e])
[a: 1, c: 3, a: 5]
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
end
@doc """
Drops the given keys from the dict.
Duplicated keys are preserved in the new keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3, a: 5]
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`.
All duplicated entries are removed. See `pop_first/3` for
removing only the first entry.
## Examples
iex> Keyword.pop [a: 1], :a
{1,[]}
iex> Keyword.pop [a: 1], :b
{nil,[a: 1]}
iex> Keyword.pop [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop [a: 1, a: 2], :a
{1,[]}
"""
def pop(dict, key, default // nil) do
{ get(dict, key, default), delete(dict, key) }
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without that particular ocurrence
of `key`.
Duplicated entries are not removed.
## Examples
iex> Keyword.pop_first [a: 1], :a
{1,[]}
iex> Keyword.pop_first [a: 1], :b
{nil,[a: 1]}
iex> Keyword.pop_first [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop_first [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop_first [a: 1, a: 2], :a
{1,[a: 2]}
"""
def pop_first(dict, key, default // nil) do
{ get(dict, key, default), delete_first(dict, key) }
end
end
+1 -13
View File
@@ -12,18 +12,6 @@ defmodule List do
@compile :inline_list_funcs
@doc false
def concat(list) when is_list(list) do
IO.write "List.concat/1 is deprecated, please use Enum.concat/1 instead\n#{Exception.format_stacktrace}"
:lists.append(list)
end
@doc false
def concat(list, elements) when is_list(list) and is_list(elements) do
IO.write "List.concat/2 is deprecated, please use Enum.concat/2 instead\n#{Exception.format_stacktrace}"
list ++ elements
end
@doc """
Deletes the given item from the list. Returns a list without the item.
If the item occurs more than once in the list, just the first occurrence
@@ -307,7 +295,7 @@ defmodule List do
"""
def unzip(list) when is_list(list) do
:lists.map tuple_to_list(&1), zip(list)
:lists.map &tuple_to_list/1, zip(list)
end
@doc """
+7 -7
View File
@@ -9,8 +9,6 @@ defprotocol List.Chars do
by Kernel invokes this protocol.
"""
@only [BitString, List, Atom, Number, Record]
def to_char_list(thing)
end
@@ -38,13 +36,15 @@ defimpl List.Chars, for: List do
def to_char_list(list), do: list
end
defimpl List.Chars, for: Number do
@digits 20
@limit :math.pow(10, @digits)
def to_char_list(thing) when is_integer(thing) do
defimpl List.Chars, for: Integer do
def to_char_list(thing) do
integer_to_list(thing)
end
end
defimpl List.Chars, for: Float do
@digits 20
@limit :math.pow(10, @digits)
def to_char_list(thing) when thing > @limit do
float_to_list(thing, scientific: @digits)
+32 -37
View File
@@ -2,6 +2,10 @@ defmodule ListDict do
@moduledoc """
A Dict implementation that works on lists of two-items tuples.
This dictionary is only recommended for keeping a small amount
of values. Other dict alternatives are more viable for keeping
any other amount than a handful.
For more information about the functions and their APIs, please
consult the `Dict` module.
"""
@@ -38,28 +42,25 @@ defmodule ListDict do
length(dict)
end
def has_key?(dict, key) do
:lists.keymember(key, 1, dict)
end
def has_key?(dict, key)
def has_key?([{ key, _ }|_], key), do: true
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
def has_key?([], _key), do: false
def get(dict, key, default // nil) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
false -> default
end
end
def get(dict, key, default // nil)
def get([{ key, value }|_], key, _default), do: value
def get([{ _, _ }|t], key, default), do: get(t, key, default)
def get([], _key, default), do: default
def fetch(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
def fetch(dict, key)
def fetch([{ key, value }|_], key), do: { :ok, value }
def fetch([{ _, _ }|t], key), do: fetch(t, key)
def fetch([], _key), do: :error
def fetch!(dict, key) do
case :lists.keyfind(key, 1, dict) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
end
end
@@ -72,26 +73,26 @@ defmodule ListDict do
end
def put_new(dict, key, val) do
case :lists.keyfind(key, 1, dict) do
{ ^key, _ } -> dict
case has_key?(dict, key) do
true -> dict
false -> [{key, val}|dict]
end
end
def delete(dict, key) do
lc { k, _ } = tuple inlist dict, key != k, do: tuple
end
def delete(dict, key)
def delete([{ key, _ }|t], key), do: t
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
def delete([], _key), do: []
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, fun) do
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end)
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce enum, dict, fn { k, v2 }, acc ->
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
end
end
def split(dict, keys) do
acc = { new(), new() }
acc = { [], [] }
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
{ [{k, v}|take], drop }
@@ -99,7 +100,7 @@ defmodule ListDict do
{ take, [{k, v}|drop] }
end
end
{Enum.reverse(take), Enum.reverse(drop)}
end
@@ -111,12 +112,6 @@ defmodule ListDict do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
@doc false
def update(dict, key, fun) when is_function(fun, 1) do
IO.write "ListDict.update/3 is deprecated, please use ListDict.update!/3 instead\n#{Exception.format_stacktrace}"
update!(dict, key, fun)
end
def update!([{key, value}|dict], key, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
@@ -144,7 +139,7 @@ defmodule ListDict do
def empty(_dict), do: []
def equal?(dict, other) do
:lists.keysort(1, dict) == :lists.keysort(1, other)
:lists.keysort(1, dict) === :lists.keysort(1, other)
end
def to_list(dict), do: dict
+64 -83
View File
@@ -5,6 +5,7 @@ defmodule Macro do
This module provides conveniences for working with macros.
"""
@typedoc "Abstract Syntax Tree (AST) node"
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
@doc false
@@ -23,8 +24,7 @@ defmodule Macro do
[:!, :@, :^, :not, :+, :-, :~~~, :&]
end
@typep precedence :: integer # Higher means binds stronger
@spec binary_op_props(atom) :: { :left|:right, precedence }
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
::: -> {:right, 30}
@@ -42,13 +42,47 @@ defmodule Macro do
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
o when o in [:<>] -> {:right, 230}
o when o in [:++, :--, :**] -> {:right, 240}
:^^^ -> {:left, 250}
:. -> {:left, 310}
end
end
@doc """
Receives an expresion representing a possible definition
Breaks a pipeline expression into a list. Raises if
the pipeline is ill-formed.
"""
@spec unpipe(Macro.t) :: [Macro.t]
def unpipe({ :|> , _, [left, right] }) do
[left|unpipe(right)]
end
def unpipe(other) do
[other]
end
@doc """
Pipes the given `expr` in to the `call_expr` as the
argument in the given `position`.
"""
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
def pipe(expr, call_args, integer // 0)
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
{ call, line, List.insert_at([], integer, expr) }
end
def pipe(expr, { call, line, args }, integer) when is_list(args) do
{ call, line, List.insert_at(args, integer, expr) }
end
def pipe(expr, call_args, _integer) do
raise ArgumentError,
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
end
@doc """
Receives an expression representing a possible definition
and extracts its arguments. It returns a tuple with the
function name and the arguments list or `:error` if not
a valid call syntax.
@@ -89,22 +123,10 @@ defmodule Macro do
1
"""
@spec escape(Macro.t) :: Macro.t
@spec escape(Macro.t, Keyword.t) :: Macro.t
@spec escape(term) :: Macro.t
@spec escape(term, Keyword.t) :: Macro.t
def escape(expr, opts // []) do
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
end
@doc false
def unescape_binary(chars) do
IO.write "Macro.unescape_binary/1 is deprecated, please use Macro.unescape_string/1 instead\n#{Exception.format_stacktrace}"
unescape_string(chars)
end
@doc false
def unescape_binary(chars, map) do
IO.write "Macro.unescape_binary/2 is deprecated, please use Macro.unescape_string/2 instead\n#{Exception.format_stacktrace}"
unescape_string(chars, map)
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc %S"""
@@ -174,7 +196,7 @@ defmodule Macro do
Using the unescape_map defined above is easy:
Macro.unescape_string "example\\n", unescape_map(&1)
Macro.unescape_string "example\\n", &unescape_map(&1)
"""
@spec unescape_string(String.t, (non_neg_integer -> non_neg_integer | false)) :: String.t
@@ -221,7 +243,7 @@ defmodule Macro do
# Variables
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
fun.(ast, atom_to_binary(var, :utf8))
fun.(ast, atom_to_binary(var))
end
# Aliases
@@ -252,22 +274,17 @@ defmodule Macro do
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
end
# List containers
def to_string({ :[], _, args } = ast, fun) do
fun.(ast, "[" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "]")
end
# Fn keyword
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] } = ast, fun)
def to_string({ :fn, _, [{ :->, _, [{_, _, tuple}] } = arrow] } = ast, fun)
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
end
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] } = ast, fun) do
def to_string({ :fn, _, [{ :->, _, [_] } = block] } = ast, fun) do
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
end
def to_string({ :fn, _, [[do: block]] } = ast, fun) do
def to_string({ :fn, _, [block] } = ast, fun) do
block = adjust_new_lines block_to_string(block, fun), "\n "
fun.(ast, "fn\n " <> block <> "\nend")
end
@@ -294,16 +311,12 @@ defmodule Macro do
end
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
fun.(ast, atom_to_binary(op, :utf8) <> to_string(arg, fun))
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
end
# Access
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
fun.(ast, if right != [] and Keyword.keyword?(right) do
to_string(left, fun) <> to_string(right, fun)
else
to_string(left, fun) <> "[" <> to_string(right, fun) <> "]"
end)
fun.(ast, to_string(left, fun) <> to_string(right, fun))
end
# All other calls
@@ -321,11 +334,11 @@ defmodule Macro do
end
# Lists
def to_string(list = ast, fun) when is_list(list) do
def to_string(list, fun) when is_list(list) do
if Keyword.keyword?(list) do
fun.(ast, "[" <> kw_list_to_string(list, fun) <> "]")
fun.(list, "[" <> kw_list_to_string(list, fun) <> "]")
else
to_string({ :[], [], list }, fun)
fun.(list, "[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]")
end
end
@@ -336,14 +349,14 @@ defmodule Macro do
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
defp is_kw_blocks?([_|_] = kw) do
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
Enum.all?(kw, &match?({x, _} when x in kw_keywords, &1))
end
defp is_kw_blocks?(_), do: false
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
@@ -375,7 +388,7 @@ defmodule Macro do
defp kw_block_to_string(key, value, fun) do
block = adjust_new_lines block_to_string(value, fun), "\n "
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
atom_to_binary(key) <> "\n " <> block <> "\n"
end
defp block_to_string({ :->, _, exprs }, fun) do
@@ -518,21 +531,21 @@ defmodule Macro do
"""
def expand_once(ast, env) do
expand_once(ast, env, nil) |> elem(0)
elem(expand_once(ast, env, nil), 0)
end
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
receiver when is_atom(receiver) ->
:elixir_tracker.record_alias(receiver, env.module)
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true, cache }
aliases ->
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
case :lists.all(is_atom(&1), aliases) do
case :lists.all(&is_atom/1, aliases) do
true ->
receiver = :elixir_aliases.concat(aliases)
:elixir_tracker.record_alias(receiver, env.module)
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true, cache }
false -> { original, false, cache }
end
@@ -611,7 +624,7 @@ defmodule Macro do
defp to_erl_env(_env, cache), do: cache
defp is_partial?(args) do
:lists.any(match?({ :&, _, [_] }, &1), args)
:lists.any(&match?({ :&, _, [_] }, &1), args)
end
@doc """
@@ -620,7 +633,7 @@ defmodule Macro do
expansion works and `expand_all/2` for recursive expansion.
"""
def expand(tree, env) do
expand(tree, env, nil) |> elem(0)
elem(expand(tree, env, nil), 0)
end
@doc false # Used internally by Elixir
@@ -636,38 +649,6 @@ defmodule Macro do
{ tree, cache }
end
@doc %S"""
Receives a AST node and expands it until it no longer represents
a macro. Then it expands all of its children recursively.
The documentation for `expand_once/2` goes into more detail
on how expansion works. Keep in mind that `expand_all/2` is
naive as it doesn't mutate the environment. Take this example:
quoted = quote do: __MODULE__
Macro.to_string Macro.expand_all(quoted, __ENV__)
#=> "nil"
The example above, works as expected. Outside of a module,
`__MODULE__` returns `nil`. Now consider this variation:
quoted = quote do
defmodule Foo, do: __MODULE__
end
Macro.to_string Macro.expand_all(quoted, __ENV__)
#=> "defmodule(Foo) do\n nil\nend"
One would expect `__MODULE__` to be expanded to `Foo`
but that is not the case since all the expansion is done
based on the environment `__ENV__`. This behaviour is on
purpose, as the proper expansion would require passing through
the whole Elixir compiler by actually executing the code, which
is beyond the scope of this function.
"""
def expand_all(tree, env) do
expand_all(tree, env, nil) |> elem(0)
end
@doc false # Used internally by Elixir
def expand_all(tree, env, cache) do
expand_all_until(expand(tree, env, cache), env)
@@ -686,7 +667,7 @@ defmodule Macro do
end
defp expand_all_until({ list, cache }, env) when is_list(list) do
:lists.mapfoldl(expand_all(&1, env, &2), cache, list)
:lists.mapfoldl(&expand_all(&1, env, &2), cache, list)
end
defp expand_all_until({ other, cache }, _env) do
@@ -703,7 +684,7 @@ defmodule Macro do
do_safe_term(terms) || :ok
end
defp do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
defp do_safe_term({ local, _, terms }) when local in [:{}, :__aliases__] do
do_safe_term(terms)
end
@@ -712,7 +693,7 @@ defmodule Macro do
end
defp do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
defp do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
defp do_safe_term(_), do: nil
end
+6 -5
View File
@@ -34,14 +34,15 @@ defmodule Macro.Env do
@type macros :: [{ module, [name_arity] }]
@type context_modules :: [module]
@type vars :: [{ atom, atom }]
@type lexical_tracker :: pid
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
:functions, :macros, :context_modules, :macro_aliases, :vars]
fields = [:module, :file, :line, :function, :aliases, :context, :requires, :functions,
:macros, :context_modules, :macro_aliases, :vars, :lexical_tracker]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros, context_modules: context_modules,
macro_aliases: aliases, vars: vars]
macro_aliases: aliases, vars: vars, lexical_tracker: lexical_tracker]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
@@ -55,13 +56,13 @@ defmodule Macro.Env do
end
@doc """
Returns wether the compilation environment is currently
Returns whether the compilation environment is currently
inside a guard.
"""
def in_guard?(record), do: context(record) == :guard
@doc """
Returns wether the compilation environment is currently
Returns whether the compilation environment is currently
inside a match clause.
"""
def in_match?(record), do: context(record) == :match
+27 -18
View File
@@ -29,7 +29,7 @@ defmodule Module do
When just a module is provided, the function is assumed to be
`__after_compile__/2`.
**Example**
### Example
defmodule M do
@after_compile __MODULE__
@@ -44,18 +44,20 @@ defmodule Module do
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
function/macro must take one argument: the module environment. If it\'s a
function/macro must take one argument: the module environment. If it's a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
**Example**
Note: differently from `@after_compile`, the callback function/macro must
be placed in a separate module (because when the callback is invoked,
the current module does not yet exist).
defmodule M do
@before_compile __MODULE__
### Example
defmodule A do
defmacro __before_compile__(_env) do
quote do
def hello, do: "world"
@@ -63,11 +65,15 @@ defmodule Module do
end
end
defmodule B do
@before_compile A
end
* `@behaviour` (notice the british spelling)
Specify an OTP or user-defined behaviour.
**Example**
### Example
defmodule M do
@behaviour gen_event
@@ -85,7 +91,7 @@ defmodule Module do
See http://www.erlang.org/doc/man/compile.html for the list of supported
options.
**Example**
### Example
defmodule M do
@compile { :inline, myfun: 1 }
@@ -106,7 +112,7 @@ defmodule Module do
Can be invoked more than once.
**Example**
### Example
defmodule M do
@doc "Hello world"
@@ -129,7 +135,7 @@ defmodule Module do
Accepts a string. Can be used more than once.
**Example**
### Example
defmodule M do
@doc "Hello world"
@@ -147,7 +153,7 @@ defmodule Module do
`@moduledoc false` will make the module invisible to the
documentation extraction tools like ExDoc.
**Example**
### Example
defmodule M do
@moduledoc """
@@ -183,7 +189,7 @@ defmodule Module do
function (i.e. `env.function` returns the current function), the hook
can only be a function, not a macro.
**Example**
### Example
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
@@ -216,7 +222,7 @@ defmodule Module do
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
loading of the module will be aborted.
**Example**
### Example
defmodule M do
@on_load :load_check
@@ -238,7 +244,7 @@ defmodule Module do
Specify the module version. Accepts any valid Elixir value.
**Example**
### Example
defmodule M do
@vsn "1.0"
@@ -477,10 +483,11 @@ defmodule Module do
end
@doc """
Attaches documentation to a given function. It expects
the module the function belongs to, the line (a non negative
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
the function and its arity and the documentation, which should
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.
## Examples
@@ -491,12 +498,14 @@ defmodule Module do
end
"""
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop] do
def add_doc(module, line, kind, tuple, signature // [], doc)
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
if doc, do: { :error, :private_doc }, else: :ok
end
def add_doc(module, line, kind, tuple, signature, doc) when
kind in [:def, :defmacro] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
+7 -96
View File
@@ -5,15 +5,11 @@
# ## Implementation
#
# The implementation uses the digraph module to track
# all dependencies. The graph starts with three main
# vertices:
# all dependencies. The graph starts with one main vertice:
#
# * `:local` - points to local functions
# * `:import` - points to imported modules
# * `:warn` - points to imported modules that should be warned
# * `:remote` - points to remote modules
#
# Besides those, we have can the following vertices:
# We also have can the following vertices:
#
# * `Module` - a module that was invoked via an import or remotely
# * `{ name, arity }` - a local function/arity pair
@@ -24,9 +20,7 @@
# as described below:
#
# * `Module`
# * in neighbours: `:import`, `:remote`, `:warn`,
# `{ :import, name, arity }` and `{ :remote, name arity }`
# * out neighbours: `:warn`
# * in neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
#
# * `{ name, arity }`
# * in neighbours: `:local`, `{ name, arity }`
@@ -92,15 +86,6 @@ defmodule Module.DispatchTracker do
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
end
@doc """
Returns all the modules which were imported.
"""
@spec imports(ref) :: [module]
def imports(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :import)
end
@doc """
Returns all imported modules that had the given
`{ name, arity }` invoked.
@@ -111,24 +96,6 @@ defmodule Module.DispatchTracker do
:digraph.out_neighbours(d, { :import, name, arity })
end
@doc """
Returns all the aliases defined in the given module.
"""
@spec aliases(ref) :: [module]
def aliases(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :alias)
end
@doc """
Returns all the modules which were remotely dispatched to.
"""
@spec remotes(ref) :: [module]
def remotes(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, :remote)
end
@doc """
Returns all modules that had the given `{ name, arity }`
invoked remotely.
@@ -157,7 +124,7 @@ defmodule Module.DispatchTracker do
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
remaining = :ordsets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices)
:lists.foldl(reduce_reachable(d, &1, &2), vertices, remaining)
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
end
defp to_pid(pid) when is_pid(pid), do: pid
@@ -206,12 +173,6 @@ defmodule Module.DispatchTracker do
:gen_server.cast(pid, { :add_local, from, to })
end
# Adds an alias.
@doc false
def add_alias(pid, module) when is_atom(module) do
:gen_server.cast(pid, { :add_alias, module })
end
# Adds a remote dispatch to the given target.
@doc false
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
@@ -224,40 +185,14 @@ defmodule Module.DispatchTracker do
:gen_server.cast(pid, { :add_external, :import, function, module, target })
end
# Associates a module with a warn. This adds the given
# module and associates it with the `:import` vertex
# permanently, even if warn is false.
@doc false
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
:gen_server.cast(pid, { :add_warnable, module, warn, line })
end
# Collect all unused imports where warn has been set to true.
def collect_unused_imports(pid) do
d = :gen_server.call(pid, :digraph, @timeout)
warnable = :digraph.out_neighbours(d, :warn)
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
{ mod, line }
end
end
defp get_warn_line(d, mod) do
[edge] = :digraph.out_edges(d, mod)
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
line
end
defp has_imports?(d, mod) do
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
end
# Yanks a local node. Returns its in and out vertices in a tuple.
@doc false
def yank(pid, local) do
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
end
# Reattach a previously yanked node
@doc false
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
@@ -282,7 +217,7 @@ defmodule Module.DispatchTracker do
@doc false
def collect_unused_locals(pid, private) do
reachable = reachable(pid)
:lists.foldl(collect_unused_locals(&1, &2, reachable), [], private)
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
end
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
@@ -322,10 +257,6 @@ defmodule Module.DispatchTracker do
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
:digraph.add_vertex(d, :alias)
:digraph.add_vertex(d, :import)
:digraph.add_vertex(d, :remote)
:digraph.add_vertex(d, :warn)
{ :ok, d }
end
@@ -353,30 +284,11 @@ defmodule Module.DispatchTracker do
{ :noreply, d }
end
def handle_cast({ :add_alias, module }, d) do
:digraph.add_vertex(d, module)
replace_edge!(d, :alias, module)
{ :noreply, d }
end
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
handle_import_or_remote(d, kind, function, module, name, arity)
{ :noreply, d }
end
def handle_cast({ :add_warnable, module, warn, line }, d) do
:digraph.add_vertex(d, module)
replace_edge!(d, :import, module)
if warn do
:digraph.add_edge(d, :warn, module, line)
:digraph.add_edge(d, module, :warn, line)
else
:digraph.del_path(d, :warn, module)
end
{ :noreply, d }
end
def handle_cast({ :add_definition, kind, tuple }, d) do
handle_add_definition(d, kind, tuple)
{ :noreply, d }
@@ -414,7 +326,6 @@ defmodule Module.DispatchTracker do
defp handle_import_or_remote(d, kind, function, module, name, arity) do
:digraph.add_vertex(d, module)
replace_edge!(d, kind, module)
tuple = { kind, name, arity }
:digraph.add_vertex(d, tuple)
+2 -2
View File
@@ -156,12 +156,12 @@ defmodule OptionParser do
:boolean in kinds ->
{ nil, value in [true, "true"] }
:integer in kinds ->
case String.to_integer(value) do
case Integer.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
:float in kinds ->
case String.to_float(value) do
case Float.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
+3 -3
View File
@@ -304,11 +304,11 @@ defmodule Process do
end
@doc """
Sets the group leader of Pid to GroupLeader. Typically, this is used when a processes
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
started from a certain shell should have another group leader than `:init`.
"""
@spec group_leader(leader :: pid, pid) :: true
def group_leader(leader, pid) do
@spec group_leader(pid, leader :: pid) :: true
def group_leader(pid, leader) do
:erlang.group_leader(leader, pid)
end
+161 -308
View File
@@ -1,58 +1,93 @@
defmodule Protocol do
@moduledoc false
# We need to use :lists because Enum is not available yet
require :lists, as: L
@doc """
Handle `defprotocol`. It will define a function for each
protocol plus two extra functions:
* `__protocol__/1` - returns the protocol name when :name is given,
and a keyword list with the protocol functions
when :functions is given;
* `__impl_for__/1` - receives one argument and returns a module
that implements the protocol for the given
data type. If no implementation matches, returns nil;
* `__impl_for__!/1` - same as above but raises an error if an implementation is not found
"""
# Callback for defprotocol.
@doc false
def defprotocol(name, [do: block]) do
quote do
defmodule unquote(name) do
# We don't allow function definition inside protocols
import Kernel, except: [
defmacrop: 1, defmacrop: 2, defmacrop: 4,
defmacro: 1, defmacro: 2, defmacro: 4,
defp: 1, defp: 2, defp: 4,
def: 1, def: 2, def: 4
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
defp: 1, defp: 2, def: 1, def: 2
]
# Import the new dsl that holds the new def
import Protocol.DSL, only: :macros
# Compile with debug info for consolidation
@compile :debug_info
# Set up a clear slate to store defined functions
@functions []
@fallback_to_any false
# Deprecated
@only nil
@except nil
# Invoke the user given block
unquote(block)
# Define callbacks and meta information
{ conversions, fallback, returns_nil } = Protocol.conversions_for(__MODULE__, @only, @except)
Protocol.impl_for(conversions, fallback, returns_nil, __ENV__)
Protocol.meta(@functions, conversions, fallback, returns_nil, __ENV__)
# Finalize expansion
unquote(after_defprotocol)
end
end
end
@doc """
Implement the given protocol for the given module.
It also defines a `__impl__` function which
returns the protocol being implemented.
"""
defp after_defprotocol do
quote unquote: false do
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
@spec impl_for(term) :: module | nil
Kernel.def impl_for(data)
lc { guard, body } inlist bodies do
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
end
@spec impl_for!(term) :: module | no_return
Kernel.def impl_for!(data) do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
# Handle special Record type
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
# Handle special Any type
if @fallback_to_any do
Kernel.defp any_impl_for do
try do
__MODULE__.Any.__impl__(:name)
catch
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
nil
end
end
else
Kernel.defp any_impl_for, do: nil
end
# Inline both helpers
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
# Store information as an attribute so it
# can be read without loading the module.
Module.register_attribute(__MODULE__, :protocol, persist: true)
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
# Callback for defimpl.
@doc false
def defimpl(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts))
end
@@ -76,6 +111,10 @@ defmodule Protocol do
unquote(block)
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
@doc false
def __impl__(:name), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
@@ -100,203 +139,88 @@ defmodule Protocol do
end
end
# Implements the function that detects the protocol and returns
# the module to dispatch to. Returns module.Record for records
# which should be properly handled by the dispatching function.
# Builtin types.
@doc false
def impl_for(conversions, fallback, returns_nil, env) do
contents = lc kind inlist conversions do
each_impl_for(kind, conversions, fallback)
end
if returns_nil do
contents = contents ++ [quote do
defp __raw_impl__(_) do
nil
end
end]
end
Module.eval_quoted env.module, contents, [], env.location
def builtin do
[ Tuple, Atom, List, BitString, Integer, Float,
Function, PID, Port, Reference, Any ]
end
# Defines meta information about the protocol and internal callbacks.
# Implements the function that detects the protocol and
# returns the module to dispatch to.
@doc false
def meta(functions, conversions, fallback, returns_nil, env) do
any = L.keyfind(Any, 1, conversions) != false
records = L.keyfind(Record, 1, conversions) != false
def impl_for(current) do
arg = quote(do: arg)
all = [Record|builtin]
meta = quote location: :keep do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: unquote(generate_type(conversions, any))
end
@doc false
def __protocol__(:name), do: __MODULE__
def __protocol__(:functions), do: unquote(:lists.sort(functions))
end
impl_for = cond do
records and fallback ->
quote location: :keep do
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
target = Module.concat(__MODULE__, :erlang.element(1, arg))
try do
target.__impl__
target
catch
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
unquote(fallback)
end
other ->
other
end
end
end
records ->
quote location: :keep do
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
Module.concat(__MODULE__, :erlang.element(1, arg))
other ->
other
end
end
end
true ->
quote location: :keep do
def __impl_for__(arg), do: __raw_impl__(arg)
end
end
impl_bang = if returns_nil do
quote do
def __impl_for__!(arg) do
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: arg)
end
end
else
quote do
def __impl_for__!(arg), do: __impl_for__(arg)
end
end
Module.eval_quoted env.module, [meta, impl_for, impl_bang], [], env.location
{ arg,
lc(mod inlist all, do: impl_for(current, mod, arg)),
rec_impl_for(current, arg) }
end
# Returns the default conversions according to the given
# only/except options.
@doc false
def conversions_for(module, only, except) do
kinds = all_types
defp rec_impl_for(current, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
conversions =
if only do
L.map(fn i -> L.keyfind(i, 1, kinds) end, only)
else
except = except || [Any]
L.foldl(fn i, list -> L.keydelete(i, 1, list) end, kinds, except)
end
fallback = cond do
L.keyfind(Tuple, 1, conversions) ->
Module.concat module, Tuple
L.keyfind(Any, 1, conversions) ->
Module.concat module, Any
true ->
nil
end
# If any is not in the list and we don't implement all
# protocols, we need to handle nil cases.
returns_nil = L.keyfind(Any, 1, conversions) == false and length(conversions) < 10
{ conversions, fallback, returns_nil }
end
## Helpers
defp generate_type(_conversions, true) do
quote(do: any)
end
defp generate_type(conversions, false) do
or_function = fn({ _, _, x }, acc) -> { :|, [], [acc, x] } end
{ _, _, first } = hd(conversions)
:lists.foldl(or_function, first, tl(conversions))
end
defp all_types do
[ { Record, :is_record, quote do: tuple },
{ Tuple, :is_tuple, quote do: tuple },
{ Atom, :is_atom, quote do: atom },
{ List, :is_list, quote do: list },
{ BitString, :is_bitstring, quote do: <<>> },
{ Number, :is_number, quote do: number },
{ Function, :is_function, quote do: (... -> any) },
{ PID, :is_pid, quote do: pid },
{ Port, :is_port, quote do: port },
{ Reference, :is_reference, quote do: reference },
{ Any, :is_any, quote do: any } ]
end
# Returns a quoted expression that allows to check
# if the first item in the tuple is a built-in or not.
defp is_builtin?([{h, _, _}]) do
quote do
first == unquote(h)
end
end
defp is_builtin?([{h, _, _}|t]) do
quote do
first == unquote(h) or unquote(is_builtin?(t))
end
end
# We don't have a fallback, so we assume what was given
# as a record is indeed a record
defp each_impl_for({ _, :is_record, _ }, _conversions, nil) do
quote do
defp __raw_impl__(arg) when is_record(arg) do
__MODULE__.Record
target = Module.concat(unquote(current), unquote(arg))
try do
target.__impl__(:name)
catch
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
unquote(fallback)
end
end
end
# Specially handle records in the case we have fallbacks.
defp each_impl_for({ _, :is_record, _ }, conversions, fallback) do
quote do
defp __raw_impl__(arg) when is_record(arg) do
first = :erlang.element(1, arg)
case unquote(is_builtin?(conversions)) do
true -> unquote(fallback)
false ->
case atom_to_list(first) do
'Elixir.' ++ _ -> __MODULE__.Record
_ -> unquote(fallback)
end
end
defp impl_for(current, Record, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
dispatch = quote do
atom = :erlang.element(1, unquote(arg))
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
true -> rec_impl_for(atom)
false -> unquote(fallback)
end
end
quote do
{ is_record(unquote(arg)), unquote(dispatch) }
end
end
# Special case any as we don't need to generate a guard.
defp each_impl_for({ _, :is_any, _ }, _, _) do
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, arg)
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, arg)
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, arg)
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, arg)
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, arg)
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, arg)
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, arg)
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, arg)
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, arg)
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, arg)
defp impl_for(_current, Any, _arg) do
{ true, quote(do: any_impl_for) }
end
# Defines an implementation with fallback to the given module.
defp impl_with_fallback(mod, guard, current, arg) do
quote do
defp __raw_impl__(_) do
__MODULE__.Any
end
{ unquote(guard)(unquote(arg)),
unquote(with_fallback(Module.concat(current, mod))) }
end
end
# Generate all others protocols.
defp each_impl_for({ kind, fun, _ }, _, _) do
# Tries to dispatch to a given target, fallbacks to the
# given `fallback` implementation if the target does not exist.
defp with_fallback(target) when is_atom(target) do
quote do
defp __raw_impl__(arg) when unquote(fun)(arg) do
__MODULE__.unquote(kind)
try do
unquote(target).__impl__(:name)
catch
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
any_impl_for
end
end
end
@@ -305,36 +229,45 @@ end
defmodule Protocol.DSL do
@moduledoc false
# We need to use :lists because Enum is not available yet
require :lists, as: L
@doc false
def args_and_body(module, name, arity) do
# Generate arguments according the arity. The arguments
# are named xa, xb and so forth. We cannot use string
# interpolation to generate the arguments because of compile
# dependencies, so we use the <<>> instead.
args = lc i inlist :lists.seq(1, arity) do
{ binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
end
defmacro def({ _, _, args }) when args == [] or is_atom(args) do
raise ArgumentError, message: "protocol functions expect at least one argument"
end
{ conversions, fallback, returns_nil } = conversions_for(module)
clauses = [default_clause(name, args)]
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
arity = length(args)
if returns_nil do
clauses = [nil_clause()|clauses]
end
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
if L.keyfind(Record, 1, conversions) do
clauses = [record_clause(name, args, fallback)|clauses]
end
call_args = lc i inlist :lists.seq(2, arity),
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
call_args = [quote(do: t) | call_args]
body =
quote do
case __raw_impl__(xA), do: unquote({ :->, [], clauses })
quote do
name = unquote(name)
arity = unquote(arity)
@functions [{name, arity}|@functions]
# Generate a fake definition with the user
# signature that will be used by docs
Kernel.def unquote(name)(unquote_splicing(args))
# Generate the actual implementation
Kernel.def unquote(name)(unquote_splicing(call_args)) do
impl_for!(t).unquote(name)(unquote_splicing(call_args))
end
{ args, body }
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
end
defmacro def(_) do
raise ArgumentError, message: "invalid args for def inside defprotocol"
end
@doc false
@@ -349,84 +282,4 @@ defmodule Protocol.DSL do
found != []
end
defp conversions_for(module) do
only = Module.get_attribute(module, :only)
except = Module.get_attribute(module, :except)
Protocol.conversions_for(module, only, except)
end
defp default_clause(name, args) do
{ [quote do: other],
[],
quote(do: apply(other, unquote(name), [unquote_splicing(args)])) }
end
defp nil_clause() do
{ [nil],
[],
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, value: xA)) }
end
defp record_clause(name, args, nil) do
{ [quote(do: __MODULE__.Record)],
[],
quote(do: Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args))) }
end
defp record_clause(name, args, fallback) do
arity = length(args)
{ [quote(do: __MODULE__.Record)],
[],
quote do
target = Module.concat(__MODULE__, :erlang.element(1, xA))
try do
target.unquote(name)(unquote_splicing(args))
catch
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
name == unquote(name) and length(args) == unquote(arity) ->
apply unquote(fallback), name, [unquote_splicing(args)]
end
end }
end
defmacro def(expression) do
case expression do
{ _, _, args } when args == [] or is_atom(args) ->
raise ArgumentError, message: "protocol functions expect at least one argument"
{ name, _, args } when is_atom(name) and is_list(args) ->
:ok
_ ->
raise ArgumentError, message: "invalid args for defprotocol"
end
arity = length(args)
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
meta = quote do
name = unquote(name)
arity = unquote(arity)
@functions [{name, arity}|@functions]
# Generate a fake definition with the user
# signature that will be used by docs
Kernel.def unquote(name)(unquote_splicing(args))
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
impl = quote bind_quoted: [name: name, arity: arity] do
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
Kernel.def unquote(name)(unquote_splicing(args)), do: unquote(body)
end
[meta, impl]
end
end
+266
View File
@@ -0,0 +1,266 @@
defmodule Protocol.Consolidation do
@moduledoc """
Module responsible for consolidating protocols and helpers for
extracting protocols and implementations from code paths for
consolidation.
"""
@doc """
Extract all protocols from the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_protocols([path])
iex> Enumerable in mods
true
"""
@spec extract_protocols([char_list | String.t]) :: [atom]
def extract_protocols(paths) do
extract_matching_by_attribute paths, 'Elixir.',
fn module, attributes ->
case attributes[:protocol] do
[fallback_to_any: _, consolidated: _] -> module
_ -> nil
end
end
end
@doc """
Extract all types implemented for the given protocol from
the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
iex> List in mods
true
"""
@spec extract_impls(module, [char_list | String.t]) :: [atom]
def extract_impls(protocol, paths) when is_atom(protocol) do
prefix = atom_to_list(protocol) ++ '.'
extract_matching_by_attribute paths, prefix, fn
_mod, attributes ->
case attributes[:impl] do
[protocol: ^protocol, for: for] -> for
_ -> nil
end
end
end
defp extract_matching_by_attribute(paths, prefix, callback) do
lc path inlist paths,
file inlist list_dir(path),
mod = extract_from_file(path, file, prefix, callback),
do: mod
end
defp list_dir(path) when is_list(path) do
case :file.list_dir(path) do
{ :ok, files } -> files
_ -> []
end
end
defp list_dir(path), do: list_dir(to_char_list(path))
defp extract_from_file(path, file, prefix, callback) do
if :lists.prefix(prefix, file) and Path.extname(file) == '.beam' do
extract_from_beam(Path.join(path, file), callback)
end
end
defp extract_from_beam(file, callback) do
case :beam_lib.chunks(file, [:attributes]) do
{:ok, { module, [attributes: attributes] } } ->
callback.(module, attributes)
_ ->
nil
end
end
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{ :ok, var } -> unquote(Macro.pipe(quote(do: var), call))
other -> other
end
end
end
@doc """
Receives a protocol and a list of implementations and
consolidates the given protocol. Consolidation happens
by changing the protocol `impl_for` in the abstract
format to have fast lookup rules.
It returns the updated version of the protocol bytecode.
A given bytecode or protocol implementation can be checked
to be consolidated or not by analyzing the protocol
attribute:
Enumerable.__info__(:attributes)[:protocol]
If the first element of the tuple is true, it means
the protocol was consolidated.
This function does not load the protocol at any point
nor loads the new bytecode for the compiled module.
"""
@spec apply_to(module, [module]) ::
{ :ok, binary } |
{ :error, :not_a_protocol } |
{ :error, :no_beam_info }
def apply_to(protocol, types) when is_atom(protocol) do
ensure_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
end
# Ensure the given module is loaded and is a protocol.
defp ensure_protocol(protocol) do
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
{ :ok, { ^protocol, [abstract_code: { _raw, abstract_code },
attributes: attributes] } } ->
case attributes[:protocol] do
[fallback_to_any: any, consolidated: _] ->
{ :ok, { protocol, any, abstract_code } }
_ ->
{ :error, :not_a_protocol }
end
_ ->
{ :error, :no_beam_info }
end
end
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
end
end
# Change the debug information to the optimized
# impl_for/1 dispatch version.
defp change_debug_info({ protocol, any, code }, types) do
types = if any, do: types, else: List.delete(types, Any)
records = types -- Protocol.builtin
builtin = Protocol.builtin -- (Protocol.builtin -- types)
builtin = if records != [], do: [Record|builtin], else: builtin
change_impl_for(code, protocol, builtin, records, false, [])
end
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
attr = [fallback_to_any: Any in builtin, consolidated: true]
change_impl_for(t, protocol, builtin, records, true,
[{ :attribute, line, :protocol, attr }|acc])
end
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
unless Any in builtin do
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
end
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :impl_for, 1, clauses }|acc])
end
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
end
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
if is_protocol do
{ :ok, { protocol, Enum.reverse(acc) } }
else
{ :error, :not_a_protocol }
end
end
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
defp clause_for(Any, protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, Module.concat(protocol, Any) }]}
end
defp clause_for(Record, _protocol, line) do
{:clause, line, [{:var, line, :x}],
[[{:op, line, :andalso,
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
[{:var, line, :x}]},
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]
}]},
}]],
[{:call, line,
{:atom, line, :rec_impl_for},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]}]}]}
end
defp builtin_clause_for(mod, guard, protocol, line) do
{:clause, line,
[{:var, line, :x}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
[{:var, line, :x}],
}]],
[{:atom, line, Module.concat(protocol, mod)}]}
end
defp record_clause_for(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, Module.concat(protocol, other)}]}
end
defp fallback_clause_for(value, _protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, value }]}
end
# Finally compile the module and emit its bytecode.
defp compile({ protocol, code }) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{ :ok, ^protocol, binary, _warnings } = :compile.forms(code, [:return|opts])
{ :ok, binary }
end
end
+8 -8
View File
@@ -17,21 +17,21 @@ defprotocol Range.Iterator do
end
defimpl Enumerable, for: Range do
def reduce(Range[first: first] = range, acc, fun) do
def reduce(first .. _ = range, acc, fun) do
Range.Iterator.reduce(first, range, acc, fun)
end
def member?(Range[first: first, last: last], value) do
value in first..last
def member?(first .. last, value) do
first <= value and value <= last
end
def count(Range[first: first] = range) do
def count(first .. _ = range) do
Range.Iterator.count(first, range)
end
end
defimpl Range.Iterator, for: Number do
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
defimpl Range.Iterator, for: Integer do
def reduce(first, Range[last: last], acc, fun) when is_integer(last) do
reducer = if last >= first do
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
else
@@ -56,11 +56,11 @@ defimpl Range.Iterator, for: Number do
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
def count(first, Range[last: last]) when is_integer(last) and last >= first do
last - first + 1
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
def count(first, Range[last: last]) when is_integer(last) do
first - last + 1
end
end
+103 -56
View File
@@ -1,19 +1,19 @@
defmodule Record do
@moduledoc %S"""
Convenience functions for working with Records.
Functions for working with Records.
A record is a tagged tuple which contains one or more elements
where the first element is an atom. We can manually create a record
by simply defining such a tuple:
iex> record = { User, "josé", 25 }
iex> record = { User, "José", 25 }
iex> is_record(record, User)
true
However, manually constructing tuples can be quite error prone.
In case we need to add a new field to our User, it would require
If we need to add a new field to our User, it would require
us to carefully change all places where the tuple is used.
Furthermore, as more and more items are added to the tuple, they
Furthermore, as more items are added to the tuple, they
lose semantic value.
This module solves these problems by allowing us to name each
@@ -38,7 +38,7 @@ defmodule Record do
In the example above, `defrecordp` is going to generate a set of
macros named `user` that allows us to create, update and match
on a record. Our record is going to have two fields, a `name` with
default value of "José" and `age` of 25.
default value of "José" and `age` with default value 25.
Let's see some examples:
@@ -75,10 +75,7 @@ defmodule Record do
As the name says, `defrecordp` is useful when you don't want to
expose the record definition. The `user` macro used above, for
example, is only available inside the `User` module and nowhere else.
You can find more information in `Kernel.defrecordp/2` docs.
In general, though, records are used as part of a module's public
interface. For such use cases, Elixir provides record modules.
You can find more information in `Kernel.defrecordp/3` docs.
## defrecord
@@ -97,7 +94,7 @@ defmodule Record do
user() #=> User[name: "José", age: 25]
user(age: 26) #=> User[name: "José", age: 26]
Notice that now, since the record definition is accessible, Elixir
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`:
@@ -112,9 +109,9 @@ defmodule Record do
User[] #=> User[name: "José", age: 25]
User[age: 26] #=> User[name: "José", age: 26]
The macro name is replaced by the module name and the parenthesis
are replaced by brackets. When the shortcut syntax is used, there
is no need to import the record.
The macro name is replaced by the module name and the parentheses
are replaced by brackets. When this syntax is used, there is no
need to import the record.
Before we sum up the differences between `defrecord` and
`defrecordp`, there is one last functionality introduced by
@@ -123,12 +120,12 @@ defmodule Record do
## Runtime access
All the functionality discussed above happens at compilation time.
This means that both `user(user, age: 26)` and `User[user, age: 26]`
will be transformed into a simple tuple operation at compile time.
This means that both `user(age: 26)` and `User[age: 26]` are expanded
into a tuple at compile time.
However, there are some situations where we want to set or update
fields dynamically. `defrecord` (and `defrecord` only) supports
it out of the box:
fields dynamically. `defrecord` (and not `defrecordp` ) supports
this behaviour out of the box:
defrecord User, name: "José", age: 25
@@ -138,14 +135,23 @@ defmodule Record do
user.update(age: 26)
#=> User[name: "Hello", age: 26]
All the calls above happen at runtime. It gives Elixir
records flexibility at the cost of performance since
there is more work happening at runtime.
All the calls above happen at runtime. It gives Elixir records
flexibility at the cost of performance since there is more work
happening at runtime.
To sum up, `defrecordp` should be used when you don't want
to expose the record information while `defrecord` should be used
whenever you want to share a record within your code or with other
libraries or whenever you need to dynamically set or update fields.
The above calls (`new` and `update`) can accept both atom and string
keys for field names, however not both at the same time. This feature
allows to "sanitize" untrusted dictionaries and initialize/update
records without using `Kernel.binary_to_existing_atom/1`.
To sum up, `defrecordp` should be used when you don't want to expose
the record information while `defrecord` should be used whenever you
want to share a record within your code or with other libraries or
whenever you need to dynamically set or update fields.
The standard library contains excellent examples of both use cases,
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
and [`Range`](Range.html) with `defrecord`.
You can learn more about records in the `Kernel.defrecord/3` docs. Now
let's discuss the usefulness of combining records with protocols.
@@ -170,22 +176,30 @@ defmodule Record do
end
end
Now we can explicitly convert our WeekDate:
Now we can explicitly convert our `WeekDate`:
to_string WeekDate[year: 2013, week: 26, week_day: 4]
"2013-W26-4"
A protocol can be implemented for any record defined via `defrecord`.
A protocol can be implemented for any record, whether
generated with `defrecordp` or `defrecord`.
"""
@type t :: tuple
@doc """
Extract record information from an Erlang file and
return the fields as a list of tuples.
Extract record information from an Erlang file.
Returns the fields as a list of tuples.
## Examples
> Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
[size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
major_device: :undefined, minor_device: :undefined, inode: :undefined,
uid: :undefined, gid: :undefined]
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
"""
@@ -194,8 +208,9 @@ defmodule Record do
end
@doc """
Imports a public record definition as a set of private macros,
as defined by `Kernel.defrecordp/2`. This is useful when one
Import a public record definition as a set of private macros.
Macros defined as in `Kernel.defrecordp/3`. This is useful when one
desires to manipulate a record via a set of macros instead
of the regular access syntax.
@@ -231,14 +246,14 @@ defmodule Record do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
import Elixir.Record.DSL
import Record.DSL
@record_fields []
@record_types []
Elixir.Record.deffunctions(unquoted_fields, __ENV__)
Record.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Elixir.Record.deftypes(@record_fields, @record_types, __ENV__)
Record.deftypes(@record_fields, @record_types, __ENV__)
value
end
end
@@ -280,8 +295,9 @@ defmodule Record do
end
@doc """
Defines record functions skipping the module definition.
This is called directly by `defrecord`. It expects the record
Define record functions skipping the module definition.
This is called directly by `Kernel.defrecord/3`. It expects the record
values, a set of options and the module environment.
## Examples
@@ -308,7 +324,7 @@ defmodule Record do
contents = [quote(do: @record_fields unquote(escaped))|contents]
# Special case for bootstraping purposes
# Special case for bootstrapping purposes
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
@@ -317,7 +333,7 @@ defmodule Record do
end
@doc """
Defines types and specs for the record.
Define types and specs for the record.
"""
def deftypes(values, types, env) do
types = types || []
@@ -339,8 +355,10 @@ defmodule Record do
end
@doc """
Defines macros for manipulating records. This is called
directly by `defrecordp`. It expects the macro name, the
Define macros for manipulating records.
This is called
directly by `Kernel.defrecordp/3`. It expects the macro name, the
record values and the environment.
## Examples
@@ -414,7 +432,7 @@ defmodule Record do
@doc false
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
raise ArgumentError, message: "expected contents inside brackets to be a keyword list, got: #{inspect keyword}"
end
in_match = caller.in_match?
@@ -627,9 +645,8 @@ defmodule Record do
# an ordered dict of options (opts) and it will try to fetch
# the given key from the ordered dict, falling back to the
# default value if one does not exist.
selective = lc { k, v } inlist values do
quote do: Keyword.get(opts, unquote(k), unquote(v))
end
atom_selective = lc { k, v } inlist values, do: initialize_lookup(k, v)
string_selective = lc { k, v } inlist values, do: initialize_lookup(atom_to_binary(k), v)
quote do
@doc false
@@ -637,7 +654,17 @@ defmodule Record do
@doc false
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
def new(opts) when is_list(opts), do: { __MODULE__, unquote_splicing(selective) }
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
end
end
defp initialize_lookup(k, v) do
quote do
case :lists.keyfind(unquote(k), 1, opts) do
false -> unquote(v)
{_, v} -> v
end
end
end
@@ -728,15 +755,14 @@ defmodule Record do
# Define an updater method that receives a
# keyword list and updates the record.
defp updater(values) do
fields =
lc {key, _default} inlist values do
index = find_index(values, key, 1)
quote do
Keyword.get(keywords, unquote(key), elem(record, unquote(index)))
end
end
atom_fields =
lc {key, _default} inlist values, do: updater_lookup(key, key, values)
contents = quote do: { __MODULE__, unquote_splicing(fields) }
string_fields =
lc {key, _default} inlist values, do: updater_lookup(atom_to_binary(key), key, values)
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
quote do
@doc false
@@ -744,8 +770,22 @@ defmodule Record do
record
end
def update(keywords, record) do
unquote(contents)
def update([{key, _}|_] = keywords, record) when is_atom(key) do
unquote(atom_contents)
end
def update([{key, _}|_] = keywords, record) when is_binary(key) do
unquote(string_contents)
end
end
end
defp updater_lookup(k, key, values) do
v = find_index(values, key, 1)
quote do
case :lists.keyfind(unquote(k), 1, keywords) do
false -> elem(record, unquote(v))
{_, value} -> value
end
end
end
@@ -768,6 +808,7 @@ defmodule Record do
defp core_specs(values) do
types = lc { _, _, spec } inlist values, do: spec
options = if values == [], do: [], else: [options_specs(values)]
values_specs = if values == [], do: [], else: values_specs(values)
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@@ -775,7 +816,7 @@ defmodule Record do
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@type options :: unquote(options)
@type options :: unquote(options) | [{String.t, unquote(values_specs)}]
end
@spec new :: t
@@ -793,6 +834,11 @@ defmodule Record do
{ :|, [], [{ k, v }, acc] }
end, { k, v }, t
end
defp values_specs([{ _, _, v }|t]) do
:lists.foldl fn { _, _, v }, acc ->
{ :|, [], [v, acc] }
end, v, t
end
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
accessor_specs(t, 2, acc)
@@ -814,7 +860,7 @@ defmodule Record do
## Helpers
defp is_keyword(list) when is_list(list), do: :lists.all(is_keyword_tuple(&1), list)
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
defp is_keyword(_), do: false
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
@@ -870,6 +916,7 @@ defmodule Record.DSL do
@doc """
Defines the type for each field in the record.
Expects a keyword list.
"""
defmacro record_type(opts) when is_list(opts) do
+2 -2
View File
@@ -19,7 +19,7 @@ defmodule Record.Extractor do
def retrieve(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(String.to_char_list!(file))
case :code.lib_dir(app) do
case :code.lib_dir(list_to_atom(app)) do
{ :error, _ } ->
raise ArgumentError, message: "lib file #{file} could not be found"
libpath ->
@@ -88,4 +88,4 @@ defmodule Record.Extractor do
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
record
end
end
end
+36 -19
View File
@@ -1,22 +1,21 @@
defmodule Regex do
@moduledoc %S"""
Regular expressions for Elixir built on top of the re module
Regular expressions for Elixir built on top of the `re` module
in the Erlang Standard Library. More information can be found
in the re documentation: http://www.erlang.org/doc/man/re.html
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
Regular expressions in Elixir can be created using Regex.compile!
Regular expressions in Elixir can be created using `Regex.compile!`
or using the special form with `%r`:
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
# A regular expression with case insensitive options and handling for unicode chars
%r/foo/iu
# A regular expression with case insensitive options
%r/foo/i
The re module provides several options, the ones available in Elixir, followed by
The `re` module provides several options, the ones available in Elixir, followed by
their shortcut in parenthesis, are:
* `unicode` (u) - enable unicode specific patterns like \p
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
@@ -27,8 +26,8 @@ defmodule Regex do
allow `#` to delimit comments
* `firstline` (f) - forces the unanchored pattern to match before or at the first
newline, though the matched text may continue over the newline
* `ungreedy` (r) - invert the "greediness" of the regexp
* `groups` (g) - compile with info about groups available
* `ungreedy` (r) - inverts the "greediness" of the regexp
* `groups` (g) - compiles with info about groups available
The options not available are:
@@ -44,7 +43,7 @@ defmodule Regex do
"""
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, binary, binary, [atom] }
@type t :: { Regex, term, binary, binary, [atom] | nil }
defexception CompileError, message: "regex could not be compiled"
@@ -53,12 +52,20 @@ defmodule Regex do
The given options can either be a binary with the characters
representing the same regex options given to the `%r` sigil,
or a list of options, as expected by the Erlang `re` docs.
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
It returns `{ :ok, regex }` in case of success,
`{ :error, reason }` otherwise.
## Examples
iex> Regex.compile("foo")
{:ok, %r"foo"}
iex> Regex.compile("*foo")
{:error, {'nothing to repeat', 0}}
"""
@spec compile(binary, binary | [term]) :: t
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
def compile(source, options // "")
def compile(source, options) when is_binary(options) do
@@ -67,7 +74,9 @@ defmodule Regex do
{ :error, { :invalid_option, rest } }
translated_options ->
compile(source, translated_options, options)
# Always use the unicode option, we don't have a latin1 legacy like
# Erlang.
compile(source, [:unicode|translated_options], options)
end
end
@@ -154,15 +163,15 @@ defmodule Regex do
## Examples
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
[foo: "d"]
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
[foo: "b", bar: "d"]
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
nil
"""
def captures(regex(groups: groups) = regex, string, options // []) do
def named_captures(regex(groups: groups) = regex, string, options // []) do
options = Keyword.put_new(options, :capture, :groups)
results = run(regex, string, options)
if results, do: Enum.zip(groups, results)
@@ -249,13 +258,14 @@ defmodule Regex do
options = [{ :capture, captures, return }, :global]
case :re.run(string, compiled, options) do
:match -> []
:nomatch -> []
{ :match, results } -> results
end
end
@doc """
Split the given target into the number of parts specified.
Splits the given target into the number of parts specified.
If no number of parts is given, it defaults to `:infinity`.
## Examples
@@ -265,6 +275,10 @@ defmodule Regex do
["a","b-c"]
iex> Regex.split(%r/-/, "abc")
["abc"]
iex> Regex.split(%r//, "abc")
["a", "b", "c", ""]
iex> Regex.split(%r//, "abc", trim: true)
["a", "b", "c"]
"""
def split(regex, string, options // [])
@@ -354,7 +368,10 @@ defmodule Regex do
defp return_for(element) when is_binary(element), do: :binary
defp return_for(element) when is_list(element), do: :list
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?u, t :: binary>>) do
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
translate_options(t)
end
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
+15
View File
@@ -15,6 +15,21 @@ defmodule Set do
For simplicity's sake, in the examples below every time
`new` is used, it implies one of the module-specific
calls like above.
## Protocols
Sets are required to implement the `Enumerable` protocol,
allowing one to write:
Enum.each(set, fn k ->
IO.inspect k
end)
## Match
Sets are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
"""
use Behaviour
+63 -21
View File
@@ -8,7 +8,7 @@ defmodule Stream do
iex> range = 1..5
1..5
iex> Enum.map range, &1 * 2
iex> Enum.map range, &(&1 * 2)
[2,4,6,8,10]
In the example above, as we mapped over the range, the elements being
@@ -16,8 +16,8 @@ defmodule Stream do
module allows us to map the range, without triggering its enumeration:
iex> range = 1..3
iex> stream = Stream.map(range, &1 * 2)
iex> Enum.map(stream, &1 + 1)
iex> stream = Stream.map(range, &(&1 * 2))
iex> Enum.map(stream, &(&1 + 1))
[3,5,7]
Notice we started with a range and then we created a stream that is
@@ -34,9 +34,9 @@ defmodule Stream do
example:
1..3 |>
Enum.map(IO.inspect(&1)) |>
Enum.map(&1 * 2) |>
Enum.map(IO.inspect(&1))
Enum.map(&IO.inspect(&1)) |>
Enum.map(&(&1 * 2)) |>
Enum.map(&IO.inspect(&1))
1
2
3
@@ -50,9 +50,9 @@ defmodule Stream do
was iterated three times. Let's see an example with streams:
stream = 1..3 |>
Stream.map(IO.inspect(&1)) |>
Stream.map(&1 * 2) |>
Stream.map(IO.inspect(&1))
Stream.map(&IO.inspect(&1)) |>
Stream.map(&(&1 * 2)) |>
Stream.map(&IO.inspect(&1))
Enum.to_list(stream)
1
2
@@ -81,8 +81,8 @@ defmodule Stream do
This module also allows us to create streams from any enumerable:
iex> stream = Stream.map([1,2,3], &1 * 2)
iex> Enum.map(stream, &1 + 1)
iex> stream = Stream.map([1,2,3], &(&1 * 2))
iex> Enum.map(stream, &(&1 + 1))
[3,5,7]
By simply passing a list (which is an enumerable) as the first argument
@@ -187,7 +187,7 @@ defmodule Stream do
"""
@spec cycle(Enumerable.t) :: t
def cycle(enumerable) do
do_cycle(enumerable, &1, &2)
&do_cycle(enumerable, &1, &2)
end
defp do_cycle(enumerable, acc, fun) do
@@ -225,7 +225,7 @@ defmodule Stream do
## Examples
iex> stream = Stream.drop_while(1..10, &1 <= 5)
iex> stream = Stream.drop_while(1..10, &(&1 <= 5))
iex> Enum.to_list(stream)
[6,7,8,9,10]
@@ -271,7 +271,7 @@ defmodule Stream do
## Examples
iex> Stream.iterate(0, &1+1) |> Enum.take(5)
iex> Stream.iterate(0, &(&1+1)) |> Enum.take(5)
[0,1,2,3,4]
"""
@@ -325,12 +325,30 @@ defmodule Stream do
def flat_map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
Enumerable.reduce(f.(entry), acc, f1)
end
fn(entry, acc) -> do_flat_map(f.(entry), acc, f1) end
end]
end
defp do_flat_map(Lazy[] = lazy, acc, f1) do
try do
Enumerable.reduce(lazy, acc, fn x, y ->
try do
f1.(x, y)
catch
{ :stream_lazy, nesting, rest } ->
throw({ :stream_flat_map, nesting, rest })
end
end)
catch
{ :stream_flat_map, nesting, rest } ->
throw({ :stream_lazy, nesting, rest })
end
end
defp do_flat_map(enum, acc, f1) do
Enumerable.reduce(enum, acc, f1)
end
@doc """
Creates a stream that will reject elements according to
the given function on enumeration.
@@ -352,7 +370,6 @@ defmodule Stream do
end]
end
@doc """
Returns a stream generated by calling `generator_fun` repeatedly.
@@ -365,7 +382,7 @@ defmodule Stream do
@spec repeatedly((() -> element)) :: t
def repeatedly(generator_fun)
when is_function(generator_fun, 0) do
do_repeatedly(generator_fun, &1, &2)
&do_repeatedly(generator_fun, &1, &2)
end
defp do_repeatedly(generator_fun, acc, fun) do
@@ -388,7 +405,7 @@ defmodule Stream do
"""
@spec take(Enumerable.t, non_neg_integer) :: t
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: & &1]
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: &(&1)]
def take(enumerable, n) when n > 0 do
Lazy[enumerable: enumerable,
@@ -407,7 +424,7 @@ defmodule Stream do
## Examples
iex> stream = Stream.take_while(1..100, &1 <= 5)
iex> stream = Stream.take_while(1..100, &(&1 <= 5))
iex> Enum.to_list(stream)
[1,2,3,4,5]
@@ -427,6 +444,31 @@ defmodule Stream do
acc: true]
end
@doc """
Emit a sequence of values and accumulators. Successive values are generated by
calling `next_fun` with the previous accumulator.
If the return value is nil iteration ends.
## Examples
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
[5, 4, 3, 2, 1]
"""
@spec unfold(acc, (acc -> { element, acc } | nil)) :: t
def unfold(acc, f) do
fn acc1, f1 ->
do_unfold(acc, f, acc1, f1)
end
end
defp do_unfold(gen_acc, gen_fun, acc, fun) do
case gen_fun.(gen_acc) do
nil -> acc
{ v, new_gen_acc } -> do_unfold(new_gen_acc, gen_fun, fun.(v, acc), fun)
end
end
@doc """
Creates a stream where each item in the enumerable will
be accompanied by its index.
+116 -101
View File
@@ -1,3 +1,5 @@
import Kernel, except: [length: 1]
defmodule String do
@moduledoc %S"""
A String in Elixir is a UTF-8 encoded binary.
@@ -5,7 +7,7 @@ defmodule String do
## String and binary operations
The functions in this module act according to the
Unicode Standard, version 6.2.0. For example,
Unicode Standard, version 6.3.0. For example,
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
module.
@@ -16,7 +18,7 @@ defmodule String do
* `Kernel.binary_part/3` - retrieves part of the binary
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
* Plus a number of conversion functions, like `Kernel.binary_to_atom/2`,
* Plus a number of conversion functions, like `Kernel.binary_to_atom/1`,
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
like `Kernel.integer_to_binary/2`
@@ -30,10 +32,14 @@ defmodule String do
For example, the character "é" is represented with two
bytes:
iex> string = "é"
...> byte_size(string)
iex> byte_size("é")
2
However, this module returns the proper length:
iex> String.length("é")
1
Furthermore, this module also presents the concept of
graphemes, which are multiple characters that may be
"perceived as a single character" by readers. For example,
@@ -43,6 +49,8 @@ defmodule String do
iex> string = "\x{0065}\x{0301}"
...> byte_size(string)
3
iex> String.length(string)
1
Although the example above is made of two characters, it is
perceived by users as one.
@@ -57,6 +65,11 @@ defmodule String do
Standard, but does not contain any of the locale specific
behaviour.
More information about graphemes can be found in the [Unicode
Standard Annex #29](http://www.unicode.org/reports/tr29/).
This current Elixir version implements Extended Grapheme Cluster
algorithm.
## Integer codepoints
Although codepoints could be represented as integers, this
@@ -139,7 +152,7 @@ defmodule String do
def printable?(_), do: false
@doc """
Splits a string on substrings at each Unicode whitespace
Divides a string into substrings at each Unicode whitespace
occurrence with leading and trailing whitespace ignored.
## Examples
@@ -155,7 +168,7 @@ defmodule String do
@spec split(t) :: [t]
defdelegate split(binary), to: String.Unicode
@doc """
@doc %S"""
Divides a string into substrings based on a pattern,
returning a list of these substrings. The pattern can
be a string, a list of strings or a regular expression.
@@ -168,6 +181,8 @@ defmodule String do
## Examples
Splitting with a string pattern:
iex> String.split("a,b,c", ",")
["a", "b", "c"]
iex> String.split("a,b,c", ",", global: false)
@@ -175,15 +190,30 @@ defmodule String do
iex> String.split(" a b c ", " ", trim: true)
["a", "b", "c"]
A list of patterns:
iex> String.split("1,2 3,4", [" ", ","])
["1", "2", "3", "4"]
A regular expression:
iex> String.split("a,b,c", %r{,})
["a", "b", "c"]
iex> String.split("a,b,c", %r{,}, global: false)
["a", "b,c"]
iex> String.split("a,b", %r{\\.})
["a,b"]
iex> String.split(" a b c ", %r{\s}, trim: true)
["a", "b", "c"]
Splitting on empty patterns returns codepoints:
iex> String.split("abc", %r{})
["a", "b", "c", ""]
iex> String.split("abc", "")
["a", "b", "c", ""]
iex> String.split("abc", "", trim: true)
["a", "b", "c"]
iex> String.split("abc", "", global: false)
["a", "bc"]
"""
@spec split(t, t | [t] | Regex.t) :: [t]
@@ -192,6 +222,8 @@ defmodule String do
def split("", _pattern, _options), do: [""]
def split(binary, "", options), do: split(binary, %r"", options)
def split(binary, pattern, options) when is_regex(pattern) do
Regex.split(pattern, binary, options)
end
@@ -435,13 +467,13 @@ defmodule String do
end
defp do_justify(subject, len, padding, type) when is_integer(padding) do
subject_len = String.length(subject)
subject_len = length(subject)
cond do
subject_len >= len ->
subject
subject_len < len ->
fill = String.duplicate(<<padding :: utf8>>, len - subject_len)
fill = duplicate(<<padding :: utf8>>, len - subject_len)
case type do
:left -> subject <> fill
@@ -523,11 +555,11 @@ defmodule String do
"""
@spec reverse(t) :: t
def reverse(string) do
do_reverse(String.Unicode.next_grapheme(string), [])
do_reverse(next_grapheme(string), [])
end
defp do_reverse({grapheme, rest}, acc) do
do_reverse(String.Unicode.next_grapheme(rest), [grapheme|acc])
do_reverse(next_grapheme(rest), [grapheme|acc])
end
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
@@ -584,6 +616,7 @@ defmodule String do
{ "j", "osé" }
"""
@compile { :inline, next_codepoint: 1 }
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
defdelegate next_codepoint(string), to: String.Unicode
@@ -642,30 +675,10 @@ defmodule String do
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc %S"""
Checks whether `str` is a valid codepoint.
Note that the empty string is considered invalid, as are
strings containing multiple codepoints.
## Examples
iex> String.valid_codepoint?("a")
true
iex> String.valid_codepoint?("ø")
true
iex> String.valid_codepoint?(<<0xffff :: 16>>)
false
iex> String.valid_codepoint?("asdf")
false
"""
@spec valid_codepoint?(codepoint) :: boolean
def valid_codepoint?(<<_ :: utf8>>), do: true
def valid_codepoint?(_), do: false
@doc """
Returns unicode graphemes in the string.
Returns unicode graphemes in the string as per Extended Grapheme
Cluster algorithm outlined in the [Unicode Standard Annex #29,
Unicode Text Segmentation](http://www.unicode.org/reports/tr29/).
## Examples
@@ -674,7 +687,7 @@ defmodule String do
"""
@spec graphemes(t) :: [grapheme]
defdelegate graphemes(string), to: String.Unicode
defdelegate graphemes(string), to: String.Graphemes
@doc """
Returns the next grapheme in a String.
@@ -689,8 +702,9 @@ defmodule String do
{ "j", "osé" }
"""
@compile { :inline, next_grapheme: 1 }
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
defdelegate next_grapheme(string), to: String.Unicode
defdelegate next_grapheme(string), to: String.Graphemes
@doc """
Returns the first grapheme from an utf8 string,
@@ -782,7 +796,7 @@ defmodule String do
end
def at(string, position) when position < 0 do
real_pos = do_length(next_grapheme(string)) - abs(position)
real_pos = length(string) - abs(position)
case real_pos >= 0 do
true -> do_at(next_grapheme(string), real_pos, 0)
false -> nil
@@ -827,7 +841,7 @@ defmodule String do
@spec slice(t, integer, integer) :: grapheme | nil
def slice(string, start, 0) do
case abs(start) <= String.length(string) do
case abs(start) <= length(string) do
true -> ""
false -> nil
end
@@ -838,13 +852,72 @@ defmodule String do
end
def slice(string, start, len) when start < 0 and len >= 0 do
real_start_pos = do_length(next_grapheme(string)) - abs(start)
real_start_pos = length(string) - abs(start)
case real_start_pos >= 0 do
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
false -> nil
end
end
@doc """
Returns a substring from the offset given by the start of the
range to the offset given by the end of the range.
If the start of the range is not a valid offset for the given
string or if the range is in reverse order, returns `nil`.
## Examples
iex> String.slice("elixir", 1..3)
"lix"
iex> String.slice("elixir", 1..10)
"lixir"
iex> String.slice("elixir", 10..3)
nil
iex> String.slice("elixir", -4..-1)
"ixir"
iex> String.slice("elixir", 2..-1)
"ixir"
iex> String.slice("elixir", -4..6)
"ixir"
iex> String.slice("elixir", -1..-4)
nil
iex> String.slice("elixir", -10..-7)
nil
iex> String.slice("a", 0..1500)
"a"
iex> String.slice("a", 1..1500)
""
iex> String.slice("a", 2..1500)
nil
"""
@spec slice(t, Range.t) :: t | nil
def slice(string, range)
def slice(string, first..last) when first >= 0 and last >= 0 do
do_slice(next_grapheme(string), first, last, 0, "")
end
def slice(string, first..last) do
total = length(string)
if first < 0 do
first = total + first
end
if last < 0 do
last = total + last
end
if first > 0 do
do_slice(next_grapheme(string), first, last, 0, "")
end
end
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
nil
end
@@ -872,64 +945,6 @@ defmodule String do
end
end
@doc """
Converts a string to an integer. If successful, returns a
tuple of the form `{integer, remainder of string}`. If unsuccessful,
returns `:error`.
## Examples
iex> String.to_integer("34")
{34,""}
iex> String.to_integer("34.5")
{34,".5"}
iex> String.to_integer("three")
:error
"""
@spec to_integer(t) :: {integer, t} | :error
def to_integer(string) do
{result, remainder} = :string.to_integer(:binary.bin_to_list(string))
case result do
:error -> :error
_ -> {result, :binary.list_to_bin(remainder)}
end
end
@doc """
Converts a string to a float. If successful, returns a
tuple of the form `{float, remainder of string}`.
If unsuccessful, returns `:error`.
## Examples
iex> String.to_float("34")
{34.0,""}
iex> String.to_float("34.25")
{34.25,""}
iex> String.to_float("56.5xyz")
{56.5,"xyz"}
iex> String.to_float("pi")
:error
"""
@spec to_float(t) :: {integer, t} | :error
def to_float(string) do
charlist = :binary.bin_to_list(string)
{result, remainder} = :string.to_float(charlist)
case result do
:error ->
{int_result, int_remainder} = :string.to_integer(charlist)
case int_result do
:error -> :error
_ -> {:erlang.float(int_result), :binary.list_to_bin(int_remainder)}
end
_ -> {result, :binary.list_to_bin(remainder)}
end
end
@doc """
Returns `true` if `string` starts with any of the prefixes given, otherwise
`false`. `prefixes` can be either a single prefix or a list of prefixes.
@@ -947,7 +962,7 @@ defmodule String do
@spec starts_with?(t, t | [t]) :: boolean
def starts_with?(string, prefixes) when is_list(prefixes) do
Enum.any?(prefixes, do_starts_with(string, &1))
Enum.any?(prefixes, &do_starts_with(string, &1))
end
def starts_with?(string, prefix) do
@@ -983,7 +998,7 @@ defmodule String do
@spec ends_with?(t, t | [t]) :: boolean
def ends_with?(string, suffixes) when is_list(suffixes) do
Enum.any?(suffixes, do_ends_with(string, &1))
Enum.any?(suffixes, &do_ends_with(string, &1))
end
def ends_with?(string, suffix) do
@@ -1022,7 +1037,7 @@ defmodule String do
@spec contains?(t, t | [t]) :: boolean
def contains?(string, matches) when is_list(matches) do
Enum.any?(matches, do_contains(string, &1))
Enum.any?(matches, &do_contains(string, &1))
end
def contains?(string, match) do
+9 -6
View File
@@ -14,8 +14,6 @@ defprotocol String.Chars do
as `"foo" <> to_string(bar)`.
"""
@only [BitString, List, Number, Atom, Record]
def to_string(thing)
end
@@ -29,7 +27,7 @@ defimpl String.Chars, for: Atom do
end
def to_string(atom) do
atom_to_binary(atom, :utf8)
atom_to_binary(atom)
end
end
@@ -67,14 +65,19 @@ defimpl String.Chars, for: List do
def to_string(char_list), do: String.from_char_list!(char_list)
end
defimpl String.Chars, for: Number do
defimpl String.Chars, for: Integer do
@doc """
Simply converts the number (integer or a float) to a binary.
Simply converts the integer to a string.
"""
def to_string(thing) when is_integer(thing) do
def to_string(thing) do
integer_to_binary(thing)
end
end
defimpl String.Chars, for: Float do
@doc """
Simply converts the float to a string.
"""
def to_string(thing) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
+58 -25
View File
@@ -9,8 +9,8 @@ defmodule System do
message: "could not get a current working directory, the current location is not accessible"
@moduledoc """
The System module provides access to some variables used or
maintained by the VM and to functions that interact strongly
The System module provides access to variables used or
maintained by the VM and to functions that interact directly
with the VM or the host system.
"""
@@ -57,13 +57,17 @@ defmodule System do
end
@doc """
Elixir version information.
Returns Elixir's version as binary.
"""
@spec version() :: String.t
def version, do: get_version
@doc """
Returns a keywords list with version, git tag info and date.
Elixir build information.
Returns a keyword list with Elixir version, git tag info and compilation date.
"""
@spec build_info() :: Keyword.t
def build_info do
@@ -71,7 +75,9 @@ defmodule System do
end
@doc """
Returns the list of command-line arguments passed to the program.
List command line arguments.
Returns the list of command line arguments passed to the program.
"""
@spec argv() :: [String.t]
def argv do
@@ -79,15 +85,19 @@ defmodule System do
end
@doc """
Changes the list of command-line arguments. Use it with caution,
as it destory any previous argv information.
Modify command line arguments.
Changes the list of command line arguments. Use it with caution,
as it destroys any previous argv information.
"""
@spec argv([String.t]) :: :ok
def argv(argv) do
:elixir_code_server.cast({ :argv, argv })
def argv(args) do
:elixir_code_server.cast({ :argv, args })
end
@doc """
Current working directory.
Returns the current working directory or `nil` if one
is not available.
"""
@@ -99,6 +109,8 @@ defmodule System do
end
@doc """
Current working directory, exception on error.
Returns the current working directory or raises `System.NoAccessCwdError`.
"""
def cwd! do
@@ -106,8 +118,10 @@ defmodule System do
end
@doc """
Returns the user home (platform independent).
It returns `nil` if no user home is set.
User home directory.
Returns the user home directory (platform independent).
Returns `nil` if no user home is set.
"""
def user_home do
case :os.type() do
@@ -117,6 +131,8 @@ defmodule System do
end
@doc """
User home directory, exception on error.
Same as `user_home/0` but raises `System.NoHomeError`
instead of returning `nil` if no user home is set.
"""
@@ -139,8 +155,10 @@ defmodule System do
end
@doc %S"""
Writable temporary directory.
Returns a writable temporary directory.
It searches for directories in the following order:
Searches for directories in the following order:
1. The directory named by the TMPDIR environment variable
2. The directory named by the TEMP environment variable
@@ -159,7 +177,9 @@ defmodule System do
end
@doc """
Same as `tmp_dir` but raises `System.NoTmpDirError`
Writable temporary directory, exception on error.
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
instead of returning `nil` if no temp dir is set.
"""
def tmp_dir! do
@@ -189,18 +209,22 @@ defmodule System do
end
@doc """
Register a program exit handler function.
Registers a function that will be invoked
at the end of program execution. Useful for
invoking a hook in a "script" mode.
invoking a hook in "script" mode.
The function must expect the exit status code
as argument.
The function must receive the exit status code
as an argument.
"""
def at_exit(fun) when is_function(fun, 1) do
:elixir_code_server.cast { :at_exit, fun }
end
@doc """
Execute a system command.
Executes `command` in a command shell of the target OS,
captures the standard output of the command and returns
the result as a binary.
@@ -222,6 +246,8 @@ defmodule System do
end
@doc """
Locate an executable on the system.
This function looks up an executable program given
its name using the environment variable PATH on Unix
and Windows. It also considers the proper executable
@@ -248,6 +274,8 @@ defmodule System do
end
@doc """
System environment variables.
Returns a list of all environment variables. Each variable is given as a
`{name, value}` tuple where both `name` and `value` are strings.
"""
@@ -261,6 +289,8 @@ defmodule System do
end
@doc """
Environment variable value.
Returns the value of the environment variable
`varname` as a binary, or `nil` if the environment
variable is undefined.
@@ -274,6 +304,8 @@ defmodule System do
end
@doc """
Erlang VM process identifier.
Returns the process identifier of the current Erlang emulator
in the format most commonly used by the operating system environment.
@@ -283,6 +315,8 @@ defmodule System do
def get_pid, do: iolist_to_binary(:os.getpid)
@doc """
Set an environment variable value.
Sets a new `value` for the environment variable `varname`.
"""
@spec put_env(binary, binary) :: :ok
@@ -292,6 +326,8 @@ defmodule System do
end
@doc """
Set multiple environment variables.
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
@@ -301,22 +337,24 @@ defmodule System do
end
@doc """
Gets Elixir's stacktrace.
Last exception stacktrace.
Notice the Erlang VM (and therefore this function) does not
Note that the Erlang VM (and therefore this function) does not
return the current stacktrace but rather the stacktrace of the
latest exception.
"""
def stacktrace do
filter_stacktrace :erlang.get_stacktrace
:erlang.get_stacktrace
end
@doc """
Halts the Erlang runtime system where the first argument status must be a
Halt the Erlang runtime system.
Halts the Erlang runtime system where the argument `status` must be a
non-negative integer, the atom `:abort` or a binary.
* If an integer, the runtime system exits with the integer value which
is returned to the Operating System;
is returned to the operating system;
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system;
@@ -347,9 +385,4 @@ defmodule System do
def halt(status) when is_binary(status) do
:erlang.halt(:binary.bin_to_list(status))
end
## Helpers
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: t
defp filter_stacktrace(t), do: t
end
+60
View File
@@ -0,0 +1,60 @@
defmodule Tuple do
@moduledoc """
Functions for working with tuples.
"""
@doc """
Create a new tuple.
Creates a tuple of size `size` containing the
given `data` at every position.
## Examples
iex> Tuple.duplicate(:hello, 3)
{ :hello, :hello, :hello }
"""
@spec duplicate(term, non_neg_integer) :: tuple
def duplicate(data, size) do
:erlang.make_tuple(size, data)
end
@doc """
Insert an element into a tuple.
Inserts `value` into `tuple` at the given zero-based `index`.
Raises an `ArgumentError` if `index` is greater than the
length of `tuple`.
## Examples
iex> tuple = { :bar, :baz }
...> Tuple.insert_at(tuple, 0, :foo)
{ :foo, :bar, :baz }
"""
@spec insert_at(tuple, non_neg_integer, term) :: tuple
def insert_at(tuple, index, term) do
:erlang.insert_element(index + 1, tuple, term)
end
@doc """
Remove an element from a tuple.
Deletes the element at the zero-based `index` from `tuple`.
Raises an `ArgumentError` if `index` is greater than
or equal to the length of `tuple`.
## Examples
iex> tuple = { :foo, :bar, :baz }
...> Tuple.delete_at(tuple, 0)
{ :bar, :baz }
"""
@spec delete_at(tuple, non_neg_integer) :: tuple
def delete_at(tuple, index) do
:erlang.delete_element(index + 1, tuple)
end
end
+2 -1
View File
@@ -54,7 +54,7 @@ defmodule URI do
that implements the `String.Chars` protocol (i.e. can be converted
to a binary).
"""
def encode_query(l), do: Enum.map_join(l, "&", pair(&1))
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
@doc """
Given a query string of the form "key1=value1&key=value2...", produces an
@@ -137,6 +137,7 @@ defmodule URI do
def decode(<<>>), do: <<>>
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n) when n in ?a..?f, do: n - ?a + 10
defp hex2dec(n) when n in ?0..?9, do: n - ?0
defp check_plus(?+), do: 32
@@ -1,15 +1,13 @@
defmodule Mix.Version do
defmodule Version do
@moduledoc %S"""
This module provides functions for parsing and matching
versions with requirements.
Functions for parsing and matching versions against requirements.
A version is a string or a `Mix.Version.Schema` generated
after parsing via `Mix.Version.parse/1`. A requirement is
a string that follows a specific format.
A version is a string in a specific format or a `Version.Schema`
generated after parsing via `Version.parse/1`.
`Mix.Version` parsing and requirements follows
`Version` parsing and requirements follow
[SemVer 2.0 schema](http://semver.org/) and you will get
the most of Mix' version system by following it. In order
the most of Mix's version system by following it. In order
to support integration with projects that may
follow different versioning schemas, Elixir won't choke
on unknown versions, however you won't be able to use
@@ -33,7 +31,7 @@ defmodule Mix.Version do
Requirements allow you to specify which versions of a given
dependency you are willing to work against. It supports common
operators like `>=`, `<=`, `>`, `==` and friends that would
operators like `>=`, `<=`, `>`, `==` and friends that
work as one would expect:
# Only version 2.0.0
@@ -54,8 +52,8 @@ defmodule Mix.Version do
"""
@type t :: String.t | Mix.Version.Schema.t
@type requirement :: String.t | Mix.Version.Requirement.t
@type t :: String.t | Version.Schema.t
@type requirement :: String.t | Version.Requirement.t
@type matchable :: { major :: String.t | non_neg_integer,
minor :: non_neg_integer | nil,
@@ -79,11 +77,22 @@ defmodule Mix.Version do
end
@doc """
Checks if the given version matches the specification.
Check if the given version matches the specification.
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
## Examples
iex> Version.match?("2.0", ">1.0")
true
iex> Version.match?("2.0", "==1.0")
false
"""
@spec match?(t, requirement) :: boolean
def match?(version, requirement) when is_binary(requirement) do
case Mix.Version.Parser.parse_requirement(requirement) do
case Version.Parser.parse_requirement(requirement) do
{ :ok, req } ->
match?(version, req)
{ :error, reason } ->
@@ -105,24 +114,38 @@ defmodule Mix.Version do
end
@doc """
Checks if a version string is compatible with [semver](http://semver.org/).
Check if a version string is compatible with [semver](http://semver.org/).
## Examples
iex> Version.valid?("2.0")
true
iex> Version.valid?("invalid")
false
"""
@spec valid?(String.t | Schema.t) :: boolean
def valid?(string) when is_binary(string) do
Mix.Version.Parser.valid_version?(string)
Version.Parser.valid_version?(string)
end
def valid?(Mix.Version.Schema[major: nil]), do: false
def valid?(Mix.Version.Schema[]), do: true
def valid?(Version.Schema[major: nil]), do: false
def valid?(Version.Schema[]), do: true
@doc """
Parse a version into a matchable value.
Parse a version string into a `Version.Schema`.
## Examples
> Version.parse("2.0.1-alpha1")
#Version.Schema<2.0.1-alpha1>
"""
@spec parse(String.t) :: { :ok, Schema.t } | { :error, term }
@spec parse(String.t) :: Schema.t
def parse(string) when is_binary(string) do
case Mix.Version.Parser.parse_version(string) do
case Version.Parser.parse_version(string) do
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
{ :error, _ } -> Mix.Version.Schema[source: string]
{ :error, _ } -> Version.Schema[source: string]
end
end
@@ -137,19 +160,25 @@ defmodule Mix.Version do
end
@doc """
Get the matchable representation.
Convert a version to a `Version.matchable`
## Examples
iex> Version.to_matchable("2.0.1-alpha.1")
{2, 0, 1, ["alpha", 1]}
"""
@spec to_matchable(String.t | Schema.t) :: Mix.Version.matchable
@spec to_matchable(String.t | Schema.t) :: Version.matchable
def to_matchable(Schema[major: nil, source: source]) do
{ source, nil, nil, [] }
end
def to_matchable(Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
{ major, minor, patch, [] }
end
def to_matchable(Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, Mix.Version.Parser.parse_pre(pre) }
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, Version.Parser.parse_pre(pre) }
end
def to_matchable(string) do
@@ -157,11 +186,17 @@ defmodule Mix.Version do
end
@doc """
Convert a matchable to a `Mix.Version`.
Convert a matchable to a `Version.Schema`.
## Examples
> Version.from_matchable({2, 0, 1, ["alpha", 1]})
#Version.Schema<2.0.1-alpha.1>
"""
@spec from_matchable(Mix.Version.matchable) :: Schema.t
@spec from_matchable(Version.matchable) :: Schema.t
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
Mix.Version.Schema[source: source]
Version.Schema[source: source]
end
def from_matchable({ major, minor, patch, pre }) do
@@ -184,7 +219,7 @@ defmodule Mix.Version do
end
end
Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
end
defmodule Parser.DSL do
@@ -251,12 +286,12 @@ defmodule Mix.Version do
end
deflexer acc do
Enum.filter(Enum.reverse(acc), &1 != :' ')
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
end
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
@spec parse_requirement(String.t) :: { :ok, Mix.Version.Requirement.t } | { :error, binary | atom }
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
def parse_requirement(source) do
lexed = lexer(source, [])
@@ -280,7 +315,7 @@ defmodule Mix.Version do
defp nillify(""), do: nil
defp nillify(o), do: o
@spec parse_version(String.t) :: { :ok, Mix.Version.matchable } | { :error, :invalid_version }
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
def parse_version(string) when is_binary(string) do
if valid_version?(string) do
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
@@ -364,7 +399,7 @@ defmodule Mix.Version do
end
defp approximate(version) do
Mix.Version.from_matchable(case Regex.run(@version_regex, version) do
Version.from_matchable(case Regex.run(@version_regex, version) do
[_, major] ->
{ binary_to_integer(major) + 1, 0, 0, [] }
@@ -387,19 +422,19 @@ defmodule Mix.Version do
end
defp to_condition([:'==', version | _]) do
version = Mix.Version.to_matchable(version)
version = Version.to_matchable(version)
{ :'==', :'$_', { :const, version } }
end
defp to_condition([:'!=', version | _]) do
version = Mix.Version.to_matchable(version)
version = Version.to_matchable(version)
{ :'/=', :'$_', { :const, version } }
end
defp to_condition([:'~>', version | _]) do
from = Mix.Version.parse(version)
from = Version.parse(version)
to = approximate(version)
{ :andalso, to_condition([:'>=', to_string(from)]),
@@ -407,7 +442,7 @@ defmodule Mix.Version do
end
defp to_condition([:'>', version | _]) do
{ major, minor, patch, pre } = Mix.Version.to_matchable(version)
{ major, minor, patch, pre } = Version.to_matchable(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
@@ -423,7 +458,7 @@ defmodule Mix.Version do
end
defp to_condition([:'>=', version | _]) do
matchable = Mix.Version.to_matchable(version)
matchable = Version.to_matchable(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
@@ -431,7 +466,7 @@ defmodule Mix.Version do
end
defp to_condition([:'<', version | _]) do
{ major, minor, patch, pre } = Mix.Version.to_matchable(version)
{ major, minor, patch, pre } = Version.to_matchable(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
@@ -447,7 +482,7 @@ defmodule Mix.Version do
end
defp to_condition([:'<=', version | _]) do
matchable = Mix.Version.to_matchable(version)
matchable = Version.to_matchable(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
@@ -468,26 +503,26 @@ defmodule Mix.Version do
end
end
defimpl String.Chars, for: Mix.Version.Schema do
def to_string(Mix.Version.Schema[source: source]) do
defimpl String.Chars, for: Version.Schema do
def to_string(Version.Schema[source: source]) do
source
end
end
defimpl Inspect, for: Mix.Version.Schema do
defimpl Inspect, for: Version.Schema do
def inspect(self, _opts) do
"#Mix.Version.Schema<" <> to_string(self) <> ">"
"#Version.Schema<" <> to_string(self) <> ">"
end
end
defimpl String.Chars, for: Mix.Version.Requirement do
defimpl String.Chars, for: Version.Requirement do
def to_string({ _, source, _ }) do
source
end
end
defimpl Inspect, for: Mix.Version.Requirement do
defimpl Inspect, for: Version.Requirement do
def inspect({ _, source, _ }, _opts) do
"#Mix.Version.Requirement<" <> source <> ">"
"#Version.Requirement<" <> source <> ">"
end
end
-420
View File
@@ -1,420 +0,0 @@
LATIN CAPITAL LETTER A WITH MACRON AND GRAVE;0100 0300
LATIN SMALL LETTER A WITH MACRON AND GRAVE;0101 0300
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW;0045 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW;0065 0329
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00C8 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00E8 0329
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00C9 0329
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00E9 0329
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND MACRON;00CA 0304
LATIN SMALL LETTER E WITH CIRCUMFLEX AND MACRON;00EA 0304
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND CARON;00CA 030C
LATIN SMALL LETTER E WITH CIRCUMFLEX AND CARON;00EA 030C
LATIN CAPITAL LETTER I WITH MACRON AND GRAVE;012A 0300
LATIN SMALL LETTER I WITH MACRON AND GRAVE;012B 0300
LATIN SMALL LETTER I WITH DOT ABOVE AND ACUTE;0069 0307 0301
LATIN SMALL LETTER NG WITH TILDE ABOVE;006E 0360 0067
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW;004F 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW;006F 0329
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00D2 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00F2 0329
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00D3 0329
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00F3 0329
LATIN CAPITAL LETTER S WITH VERTICAL LINE BELOW;0053 0329
LATIN SMALL LETTER S WITH VERTICAL LINE BELOW;0073 0329
LATIN CAPITAL LETTER U WITH MACRON AND GRAVE;016A 0300
LATIN SMALL LETTER U WITH MACRON AND GRAVE;016B 0300
LATIN CAPITAL LETTER A WITH OGONEK AND ACUTE;0104 0301
LATIN SMALL LETTER A WITH OGONEK AND ACUTE;0105 0301
LATIN CAPITAL LETTER A WITH OGONEK AND TILDE;0104 0303
LATIN SMALL LETTER A WITH OGONEK AND TILDE;0105 0303
LATIN CAPITAL LETTER E WITH OGONEK AND ACUTE;0118 0301
LATIN SMALL LETTER E WITH OGONEK AND ACUTE;0119 0301
LATIN CAPITAL LETTER E WITH OGONEK AND TILDE;0118 0303
LATIN SMALL LETTER E WITH OGONEK AND TILDE;0119 0303
LATIN CAPITAL LETTER E WITH DOT ABOVE AND ACUTE;0116 0301
LATIN SMALL LETTER E WITH DOT ABOVE AND ACUTE;0117 0301
LATIN CAPITAL LETTER E WITH DOT ABOVE AND TILDE;0116 0303
LATIN SMALL LETTER E WITH DOT ABOVE AND TILDE;0117 0303
LATIN SMALL LETTER I WITH DOT ABOVE AND GRAVE;0069 0307 0300
LATIN SMALL LETTER I WITH DOT ABOVE AND TILDE;0069 0307 0303
LATIN CAPITAL LETTER I WITH OGONEK AND ACUTE;012E 0301
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND ACUTE;012F 0307 0301
LATIN CAPITAL LETTER I WITH OGONEK AND TILDE;012E 0303
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND TILDE;012F 0307 0303
LATIN CAPITAL LETTER J WITH TILDE;004A 0303
LATIN SMALL LETTER J WITH DOT ABOVE AND TILDE;006A 0307 0303
LATIN CAPITAL LETTER L WITH TILDE;004C 0303
LATIN SMALL LETTER L WITH TILDE;006C 0303
LATIN CAPITAL LETTER M WITH TILDE;004D 0303
LATIN SMALL LETTER M WITH TILDE;006D 0303
LATIN CAPITAL LETTER R WITH TILDE;0052 0303
LATIN SMALL LETTER R WITH TILDE;0072 0303
LATIN CAPITAL LETTER U WITH OGONEK AND ACUTE;0172 0301
LATIN SMALL LETTER U WITH OGONEK AND ACUTE;0173 0301
LATIN CAPITAL LETTER U WITH OGONEK AND TILDE;0172 0303
LATIN SMALL LETTER U WITH OGONEK AND TILDE;0173 0303
LATIN CAPITAL LETTER U WITH MACRON AND ACUTE;016A 0301
LATIN SMALL LETTER U WITH MACRON AND ACUTE;016B 0301
LATIN CAPITAL LETTER U WITH MACRON AND TILDE;016A 0303
LATIN SMALL LETTER U WITH MACRON AND TILDE;016B 0303
LATIN SMALL LETTER AE WITH GRAVE;00E6 0300
LATIN SMALL LETTER OPEN O WITH GRAVE;0254 0300
LATIN SMALL LETTER OPEN O WITH ACUTE;0254 0301
LATIN SMALL LETTER TURNED V WITH GRAVE;028C 0300
LATIN SMALL LETTER TURNED V WITH ACUTE;028C 0301
LATIN SMALL LETTER SCHWA WITH GRAVE;0259 0300
LATIN SMALL LETTER SCHWA WITH ACUTE;0259 0301
LATIN SMALL LETTER HOOKED SCHWA WITH GRAVE;025A 0300
LATIN SMALL LETTER HOOKED SCHWA WITH ACUTE;025A 0301
BENGALI LETTER KHINYA;0995 09CD 09B7
TAMIL CONSONANT K; 0B95 0BCD
TAMIL CONSONANT NG; 0B99 0BCD
TAMIL CONSONANT C; 0B9A 0BCD
TAMIL CONSONANT NY; 0B9E 0BCD
TAMIL CONSONANT TT; 0B9F 0BCD
TAMIL CONSONANT NN; 0BA3 0BCD
TAMIL CONSONANT T; 0BA4 0BCD
TAMIL CONSONANT N; 0BA8 0BCD
TAMIL CONSONANT P; 0BAA 0BCD
TAMIL CONSONANT M; 0BAE 0BCD
TAMIL CONSONANT Y; 0BAF 0BCD
TAMIL CONSONANT R; 0BB0 0BCD
TAMIL CONSONANT L; 0BB2 0BCD
TAMIL CONSONANT V; 0BB5 0BCD
TAMIL CONSONANT LLL;0BB4 0BCD
TAMIL CONSONANT LL; 0BB3 0BCD
TAMIL CONSONANT RR; 0BB1 0BCD
TAMIL CONSONANT NNN;0BA9 0BCD
TAMIL CONSONANT J; 0B9C 0BCD
TAMIL CONSONANT SH; 0BB6 0BCD
TAMIL CONSONANT SS; 0BB7 0BCD
TAMIL CONSONANT S; 0BB8 0BCD
TAMIL CONSONANT H; 0BB9 0BCD
TAMIL CONSONANT KSS;0B95 0BCD 0BB7 0BCD
TAMIL SYLLABLE KAA; 0B95 0BBE
TAMIL SYLLABLE KI; 0B95 0BBF
TAMIL SYLLABLE KII; 0B95 0BC0
TAMIL SYLLABLE KU; 0B95 0BC1
TAMIL SYLLABLE KUU; 0B95 0BC2
TAMIL SYLLABLE KE; 0B95 0BC6
TAMIL SYLLABLE KEE; 0B95 0BC7
TAMIL SYLLABLE KAI; 0B95 0BC8
TAMIL SYLLABLE KO; 0B95 0BCA
TAMIL SYLLABLE KOO; 0B95 0BCB
TAMIL SYLLABLE KAU; 0B95 0BCC
TAMIL SYLLABLE NGAA; 0B99 0BBE
TAMIL SYLLABLE NGI; 0B99 0BBF
TAMIL SYLLABLE NGII; 0B99 0BC0
TAMIL SYLLABLE NGU; 0B99 0BC1
TAMIL SYLLABLE NGUU; 0B99 0BC2
TAMIL SYLLABLE NGE; 0B99 0BC6
TAMIL SYLLABLE NGEE; 0B99 0BC7
TAMIL SYLLABLE NGAI; 0B99 0BC8
TAMIL SYLLABLE NGO; 0B99 0BCA
TAMIL SYLLABLE NGOO; 0B99 0BCB
TAMIL SYLLABLE NGAU; 0B99 0BCC
TAMIL SYLLABLE CI; 0B9A 0BBF
TAMIL SYLLABLE CII; 0B9A 0BC0
TAMIL SYLLABLE CU; 0B9A 0BC1
TAMIL SYLLABLE CUU; 0B9A 0BC2
TAMIL SYLLABLE CE; 0B9A 0BC6
TAMIL SYLLABLE CEE; 0B9A 0BC7
TAMIL SYLLABLE CAI; 0B9A 0BC8
TAMIL SYLLABLE CO; 0B9A 0BCA
TAMIL SYLLABLE COO; 0B9A 0BCB
TAMIL SYLLABLE CAU; 0B9A 0BCC
TAMIL SYLLABLE NYAA; 0B9E 0BBE
TAMIL SYLLABLE NYI; 0B9E 0BBF
TAMIL SYLLABLE NYII; 0B9E 0BC0
TAMIL SYLLABLE NYU; 0B9E 0BC1
TAMIL SYLLABLE NYUU; 0B9E 0BC2
TAMIL SYLLABLE NYE; 0B9E 0BC6
TAMIL SYLLABLE NYEE; 0B9E 0BC7
TAMIL SYLLABLE NYAI; 0B9E 0BC8
TAMIL SYLLABLE NYO; 0B9E 0BCA
TAMIL SYLLABLE NYOO; 0B9E 0BCB
TAMIL SYLLABLE NYAU; 0B9E 0BCC
TAMIL SYLLABLE TTAA; 0B9F 0BBE
TAMIL SYLLABLE TTI; 0B9F 0BBF
TAMIL SYLLABLE TTII; 0B9F 0BC0
TAMIL SYLLABLE TTU; 0B9F 0BC1
TAMIL SYLLABLE TTUU; 0B9F 0BC2
TAMIL SYLLABLE TTE; 0B9F 0BC6
TAMIL SYLLABLE TTEE; 0B9F 0BC7
TAMIL SYLLABLE TTAI; 0B9F 0BC8
TAMIL SYLLABLE TTO; 0B9F 0BCA
TAMIL SYLLABLE TTOO; 0B9F 0BCB
TAMIL SYLLABLE TTAU; 0B9F 0BCC
TAMIL SYLLABLE NNAA; 0BA3 0BBE
TAMIL SYLLABLE NNI; 0BA3 0BBF
TAMIL SYLLABLE NNII; 0BA3 0BC0
TAMIL SYLLABLE NNU; 0BA3 0BC1
TAMIL SYLLABLE NNUU; 0BA3 0BC2
TAMIL SYLLABLE NNE; 0BA3 0BC6
TAMIL SYLLABLE NNEE; 0BA3 0BC7
TAMIL SYLLABLE NNAI; 0BA3 0BC8
TAMIL SYLLABLE NNO; 0BA3 0BCA
TAMIL SYLLABLE NNOO; 0BA3 0BCB
TAMIL SYLLABLE NNAU; 0BA3 0BCC
TAMIL SYLLABLE TAA; 0BA4 0BBE
TAMIL SYLLABLE TI; 0BA4 0BBF
TAMIL SYLLABLE TII; 0BA4 0BC0
TAMIL SYLLABLE TU; 0BA4 0BC1
TAMIL SYLLABLE TUU; 0BA4 0BC2
TAMIL SYLLABLE TE; 0BA4 0BC6
TAMIL SYLLABLE TEE; 0BA4 0BC7
TAMIL SYLLABLE TAI; 0BA4 0BC8
TAMIL SYLLABLE TO; 0BA4 0BCA
TAMIL SYLLABLE TOO; 0BA4 0BCB
TAMIL SYLLABLE TAU; 0BA4 0BCC
TAMIL SYLLABLE NAA; 0BA8 0BBE
TAMIL SYLLABLE NI; 0BA8 0BBF
TAMIL SYLLABLE NII; 0BA8 0BC0
TAMIL SYLLABLE NU; 0BA8 0BC1
TAMIL SYLLABLE NUU; 0BA8 0BC2
TAMIL SYLLABLE NE; 0BA8 0BC6
TAMIL SYLLABLE NEE; 0BA8 0BC7
TAMIL SYLLABLE NAI; 0BA8 0BC8
TAMIL SYLLABLE NO; 0BA8 0BCA
TAMIL SYLLABLE NOO; 0BA8 0BCB
TAMIL SYLLABLE NAU; 0BA8 0BCC
TAMIL SYLLABLE PAA; 0BAA 0BBE
TAMIL SYLLABLE PI; 0BAA 0BBF
TAMIL SYLLABLE PII; 0BAA 0BC0
TAMIL SYLLABLE PU; 0BAA 0BC1
TAMIL SYLLABLE PUU; 0BAA 0BC2
TAMIL SYLLABLE PE; 0BAA 0BC6
TAMIL SYLLABLE PEE; 0BAA 0BC7
TAMIL SYLLABLE PAI; 0BAA 0BC8
TAMIL SYLLABLE PO; 0BAA 0BCA
TAMIL SYLLABLE POO; 0BAA 0BCB
TAMIL SYLLABLE PAU; 0BAA 0BCC
TAMIL SYLLABLE MAA; 0BAE 0BBE
TAMIL SYLLABLE MI; 0BAE 0BBF
TAMIL SYLLABLE MII; 0BAE 0BC0
TAMIL SYLLABLE MU; 0BAE 0BC1
TAMIL SYLLABLE MUU; 0BAE 0BC2
TAMIL SYLLABLE ME; 0BAE 0BC6
TAMIL SYLLABLE MEE; 0BAE 0BC7
TAMIL SYLLABLE MAI; 0BAE 0BC8
TAMIL SYLLABLE MO; 0BAE 0BCA
TAMIL SYLLABLE MOO; 0BAE 0BCB
TAMIL SYLLABLE MAU; 0BAE 0BCC
TAMIL SYLLABLE YAA; 0BAF 0BBE
TAMIL SYLLABLE YI; 0BAF 0BBF
TAMIL SYLLABLE YII; 0BAF 0BC0
TAMIL SYLLABLE YU; 0BAF 0BC1
TAMIL SYLLABLE YUU; 0BAF 0BC2
TAMIL SYLLABLE YE; 0BAF 0BC6
TAMIL SYLLABLE YEE; 0BAF 0BC7
TAMIL SYLLABLE YAI; 0BAF 0BC8
TAMIL SYLLABLE YO; 0BAF 0BCA
TAMIL SYLLABLE YOO; 0BAF 0BCB
TAMIL SYLLABLE YAU; 0BAF 0BCC
TAMIL SYLLABLE RAA; 0BB0 0BBE
TAMIL SYLLABLE RI; 0BB0 0BBF
TAMIL SYLLABLE RII; 0BB0 0BC0
TAMIL SYLLABLE RU; 0BB0 0BC1
TAMIL SYLLABLE RUU; 0BB0 0BC2
TAMIL SYLLABLE RE; 0BB0 0BC6
TAMIL SYLLABLE REE; 0BB0 0BC7
TAMIL SYLLABLE RAI; 0BB0 0BC8
TAMIL SYLLABLE RO; 0BB0 0BCA
TAMIL SYLLABLE ROO; 0BB0 0BCB
TAMIL SYLLABLE RAU; 0BB0 0BCC
TAMIL SYLLABLE LAA; 0BB2 0BBE
TAMIL SYLLABLE LI; 0BB2 0BBF
TAMIL SYLLABLE LII; 0BB2 0BC0
TAMIL SYLLABLE LU; 0BB2 0BC1
TAMIL SYLLABLE LUU; 0BB2 0BC2
TAMIL SYLLABLE LE; 0BB2 0BC6
TAMIL SYLLABLE LEE; 0BB2 0BC7
TAMIL SYLLABLE LAI; 0BB2 0BC8
TAMIL SYLLABLE LO; 0BB2 0BCA
TAMIL SYLLABLE LOO; 0BB2 0BCB
TAMIL SYLLABLE LAU; 0BB2 0BCC
TAMIL SYLLABLE VAA; 0BB5 0BBE
TAMIL SYLLABLE VI; 0BB5 0BBF
TAMIL SYLLABLE VII; 0BB5 0BC0
TAMIL SYLLABLE VU; 0BB5 0BC1
TAMIL SYLLABLE VUU; 0BB5 0BC2
TAMIL SYLLABLE VE; 0BB5 0BC6
TAMIL SYLLABLE VEE; 0BB5 0BC7
TAMIL SYLLABLE VAI; 0BB5 0BC8
TAMIL SYLLABLE VO; 0BB5 0BCA
TAMIL SYLLABLE VOO; 0BB5 0BCB
TAMIL SYLLABLE VAU; 0BB5 0BCC
TAMIL SYLLABLE LLLAA; 0BB4 0BBE
TAMIL SYLLABLE LLLI; 0BB4 0BBF
TAMIL SYLLABLE LLLII; 0BB4 0BC0
TAMIL SYLLABLE LLLU; 0BB4 0BC1
TAMIL SYLLABLE LLLUU; 0BB4 0BC2
TAMIL SYLLABLE LLLE; 0BB4 0BC6
TAMIL SYLLABLE LLLEE; 0BB4 0BC7
TAMIL SYLLABLE LLLAI; 0BB4 0BC8
TAMIL SYLLABLE LLLO; 0BB4 0BCA
TAMIL SYLLABLE LLLOO; 0BB4 0BCB
TAMIL SYLLABLE LLLAU; 0BB4 0BCC
TAMIL SYLLABLE LLAA; 0BB3 0BBE
TAMIL SYLLABLE LLI; 0BB3 0BBF
TAMIL SYLLABLE LLII; 0BB3 0BC0
TAMIL SYLLABLE LLU; 0BB3 0BC1
TAMIL SYLLABLE LLUU; 0BB3 0BC2
TAMIL SYLLABLE LLE; 0BB3 0BC6
TAMIL SYLLABLE LLEE; 0BB3 0BC7
TAMIL SYLLABLE LLAI; 0BB3 0BC8
TAMIL SYLLABLE LLO; 0BB3 0BCA
TAMIL SYLLABLE LLOO; 0BB3 0BCB
TAMIL SYLLABLE LLAU; 0BB3 0BCC
TAMIL SYLLABLE RRAA; 0BB1 0BBE
TAMIL SYLLABLE RRI; 0BB1 0BBF
TAMIL SYLLABLE RRII; 0BB1 0BC0
TAMIL SYLLABLE RRU; 0BB1 0BC1
TAMIL SYLLABLE RRUU; 0BB1 0BC2
TAMIL SYLLABLE RRE; 0BB1 0BC6
TAMIL SYLLABLE RREE; 0BB1 0BC7
TAMIL SYLLABLE RRAI; 0BB1 0BC8
TAMIL SYLLABLE RRO; 0BB1 0BCA
TAMIL SYLLABLE RROO; 0BB1 0BCB
TAMIL SYLLABLE RRAU; 0BB1 0BCC
TAMIL SYLLABLE NNNAA; 0BA9 0BBE
TAMIL SYLLABLE NNNI; 0BA9 0BBF
TAMIL SYLLABLE NNNII; 0BA9 0BC0
TAMIL SYLLABLE NNNU; 0BA9 0BC1
TAMIL SYLLABLE NNNUU; 0BA9 0BC2
TAMIL SYLLABLE NNNE; 0BA9 0BC6
TAMIL SYLLABLE NNNEE; 0BA9 0BC7
TAMIL SYLLABLE NNNAI; 0BA9 0BC8
TAMIL SYLLABLE NNNO; 0BA9 0BCA
TAMIL SYLLABLE NNNOO; 0BA9 0BCB
TAMIL SYLLABLE NNNAU; 0BA9 0BCC
TAMIL SYLLABLE JAA; 0B9C 0BBE
TAMIL SYLLABLE JI; 0B9C 0BBF
TAMIL SYLLABLE JII; 0B9C 0BC0
TAMIL SYLLABLE JU; 0B9C 0BC1
TAMIL SYLLABLE JUU; 0B9C 0BC2
TAMIL SYLLABLE JE; 0B9C 0BC6
TAMIL SYLLABLE JEE; 0B9C 0BC7
TAMIL SYLLABLE JAI; 0B9C 0BC8
TAMIL SYLLABLE JO; 0B9C 0BCA
TAMIL SYLLABLE JOO; 0B9C 0BCB
TAMIL SYLLABLE JAU; 0B9C 0BCC
TAMIL SYLLABLE SHAA; 0BB6 0BBE
TAMIL SYLLABLE SHI; 0BB6 0BBF
TAMIL SYLLABLE SHII; 0BB6 0BC0
TAMIL SYLLABLE SHU; 0BB6 0BC1
TAMIL SYLLABLE SHUU; 0BB6 0BC2
TAMIL SYLLABLE SHE; 0BB6 0BC6
TAMIL SYLLABLE SHEE; 0BB6 0BC7
TAMIL SYLLABLE SHAI; 0BB6 0BC8
TAMIL SYLLABLE SHO; 0BB6 0BCA
TAMIL SYLLABLE SHOO; 0BB6 0BCB
TAMIL SYLLABLE SHAU; 0BB6 0BCC
TAMIL SYLLABLE SSAA; 0BB7 0BBE
TAMIL SYLLABLE SSI; 0BB7 0BBF
TAMIL SYLLABLE SSII; 0BB7 0BC0
TAMIL SYLLABLE SSU; 0BB7 0BC1
TAMIL SYLLABLE SSUU; 0BB7 0BC2
TAMIL SYLLABLE SSE; 0BB7 0BC6
TAMIL SYLLABLE SSEE; 0BB7 0BC7
TAMIL SYLLABLE SSAI; 0BB7 0BC8
TAMIL SYLLABLE SSO; 0BB7 0BCA
TAMIL SYLLABLE SSOO; 0BB7 0BCB
TAMIL SYLLABLE SSAU; 0BB7 0BCC
TAMIL SYLLABLE SAA; 0BB8 0BBE
TAMIL SYLLABLE SI; 0BB8 0BBF
TAMIL SYLLABLE SII; 0BB8 0BC0
TAMIL SYLLABLE SU; 0BB8 0BC1
TAMIL SYLLABLE SUU; 0BB8 0BC2
TAMIL SYLLABLE SE; 0BB8 0BC6
TAMIL SYLLABLE SEE; 0BB8 0BC7
TAMIL SYLLABLE SAI; 0BB8 0BC8
TAMIL SYLLABLE SO; 0BB8 0BCA
TAMIL SYLLABLE SOO; 0BB8 0BCB
TAMIL SYLLABLE SAU; 0BB8 0BCC
TAMIL SYLLABLE HAA; 0BB9 0BBE
TAMIL SYLLABLE HI; 0BB9 0BBF
TAMIL SYLLABLE HII; 0BB9 0BC0
TAMIL SYLLABLE HU; 0BB9 0BC1
TAMIL SYLLABLE HUU; 0BB9 0BC2
TAMIL SYLLABLE HE; 0BB9 0BC6
TAMIL SYLLABLE HEE; 0BB9 0BC7
TAMIL SYLLABLE HAI; 0BB9 0BC8
TAMIL SYLLABLE HO; 0BB9 0BCA
TAMIL SYLLABLE HOO; 0BB9 0BCB
TAMIL SYLLABLE HAU; 0BB9 0BCC
TAMIL SYLLABLE KSSA; 0B95 0BCD 0BB7
TAMIL SYLLABLE KSSAA; 0B95 0BCD 0BB7 0BBE
TAMIL SYLLABLE KSSI; 0B95 0BCD 0BB7 0BBF
TAMIL SYLLABLE KSSII; 0B95 0BCD 0BB7 0BC0
TAMIL SYLLABLE KSSU; 0B95 0BCD 0BB7 0BC1
TAMIL SYLLABLE KSSUU; 0B95 0BCD 0BB7 0BC2
TAMIL SYLLABLE KSSE; 0B95 0BCD 0BB7 0BC6
TAMIL SYLLABLE KSSEE; 0B95 0BCD 0BB7 0BC7
TAMIL SYLLABLE KSSAI; 0B95 0BCD 0BB7 0BC8
TAMIL SYLLABLE KSSO; 0B95 0BCD 0BB7 0BCA
TAMIL SYLLABLE KSSOO; 0B95 0BCD 0BB7 0BCB
TAMIL SYLLABLE KSSAU; 0B95 0BCD 0BB7 0BCC
TAMIL SYLLABLE SHRII; 0BB6 0BCD 0BB0 0BC0
SINHALA CONSONANT SIGN YANSAYA;0DCA 200D 0DBA
SINHALA CONSONANT SIGN RAKAARAANSAYA;0DCA 200D 0DBB
SINHALA CONSONANT SIGN REPAYA;0DBB 0DCA 200D
GEORGIAN LETTER U-BRJGU;10E3 0302
KHMER CONSONANT SIGN COENG KA;17D2 1780
KHMER CONSONANT SIGN COENG KHA;17D2 1781
KHMER CONSONANT SIGN COENG KO;17D2 1782
KHMER CONSONANT SIGN COENG KHO;17D2 1783
KHMER CONSONANT SIGN COENG NGO;17D2 1784
KHMER CONSONANT SIGN COENG CA;17D2 1785
KHMER CONSONANT SIGN COENG CHA;17D2 1786
KHMER CONSONANT SIGN COENG CO;17D2 1787
KHMER CONSONANT SIGN COENG CHO;17D2 1788
KHMER CONSONANT SIGN COENG NYO;17D2 1789
KHMER CONSONANT SIGN COENG DA;17D2 178A
KHMER CONSONANT SIGN COENG TTHA;17D2 178B
KHMER CONSONANT SIGN COENG DO;17D2 178C
KHMER CONSONANT SIGN COENG TTHO;17D2 178D
KHMER CONSONANT SIGN COENG NA;17D2 178E
KHMER CONSONANT SIGN COENG TA;17D2 178F
KHMER CONSONANT SIGN COENG THA;17D2 1790
KHMER CONSONANT SIGN COENG TO;17D2 1791
KHMER CONSONANT SIGN COENG THO;17D2 1792
KHMER CONSONANT SIGN COENG NO;17D2 1793
KHMER CONSONANT SIGN COENG BA;17D2 1794
KHMER CONSONANT SIGN COENG PHA;17D2 1795
KHMER CONSONANT SIGN COENG PO;17D2 1796
KHMER CONSONANT SIGN COENG PHO;17D2 1797
KHMER CONSONANT SIGN COENG MO;17D2 1798
KHMER CONSONANT SIGN COENG YO;17D2 1799
KHMER CONSONANT SIGN COENG RO;17D2 179A
KHMER CONSONANT SIGN COENG LO;17D2 179B
KHMER CONSONANT SIGN COENG VO;17D2 179C
KHMER CONSONANT SIGN COENG SHA;17D2 179D
KHMER CONSONANT SIGN COENG SSA;17D2 179E
KHMER CONSONANT SIGN COENG SA;17D2 179F
KHMER CONSONANT SIGN COENG HA;17D2 17A0
KHMER CONSONANT SIGN COENG LA;17D2 17A1
KHMER VOWEL SIGN COENG QA;17D2 17A2
KHMER INDEPENDENT VOWEL SIGN COENG QU;17D2 17A7
KHMER INDEPENDENT VOWEL SIGN COENG RY;17D2 17AB
KHMER INDEPENDENT VOWEL SIGN COENG RYY;17D2 17AC
KHMER INDEPENDENT VOWEL SIGN COENG QE;17D2 17AF
KHMER VOWEL SIGN OM;17BB 17C6
KHMER VOWEL SIGN AAM;17B6 17C6
HIRAGANA LETTER BIDAKUON NGA;304B 309A
HIRAGANA LETTER BIDAKUON NGI;304D 309A
HIRAGANA LETTER BIDAKUON NGU;304F 309A
HIRAGANA LETTER BIDAKUON NGE;3051 309A
HIRAGANA LETTER BIDAKUON NGO;3053 309A
KATAKANA LETTER BIDAKUON NGA;30AB 309A
KATAKANA LETTER BIDAKUON NGI;30AD 309A
KATAKANA LETTER BIDAKUON NGU;30AF 309A
KATAKANA LETTER BIDAKUON NGE;30B1 309A
KATAKANA LETTER BIDAKUON NGO;30B3 309A
KATAKANA LETTER AINU CE;30BB 309A
KATAKANA LETTER AINU TU;30C4 309A
KATAKANA LETTER AINU TO;30C8 309A
KATAKANA LETTER AINU P;31F7 309A
MODIFIER LETTER EXTRA-HIGH EXTRA-LOW CONTOUR TONE BAR;02E5 02E9
MODIFIER LETTER EXTRA-LOW EXTRA-HIGH CONTOUR TONE BAR;02E9 02E5
+1 -1
View File
@@ -24,4 +24,4 @@
{verbose, false}
]}.
{require_otp_vsn,"(R16B).*"}.
{require_otp_vsn,"(R16B|R17A).*"}.
+11 -13
View File
@@ -2,9 +2,8 @@
-behaviour(application).
-export([main/1, start_cli/0,
scope_for_eval/1, scope_for_eval/2,
eval/2, eval/3, eval/4,
eval_quoted/2, eval_quoted/3, eval_quoted/4,
eval_forms/3, translate_forms/3]).
eval/2, eval/3, eval/4, eval_forms/3,
eval_quoted/2, eval_quoted/3, eval_quoted/4]).
-include("elixir.hrl").
%% Top level types
@@ -124,17 +123,16 @@ eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
%% Handle forms evaluation internally, it is an
%% internal API not meant for external usage.
translate_forms(Tree, Binding, Opts) when is_list(Opts) ->
translate_forms(Tree, Binding, scope_for_eval(Opts));
translate_forms(Tree, Binding, #elixir_scope{} = Scope) ->
elixir_translator:translate(Tree, elixir_scope:vars_from_binding(Scope, Binding)).
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
eval_forms(Tree, Binding, scope_for_eval(Opts));
eval_forms(Tree, Binding, Scope) ->
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
case ParseTree of
[] -> { nil, Binding, NewScope };
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope, nil),
{ Exprs, NewScope } = elixir_translator:translate(Tree, ParsedScope),
case Exprs of
[] ->
{ nil, Binding, NewScope };
_ ->
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, elixir_scope:binding_for_eval(Binding, nil)),
{Value, elixir_scope:binding_from_vars(NewScope, NewBinding), NewScope }
{ value, Value, NewBinding } = erl_eval:exprs(Exprs, ParsedBinding),
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewScope }
end.
+27 -14
View File
@@ -1,11 +1,13 @@
-module(elixir_aliases).
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/4, store/5]).
-include("elixir.hrl").
%% Store an alias in the given scope
store(_Meta, New, New, S) -> S;
store(Meta, New, Old, S) ->
store(_Meta, New, New, _TKV, S) -> S;
store(Meta, New, Old, TKV, S) ->
record_warn(Meta, New, TKV, S),
SA = S#elixir_scope{
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
},
@@ -19,47 +21,58 @@ store(Meta, New, Old, S) ->
SA
end.
record_warn(Meta, Ref, Opts, S) ->
Warn =
case lists:keyfind(warn, 1, Opts) of
{ warn, false } -> false;
{ warn, true } -> true;
false -> not lists:keymember(context, 1, Meta)
end,
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
%% Expand an alias. It returns an atom (meaning that there
%% was an expansion) or a list of atoms.
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases) ->
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
case lists:keyfind(alias, 1, Meta) of
{ alias, false } ->
expand(Alias, MacroAliases);
expand(Alias, MacroAliases, LexicalTracker);
{ alias, Atom } when is_atom(Atom) ->
case expand(Alias, MacroAliases) of
case expand(Alias, MacroAliases, LexicalTracker) of
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
OtherAliases when is_list(OtherAliases) -> Atom
end;
false ->
expand(Alias, Aliases)
expand(Alias, Aliases, LexicalTracker)
end.
expand({ '__aliases__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
case expand_one(H, Aliases) of
expand({ '__aliases__', _Meta, [H] }, Aliases, LexicalTracker) when H /= 'Elixir' ->
case expand_one(H, Aliases, LexicalTracker) of
false -> [H];
Atom -> Atom
end;
expand({ '__aliases__', _Meta, [H|T] }, Aliases) when is_atom(H) ->
expand({ '__aliases__', _Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
case H of
'Elixir' ->
concat(T);
_ ->
case expand_one(H, Aliases) of
case expand_one(H, Aliases, LexicalTracker) of
false -> [H|T];
Atom -> concat([Atom|T])
end
end;
expand({ '__aliases__', _Meta, List }, _Aliases) ->
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
List.
expand_one(H, Aliases) ->
expand_one(H, Aliases, LexicalTracker) ->
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
case lookup(Lookup, Aliases) of
Lookup -> false;
Else -> Else
Else ->
elixir_lexical:record_alias(Lookup, LexicalTracker),
Else
end.
%% Ensure a module is loaded before its usage.
+55 -19
View File
@@ -4,14 +4,13 @@
handle_info/2, terminate/2, code_change/3]).
-behavior(gen_server).
-record(elixir_code_server, {
compilation_status=[],
argv=[],
compilation_status=ok,
loaded=[],
at_exit=[],
pool=[],
counter=0,
pool={[],0},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
waiting=[]
erl_compiler_options=nil
}).
call(Args) ->
@@ -28,7 +27,9 @@ get_timeout() ->
start_link() ->
{ ok, _ } = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init(_args) ->
init(_Args) ->
code:ensure_loaded('Elixir.Module.DispatchTracker'),
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
{ ok, #elixir_code_server{} }.
handle_call({ acquire, Path }, From, Config) ->
@@ -59,16 +60,27 @@ handle_call(argv, _From, Config) ->
handle_call(compiler_options, _From, Config) ->
{ reply, Config#elixir_code_server.compiler_options, Config };
handle_call(compilation_status, _From, Config) ->
{ reply, Config#elixir_code_server.compilation_status, Config };
handle_call({ compilation_status, CompilerPid }, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{ reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew} };
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
[H|T] ->
{ reply, module_tuple(H), Config#elixir_code_server{pool=T} };
[] ->
Counter = Config#elixir_code_server.counter,
{ reply, module_tuple(Counter), Config#elixir_code_server{counter=Counter+1} }
{ [H|T], Counter } ->
{ reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}} };
{ [], Counter } ->
{ reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}} }
end;
handle_call(erl_compiler_options, _From, Config) ->
case Config#elixir_code_server.erl_compiler_options of
nil ->
Opts = erl_compiler_options(),
{ reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts} };
Opts ->
{ reply, Opts, Config }
end;
handle_call(_Request, _From, Config) ->
@@ -84,14 +96,18 @@ handle_cast({ compiler_options, Options }, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ noreply, Config#elixir_code_server{compiler_options=Final} };
handle_cast(register_warning, Config) ->
handle_cast({ register_warning, CompilerPid }, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=error} };
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
_ -> { noreply, Config }
end;
handle_cast(reset_warnings, Config) ->
{ noreply, Config#elixir_code_server{compilation_status=ok} };
handle_cast({ reset_warnings, CompilerPid }, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
{ noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
handle_cast({ loaded, Path }, Config) ->
Current = Config#elixir_code_server.loaded,
@@ -112,8 +128,8 @@ handle_cast({ unload_files, Files }, Config) ->
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
handle_cast({ return_module_name, I }, #elixir_code_server{pool=Pool} = Config) ->
{ noreply, Config#elixir_code_server{pool=[I|Pool]} };
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
handle_cast(_Request, Config) ->
{ noreply, Config }.
@@ -127,4 +143,24 @@ terminate(_Reason, _Config) ->
code_change(_Old, Config, _Extra) ->
{ ok, Config }.
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
erl_compiler_options() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end.
+74 -99
View File
@@ -1,6 +1,6 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/4, format_error/1]).
-export([core/0, module/4, eval_forms/4, format_error/1]).
-include("elixir.hrl").
%% Public API
@@ -18,7 +18,7 @@ get_opt(Key, Dict) ->
get_opts() ->
elixir_code_server:call(compiler_options).
%% Compiles the given string.
%% Compilation entry points.
string(Contents, File) when is_list(Contents), is_binary(File) ->
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
@@ -29,34 +29,34 @@ quoted(Forms, File) when is_binary(File) ->
try
put(elixir_compiled, []),
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
elixir_lexical:run(File, fun
(Pid) ->
Scope = elixir:scope_for_eval([{file,File}]),
eval_forms(Forms, 1, [], Scope#elixir_scope{lexical_tracker=Pid})
end),
lists:reverse(get(elixir_compiled))
after
put(elixir_compiled, Previous)
end.
%% Compile a file, return a tuple of module names and binaries.
file(Relative) when is_binary(Relative) ->
File = filename:absname(Relative),
{ ok, Bin } = file:read_file(File),
string(elixir_utils:characters_to_list(Bin), File).
%% Compiles a file to the given path (directory).
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
Lists = file(File),
[binary_to_path(X, Path) || X <- Lists],
Lists.
%% Evaluates the contents/forms by compiling them to an Erlang module.
%% Evaluation
eval_forms(Forms, Line, Vars, S) ->
{ Module, I } = retrieve_module_name(),
{ Exprs, FS } = elixir_translator:translate(Forms, S),
Fun = module_form_fun(S#elixir_scope.module),
Form = module_form(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
Fun = eval_fun(S#elixir_scope.module),
Form = eval_mod(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
Args = [X || { _, _, _, X } <- Vars],
%% Pass { native, false } to speed up bootstrap
@@ -79,88 +79,10 @@ eval_forms(Forms, Line, Vars, S) ->
Res
end), FS }.
%% Internal API
eval_fun(nil) -> '__FILE__';
eval_fun(_) -> '__MODULE__'.
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, File, Callback) ->
Options = case get_opt(debug_info) of
true -> [debug_info];
_ -> []
end,
module(Forms, File, Options, false, Callback).
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
{ Options, SkipNative } = compile_opts(Forms, RawOptions),
Listname = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, RawWarnings} ->
Warnings = case SkipNative of
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
false -> RawWarnings
end,
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
format_errors(Errors)
end.
%% Compile core files for bootstrap.
%% Invoked from the Makefile.
core() ->
application:start(elixir),
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()].
%% HELPERS
compile_opts(Forms, Options) ->
EnvOptions = env_default_opts(),
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
case SkipNative or lists:member([{native,false}], Options) of
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
false -> { Options ++ EnvOptions, SkipNative }
end.
env_default_opts() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end.
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
contains_on_load([_|T]) -> contains_on_load(T);
contains_on_load([]) -> false.
no_auto_import() ->
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
module_form_fun(nil) -> '__FILE__';
module_form_fun(_) -> '__MODULE__'.
module_form(Fun, Exprs, Line, File, Module, Vars) when
is_binary(File), is_integer(Line), is_list(Exprs), is_atom(Module) ->
eval_mod(Fun, Exprs, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
@@ -180,22 +102,68 @@ module_form(Fun, Exprs, Line, File, Module, Vars) when
] }
].
%% Generate module names from code server.
retrieve_module_name() ->
elixir_code_server:call(retrieve_module_name).
return_module_name(I) ->
elixir_code_server:cast({ return_module_name, I }).
%% Receives a module Binary and outputs it in the given path.
%% INTERNAL API
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
ok = file:write_file(Path, Binary),
Path.
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, File, Opts, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info];
false -> []
end,
module(Forms, File, Final, false, Callback).
%% CORE FILES COMPILATION
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
Listname = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, RawWarnings} ->
Warnings = case SkipNative of
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
false -> RawWarnings
end,
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
format_errors(Errors)
end.
compiler_options(Forms, Options) ->
EnvOptions = elixir_code_server:call(erl_compiler_options),
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
case SkipNative or lists:member([{native,false}], Options) of
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
false -> { Options ++ EnvOptions, SkipNative }
end.
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
contains_on_load([_|T]) -> contains_on_load(T);
contains_on_load([]) -> false.
no_auto_import() ->
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
%% CORE HANDLING
core() ->
application:start(elixir),
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()].
core_file(File) ->
try
@@ -225,6 +193,7 @@ core_main() ->
<<"lib/elixir/lib/inspect/algebra.ex">>,
<<"lib/elixir/lib/inspect.ex">>,
<<"lib/elixir/lib/range.ex">>,
<<"lib/elixir/lib/regex.ex">>,
<<"lib/elixir/lib/string.ex">>,
<<"lib/elixir/lib/string/chars.ex">>,
<<"lib/elixir/lib/io.ex">>,
@@ -234,9 +203,15 @@ 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">>
].
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
ok = file:write_file(Path, Binary),
Path.
%% ERROR HANDLING
format_error({ skip_native, Module }) ->
+10 -23
View File
@@ -8,9 +8,7 @@
lookup_definition/2,
delete_definition/2,
wrap_definition/6,
wrap_definition/8,
store_definition/7,
store_definition/9,
store_each/8,
unwrap_stored_definitions/2,
format_error/1]).
@@ -72,25 +70,17 @@ delete_definition(Module, Tuple) ->
%% before evaluating the function body), we would see both definitions.
wrap_definition(Kind, Meta, Call, Expr, CheckClauses, S) ->
do_wrap_definition(Kind, Meta, CheckClauses, [
Call, Expr, elixir_scope:serialize(S)
Line = ?line(Meta),
?wrap_call(Line, ?MODULE, store_definition, [
{atom, Line, Kind},
{integer, Line, Line},
{atom, Line, CheckClauses},
{var, Line, '_@MODULE'},
Call,
Expr,
elixir_scope:serialize(S)
]).
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, CheckClauses, S) ->
do_wrap_definition(Kind, Meta, CheckClauses, [
Name, Args, Guards, Expr, elixir_scope:serialize(S)
]).
do_wrap_definition(Kind, Meta, CheckClauses, Extra) ->
Line = ?line(Meta),
Invoke =
[{atom, Line, Kind},
{integer, Line, Line},
{atom, Line, CheckClauses},
{var, Line, '_@MODULE'}] ++ Extra,
?wrap_call(Line, ?MODULE, store_definition, Invoke).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
@@ -139,10 +129,7 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
[store_each(false, Kind, File, Location, Table, CTable, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity };
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, RawS) ->
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
{ Name, Arity }.
%% @on_definition
+1 -14
View File
@@ -1,7 +1,7 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([store_pending/1, is_defined/2, ensure_defined/4,
assign_args/3, name/2, store/3, format_error/1]).
name/2, store/3, format_error/1]).
-include("elixir.hrl").
overridable(Module) ->
@@ -25,19 +25,6 @@ ensure_defined(Meta, Module, Tuple, S) ->
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
end.
%% Assign pseudo variables to the given vars.
assign_args(Line, Args, S) when is_integer(Line) ->
{ FArgs, _ } = lists:mapfoldl(fun(X, Acc) -> assign_args(Line, X, Acc, S) end, 1, Args),
FArgs.
assign_args(Line, X, Acc, _) ->
Match = { match, Line, X, { var, Line, super_arg(Acc) } },
{ Match, Acc + 1 }.
super_arg(Counter) ->
?atom_concat(['_@S', Counter]).
%% Gets the name based on the function and stored overridables
name(Module, Function) ->
+41 -40
View File
@@ -24,9 +24,11 @@ find_import(Meta, Name, Arity, S) ->
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, S#elixir_scope.module, S#elixir_scope.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),
Receiver;
_ ->
@@ -39,6 +41,7 @@ import_function(Meta, Name, Arity, S) ->
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, S#elixir_scope.module, S#elixir_scope.function),
remote_function(Meta, Receiver, Name, Arity, S);
{ macro, _Receiver } ->
@@ -60,6 +63,7 @@ require_function(Meta, Receiver, Name, Arity, S) ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> false;
false ->
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
remote_function(Meta, Receiver, Name, Arity, S)
end.
@@ -73,6 +77,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
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;
@@ -80,6 +85,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
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 ->
@@ -87,10 +93,11 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
{ ok, Receiver, Tree } ->
translate_expansion(Meta, Receiver, Tree, S)
end
end.
@@ -101,6 +108,7 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
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 };
@@ -109,10 +117,11 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
elixir_macros:translate({ Name, Meta, Args }, S);
{ ok, _Receiver, Tree } ->
translate_expansion(Meta, Tree, S)
{ ok, Receiver, Tree } ->
translate_expansion(Meta, Receiver, Tree, S)
end
end.
@@ -150,10 +159,12 @@ do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
end;
{ macro, Receiver } ->
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
{ import, Receiver } ->
@@ -168,6 +179,7 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
false ->
case is_element(Tuple, in_elixir_macros()) of
true ->
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
false ->
@@ -178,20 +190,12 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
Function = S#elixir_scope.function,
Fun = (Module == Receiver) andalso (Function /= nil) andalso
(Function /= Tuple) andalso elixir_def_local:macro_for(Tuple, false, S),
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true ->
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
end;
_ ->
elixir_tracker:record_local(Tuple, Module, Function),
{ ok, Receiver, expand_macro_fun(Meta, Fun(), Receiver, Name, Args, Module, S) }
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 }
end.
%% Expansion helpers
@@ -213,7 +217,7 @@ expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
catch
Kind:Reason ->
Info = { Receiver, Name, length(Args), location(Line, S) },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), SArg))
end.
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
@@ -222,17 +226,18 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
translate_expansion(Meta, Tree, S) ->
translate_expansion(Meta, Receiver, Tree, S) ->
Line = ?line(Meta),
New = S#elixir_scope.macro_counter + 1,
try
elixir_translator:translate_each(
elixir_quote:linify(Line, Tree),
S
elixir_quote:linify(Line, { Receiver, New }, Tree),
S#elixir_scope{macro_counter=New}
)
catch
Kind:Reason ->
erlang:raise(Kind, Reason, munge_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
end.
mfa(Line, #elixir_scope{module=nil} = S) ->
@@ -305,17 +310,17 @@ not_an_import(Tuple, Context, Dict) ->
end.
%% We've reached the invoked macro, skip it with the rest
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
%% We've reached the elixir_dispatch internals, skip it with the rest
munge_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
prune_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
[Info];
munge_stacktrace(Info, [H|T], S) ->
[H|munge_stacktrace(Info, T, S)];
prune_stacktrace(Info, [H|T], S) ->
[H|prune_stacktrace(Info, T, S)];
munge_stacktrace(Info, [], _) ->
prune_stacktrace(Info, [], _) ->
[Info].
remote_function(Meta, Receiver, Name, Arity, S) ->
@@ -336,13 +341,14 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
%% ERROR HANDLING
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], <<", ">>),
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
[elixir_errors:inspect(Receiver), Name, Arity, String]);
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
Module = elixir_errors: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",
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]);
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
@@ -392,11 +398,10 @@ in_elixir_macros() ->
in_erlang_functions() ->
[
{ abs, 1 },
{ atom_to_binary, 2 },
{ apply, 2 },
{ apply, 3 },
{ atom_to_list, 1 },
{ binary_part, 3 },
{ binary_to_atom, 2 },
{ binary_to_existing_atom, 2 },
{ binary_to_float, 1 },
{ binary_to_float, 2 },
{ binary_to_integer, 1 },
@@ -440,7 +445,6 @@ in_erlang_functions() ->
{ list_to_float, 1 },
{ list_to_integer, 1 },
{ list_to_integer, 2 },
{ list_to_pid, 1 },
{ list_to_tuple, 1 },
{ make_ref, 0 },
{ max, 2 },
@@ -448,7 +452,6 @@ in_erlang_functions() ->
{ node, 0 },
{ node, 1 },
% { now, 0 },
{ pid_to_list, 1 },
{ round, 1 },
{ self, 0 },
{ size, 1 },
@@ -490,8 +493,6 @@ in_erlang_macros() ->
{'>=',2},
{'@',1},
{'and',2},
{apply,2},
{apply,3},
{'case',2},
{def,1},
{def,2},
+30 -19
View File
@@ -12,18 +12,25 @@
-include("elixir.hrl").
warn(Warning) ->
elixir_code_server:cast(register_warning),
CompilerPid = get(elixir_compiler_pid),
if
CompilerPid =/= undefined ->
elixir_code_server:cast({ register_warning, CompilerPid });
true -> false
end,
io:put_chars(standard_error, Warning).
%% Handle inspecting for exceptions
%% Handle inspecting for exceptions modules
inspect(Atom) when is_atom(Atom) ->
'Elixir.Inspect.Atom':inspect(Atom).
%% Raised during macros translation.
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string()) -> no_return().
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string(), list()) -> no_return().
-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));
@@ -47,7 +54,7 @@ compile_error(Meta, File, Format, Args) ->
%% Raised on tokenizing/parsing
-spec parse_error(non_neg_integer() | list(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
parse_error(Meta, File, Error, []) ->
Message = case Error of
@@ -75,7 +82,7 @@ parse_error(Meta, File, Error, Token) ->
%% Raised during compilation
-spec form_error(non_neg_integer() | list(), file:filename(), module(), any()) -> no_return().
-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)),
@@ -158,7 +165,7 @@ handle_file_warning(_, File, {Line,Module,Desc}) ->
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
-spec handle_file_error(file:filename(), {non_neg_integer(), module(), any()}) -> no_return().
-spec handle_file_error(file:filename_all(), {non_neg_integer(), module(), any()}) -> no_return().
handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
Translated = case In of
@@ -169,6 +176,10 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
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]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc).
@@ -178,18 +189,6 @@ assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
assert_no_function_scope(Meta, Kind, S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
assert_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) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside match clause", [Kind]);
assert_no_match_scope(_Meta, _Kind, _S) -> [].
assert_no_guard_scope(Meta, Kind, #elixir_scope{context=guard} = S) ->
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside guard", [Kind]);
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
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.
@@ -198,6 +197,18 @@ 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) ->
+4 -1
View File
@@ -48,6 +48,9 @@ capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, S) when is_list(Args) ->
do_capture(Meta, Expr, S, false);
capture(Meta, { '__block__', _, [Expr] }, S) ->
capture(Meta, Expr, S);
capture(Meta, { '__block__', _, _ } = Expr, S) ->
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
@@ -59,7 +62,7 @@ capture(Meta, { Left, Right }, S) ->
capture(Meta, { '{}', Meta, [Left, Right] }, S);
capture(Meta, List, S) when is_list(List) ->
capture(Meta, { '[]', Meta, List }, S);
do_capture(Meta, List, S, 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]);
+2 -4
View File
@@ -38,15 +38,14 @@ import_macros(Force, Meta, Ref, Opts, S) ->
S#elixir_scope.macro_macros, Existing, S),
S#elixir_scope{macros=Macros, macro_macros=Temp}.
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
record_warn(Meta, Ref, Opts, S) ->
Warn =
case keyfind(warn, Opts) of
{ warn, false } -> false;
{ warn, true } -> true;
false -> not lists:keymember(context, 1, Meta)
end,
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
elixir_lexical:record_import(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
%% Calculates the imports based on only and except
@@ -202,7 +201,6 @@ special_form('__block__',_) -> true;
special_form('->',_) -> true;
special_form('<<>>',_) -> true;
special_form('{}',_) -> true;
special_form('[]',_) -> true;
special_form('alias',1) -> true;
special_form('alias',2) -> true;
special_form('require',1) -> true;
+53 -59
View File
@@ -6,62 +6,70 @@ unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
%% Extract string interpolations
extract(Line, File, Interpol, String, Last) ->
extract(Line, File, Interpol, String, [], [], [], Last).
extract(Line, Raw, Interpol, String, Last) ->
%% Ignore whatever is in the scope and enable terminator checking.
Scope = Raw#elixir_tokenizer{terminators=[], check_terminators=true},
extract(Line, Scope, Interpol, String, [], 0, [], Last).
extract(Line, File, _Interpol, [], Buffer, [], Output, []) ->
finish_extraction(Line, File, Buffer, Output, []);
%% Terminators
extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
finish_extraction(Line, Buffer, Output, []);
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
finish_extraction(Line, File, Buffer, Output, Remaining);
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, _File, _Interpol, End, _Buffer, _Search, _Output, Last) when End == [Last]; End == [] ->
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, Output, Last) ->
finish_extraction(Line, Buffer, Output, Remaining);
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
extract(Line, File, Interpol, [$\\, $#, ${|Rest], Buffer, [], Output, Last) ->
extract(Line, File, Interpol, Rest, [${,$#|Buffer], [], Output, Last);
%% Going through the string
extract(Line, File, Interpol, [$\\,Char|Rest], Buffer, [], Output, Last) ->
extract(Line, File, Interpol, Rest, [Char,$\\|Buffer], [], Output, Last);
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
extract(Line, File, true, [$#, ${|Rest], Buffer, [], Output, Last) ->
NewOutput = build_interpol(s, Line, File, Buffer, Output),
extract(Line, File, true, Rest, [], [$}], NewOutput, Last);
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Search, Output, Last);
extract(Line, File, true, [$}|Rest], Buffer, [$}], Output, Last) ->
NewOutput = build_interpol(i, Line, File, Buffer, Output),
extract(Line, File, true, Rest, [], [], NewOutput, Last);
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Search, Output, Last);
%% Check for available separators inside interpolation
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
Output1 = build_string(Line, Buffer, Output),
extract(Line, File, Interpol, [C|Rest], Buffer, [C|Search], Output, Last) when C == $); C == $]; C == $}; C == $>; C == $"; C == $' ->
extract(Line, File, Interpol, Rest, [C|Buffer], Search, Output, Last);
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
{ error, { EndLine, _, "}" }, [$}|NewRest], Tokens } ->
Output2 = build_interpol(Line, Tokens, Output1),
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
{ error, Reason, _, _ } ->
{ error, Reason };
{ ok, _EndLine, _ } ->
{ error, { string, Line, "missing interpolation terminator: }", [] } }
end;
extract(Line, File, Interpol, [C|Rest], Buffer, [_|_] = Search, Output, Last) when C == $"; C == $' ->
extract(Line, File, Interpol, Rest, [C|Buffer], [C|Search], Output, Last);
%% Matching () [] {} <> inside sigils
extract(Line, File, Interpol, [$<|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [$<|Buffer], [$>|Search], Output, Last);
extract(Line, Scope, Interpol, [$(|Rest], Buffer, Search, Output, $)) ->
extract(Line, Scope, Interpol, Rest, [$(|Buffer], Search + 1, Output, $));
extract(Line, File, Interpol, [${|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [${|Buffer], [$}|Search], Output, Last);
extract(Line, Scope, Interpol, [$[|Rest], Buffer, Search, Output, $]) ->
extract(Line, Scope, Interpol, Rest, [$[|Buffer], Search + 1, Output, $]);
extract(Line, File, Interpol, [$[|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [$[|Buffer], [$]|Search], Output, Last);
extract(Line, Scope, Interpol, [${|Rest], Buffer, Search, Output, $}) ->
extract(Line, Scope, Interpol, Rest, [${|Buffer], Search + 1, Output, $});
extract(Line, File, Interpol, [$(|Rest], Buffer, [_|_] = Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [$(|Buffer], [$)|Search], Output, Last);
extract(Line, Scope, Interpol, [$<|Rest], Buffer, Search, Output, $>) ->
extract(Line, Scope, Interpol, Rest, [$<|Buffer], Search + 1, Output, $>);
%% Else
extract(Line, Scope, Interpol, [Last|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Last|Buffer], Search - 1, Output, Last);
extract(Line, File, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
extract(Line, File, Interpol, Rest, [Char|Buffer], Search, Output, Last).
%% Catch all clause
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Search, Output, Last).
%% Unescape a series of tokens as returned by extract.
@@ -148,30 +156,16 @@ unescape_map(E) -> E.
% Extract Helpers
finish_extraction(Line, File, Buffer, Output, Remaining) ->
case build_interpol(s, Line, File, Buffer, Output) of
finish_extraction(Line, Buffer, Output, Remaining) ->
case build_string(Line, Buffer, Output) of
[] -> Final = [<<>>];
Final -> []
end,
{ Line, lists:reverse(Final), Remaining }.
build_interpol(_Kind, _Line, _File, [], Output) ->
Output;
build_string(_Line, [], Output) -> Output;
build_string(_Line, Buffer, Output) ->
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output].
build_interpol(s, _Line, _File, Buffer, Output) ->
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output];
build_interpol(i, Line, File, Buffer, Output) ->
[wrap_interpol(Line, forms(lists:reverse(Buffer), Line, File))| Output].
wrap_interpol(Line, Form) ->
Meta = [{line,Line}],
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
forms(String, StartLine, File) ->
case elixir_translator:forms(String, StartLine, File, []) of
{ ok, [] } -> nil;
{ ok, [Forms] } when not is_list(Forms) -> Forms;
{ ok, Forms } -> { '__block__', [{line,StartLine}], Forms };
{ error, Tuple } -> throw({ interpolation_error, Tuple })
end.
build_interpol(Line, Buffer, Output) ->
[{ Line, lists:reverse(Buffer) }|Output].
+80
View File
@@ -0,0 +1,80 @@
%% Module responsible for tracking lexical information.
-module(elixir_lexical).
-export([run/2,
record_alias/4, record_alias/2,
record_import/4, record_import/2,
record_remote/2, format_error/1
]).
-include("elixir.hrl").
-define(tracker, 'Elixir.Kernel.LexicalTracker').
run(File, Callback) ->
case code:is_loaded(?tracker) of
{ file, _ } ->
Pid = ?tracker:start_link(),
try
Callback(Pid)
after
warn_unused_aliases(File, Pid),
warn_unused_imports(File, Pid),
unlink(Pid), ?tracker:stop(Pid)
end;
false ->
Callback(nil)
end.
%% RECORD
record_alias(Module, Line, Warn, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:add_alias(Pid, Module, Line, Warn),
true
end).
record_import(Module, Line, Warn, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:add_import(Pid, Module, Line, Warn),
true
end).
record_alias(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:alias_dispatch(Pid, Module),
true
end).
record_import(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:import_dispatch(Pid, Module),
true
end).
record_remote(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:remote_dispatch(Pid, Module),
true
end).
%% HELPERS
if_tracker(nil, _Callback) -> false;
if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid).
%% ERROR HANDLING
warn_unused_imports(File, Pid) ->
[ begin
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
end || { M, L } <- ?tracker:collect_unused_imports(Pid)].
warn_unused_aliases(File, Pid) ->
[ begin
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_alias, M } })
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
format_error({unused_alias, Module}) ->
io_lib:format("unused alias ~ts", [elixir_errors:inspect(Module)]);
format_error({unused_import, Module}) ->
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]).
+28 -43
View File
@@ -5,55 +5,40 @@
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
case S#elixir_scope.context of
match ->
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
_ ->
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
{ TArgs, umergec(SV, SC) }
end;
Else -> Else
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 } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S) of
error ->
{ TArgs, SE } = translate_args(Args, S),
{ { tuple, ?line(Meta), TArgs }, SE };
Else -> Else
end;
translate({ '[]', _Meta, [] }, S) ->
{ { nil, 0 }, S };
translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S, allow_tail) of
error ->
[RTail|RArgs] = lists:reverse(Args),
case RTail of
{'|',_,[Left,Right]} ->
RExprs = [Left|RArgs],
TailFun = fun(ST) -> translate_each(Right, ST) end;
_ ->
RExprs = [RTail|RArgs],
TailFun = fun(ST) -> { { nil, ?line(Meta) }, ST } end
end,
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
{ Tail, ST } = TailFun(SE),
{ elixir_utils:list_to_cons(?line(Meta), Exprs, Tail), ST };
Else -> Else
end;
translate({ '{}', 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(Args, S) when is_list(Args) ->
translate({ '[]', [], Args }, 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,
+25 -45
View File
@@ -2,7 +2,7 @@
%% but do not since they need to be implemented in Erlang.
-module(elixir_macros).
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
-import(elixir_translator, [translate_each/2]).
-import(elixir_scope, [umergec/2, umergea/2]).
-import(elixir_errors, [compile_error/3, syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
@@ -40,12 +40,7 @@ translate({ '!', Meta, [Expr] }, S) ->
elixir_utils:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
{ _, TExpr, TS } = translate_in(Meta, Left, Right, S),
{ TExpr, TS };
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
{ TVar, TExpr, TS } = translate_in(Meta, Left, Right, S),
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
translate_in(Meta, Left, Right, S);
%% @
@@ -162,7 +157,7 @@ translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
FullModule = expand_module(Ref, Module, S),
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
{ New, Old } -> elixir_aliases:store(Meta, New, Old, [{warn,false}], S);
false -> S
end,
@@ -189,27 +184,6 @@ translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "~ts/4 is deprecated, please use ~ts/2 instead", [Kind, Kind]),
assert_module_scope(Meta, Kind, S),
assert_no_function_scope(Meta, Kind, S),
{ TName, SN } = translate_each(Name, S),
{ TArgs, SA } = translate_each(Args, SN),
{ TGuards, SG } = translate_each(Guards, SA),
{ TExpr, SE } = translate_each(Expr, SG),
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, false, SE), SE };
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
{ TLeft, SL } = translate_each(Left, S),
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR);
translate({ apply, Meta, Args }, S) ->
{ TArgs, NS } = translate_args(Args, S),
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
translate({ Name, Meta, Args }, S) ->
syntax_error(Meta, S#elixir_scope.file,
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
@@ -219,13 +193,7 @@ translate({ Name, Meta, Args }, S) ->
translate_in(Meta, Left, Right, S) ->
Line = ?line(Meta),
{ TLeft, SL } = case Left of
{ '_', _, Atom } when is_atom(Atom) ->
elixir_scope:build_erl_var(Line, S);
_ ->
translate_each(Left, S)
end,
{ TLeft, SL } = translate_each(Left, S),
{ TRight, SR } = translate_each(Right, SL),
Cache = (S#elixir_scope.context == nil),
@@ -242,13 +210,13 @@ translate_in(Meta, Left, Right, S) ->
{ cons, _, _, _ } ->
[H|T] = elixir_utils:cons_to_list(TRight),
Expr = lists:foldr(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
end, { op, Line, '==', Var, H }, T),
{ op, Line, 'orelse', { op, Line, '=:=', Var, X }, Acc }
end, { op, Line, '=:=', Var, H }, T),
{ Cache, Expr };
{ string, _, [H|T] } ->
Expr = lists:foldl(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
end, { op, Line, '==', Var, { integer, Line, H } }, T),
{ op, Line, 'orelse', { op, Line, '=:=', Var, { integer, Line, X } }, Acc }
end, { op, Line, '=:=', Var, { integer, Line, H } }, T),
{ Cache, Expr };
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
Expr = case { Start, End } of
@@ -277,8 +245,8 @@ translate_in(Meta, Left, Right, S) ->
end,
case TCache of
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
false -> { Var, TExpr, SV }
true -> { { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
false -> { TExpr, SV }
end.
increasing_compare(Line, Var, Start, End) ->
@@ -291,8 +259,18 @@ decreasing_compare(Line, Var, Start, End) ->
{ op, Line, '=<', Var, Start },
{ op, Line, '>=', Var, End } }.
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
%% TODO: Once we have elixir_exp, we can move this
%% clause to Elixir code and out of case.
rewrite_case_clauses([
{do,Meta1,[{'when',_,[{V,M,C},{in,_,[{V,M,C},[false,nil]]}]}],False},
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
case lists:keyfind('cond', 1, M) of
{ 'cond', true } ->
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
_ ->
Clauses
end;
rewrite_case_clauses(Clauses) ->
Clauses.
@@ -307,7 +285,8 @@ expand_module({ '__aliases__', _, [H] }, _Module, S) ->
%% defmodule Hello.World
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases,
S#elixir_scope.lexical_tracker) of
Atom when is_atom(Atom) ->
Module;
Aliases when is_list(Aliases) ->
@@ -319,6 +298,7 @@ expand_module(_Raw, Module, S) ->
elixir_aliases:concat([S#elixir_scope.module, Module]).
is_reserved_data(moduledoc) -> true;
is_reserved_data(typedoc) -> true;
is_reserved_data(doc) -> true;
is_reserved_data(_) -> false.
+93 -41
View File
@@ -3,12 +3,14 @@
eval_quoted/4, format_error/1, eval_callbacks/5]).
-include("elixir.hrl").
-define(docs_attr, '__docs_table').
-define(acc_attr, '__acc_attributes').
-define(docs_attr, '__docs_table').
-define(lexical_attr, '__lexical_tracker').
-define(persisted_attr, '__persisted_attributes').
-define(overridable_attr, '__overridable').
eval_quoted(Module, Quoted, RawBinding, Opts) ->
Binding = elixir_scope:binding_for_eval(RawBinding, Module),
Binding = orddict:store('_@MODULE', Module, RawBinding),
Scope = scope_for_eval(Module, Opts),
elixir_def:reset_last(Module),
@@ -23,7 +25,6 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
scope_for_eval(Module, #elixir_scope{} = S) ->
S#elixir_scope{module=Module};
scope_for_eval(Module, Opts) ->
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
@@ -42,9 +43,14 @@ docs_table(Module) ->
%% will be passed to the invoked function.
translate(Meta, Ref, Block, S) ->
Line = ?line(Meta),
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, S),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Line = ?line(Meta),
LexS = case S#elixir_scope.function of
nil -> S;
_ -> S#elixir_scope{lexical_tracker=nil}
end,
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, LexS),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, LexS),
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
?wrap_call(Line, ?MODULE, compile, Args).
@@ -62,15 +68,16 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
FileList = elixir_utils:characters_to_list(File),
check_module_availability(Line, File, Module, C),
build(Line, File, Module),
build(Line, File, Module, S#elixir_scope.lexical_tracker),
try
Result = eval_form(Line, Module, Block, Vars, S),
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
Forms1 = specs_form(Module, Private, Defmacro, Forms0, C),
Forms2 = attributes_form(Line, File, Module, Forms1),
Forms3 = typedocs_form(Module, Forms2),
case ets:lookup(data_table(Module), 'on_load') of
[] -> ok;
@@ -80,16 +87,16 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
elixir_tracker:ensure_all_imports_used(Line, File, Module),
elixir_tracker:warn_unused_local(File, Module, Private),
warn_unused_docs(Line, File, Module, All),
warn_invalid_clauses(Line, File, Module, All),
warn_unused_docs(Line, File, Module),
Final = [
{ attribute, Line, file, { FileList, Line } },
{ attribute, Line, module, Module } | Forms2
{ attribute, Line, module, Module } | Forms3
],
Binary = load_form(Line, Final, S),
Binary = load_form(Line, Final, compile_opts(Module), S),
{ module, Module, Binary, Result }
after
elixir_tracker:cleanup(Module),
@@ -108,7 +115,7 @@ compile(Line, Module, Block, Vars, RawS) ->
%% Hook that builds both attribute and functions and set up common hooks.
build(Line, File, Module) ->
build(Line, File, Module, Lexical) ->
%% Table with meta information about the module.
DataTable = data_table(Module),
@@ -118,7 +125,6 @@ build(Line, File, Module) ->
end,
ets:new(DataTable, [set, named_table, public]),
ets:insert(DataTable, { '__overridable', [] }),
ets:insert(DataTable, { before_compile, [] }),
ets:insert(DataTable, { after_compile, [] }),
@@ -128,9 +134,11 @@ build(Line, File, Module) ->
end,
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
ets:insert(DataTable, { ?acc_attr, [before_compile,after_compile,on_definition|Attributes] }),
ets:insert(DataTable, { ?acc_attr, [before_compile, after_compile, on_definition|Attributes] }),
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
ets:insert(DataTable, { ?lexical_attr, Lexical }),
ets:insert(DataTable, { ?overridable_attr, [] }),
%% Setup other modules
elixir_def:setup(Module),
@@ -198,9 +206,21 @@ attributes_form(Line, _File, Module, Current) ->
ets:foldl(Transform, Current, Table).
%% Add typedocs to the form
typedocs_form(Module, Current) ->
Table = docs_table(Module),
Transform = fun({ Tuple, Line, Kind, _Sig, Doc }, Acc) ->
case Kind of
type -> [{ attribute, Line, typedoc, { Tuple, Doc } } | Acc];
opaque -> [{ attribute, Line, typedoc, { Tuple, Doc } } | Acc];
_ -> Acc
end
end,
ets:foldl(Transform, Current, Table).
%% Specs
specs_form(Line, Module, Private, Defmacro, Forms, C) ->
specs_form(Module, Private, Defmacro, Forms, C) ->
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
case elixir_compiler:get_opt(internal, C) of
true -> Forms;
@@ -212,16 +232,17 @@ specs_form(Line, Module, Private, Defmacro, Forms, C) ->
'Elixir.Module':delete_attribute(Module, spec),
'Elixir.Module':delete_attribute(Module, callback),
Temp = specs_attributes(Line, spec, Forms, Specs),
specs_attributes(Line, callback, Temp, Callbacks)
Temp = specs_attributes(spec, Forms, Specs),
specs_attributes(callback, Temp, Callbacks)
end.
specs_attributes(Line, Type, Forms, Specs) ->
specs_attributes(Type, Forms, Specs) ->
Keys = lists:foldl(fun({ Tuple, Value }, Acc) ->
lists:keystore(Tuple, 1, Acc, { Tuple, Value } )
lists:keystore(Tuple, 1, Acc, { Tuple, Value })
end, [], Specs),
lists:foldl(fun({ Tuple, _ }, Acc) ->
Values = [V || { K, V } <- Specs, K == Tuple],
{ type, Line, _, _ } = hd(Values),
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
end, Forms, Keys).
@@ -246,8 +267,14 @@ spec_for_macro(Else) -> Else.
%% Loads the form into the code server.
load_form(Line, Forms, #elixir_scope{file=File} = S) ->
elixir_compiler:module(Forms, File, fun(Module, Binary) ->
compile_opts(Module) ->
case ets:lookup(data_table(Module), compile) of
[{compile,Opts}] when is_list(Opts) -> Opts;
[] -> []
end.
load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
EvalS = scope_for_eval(Module, S),
Env = elixir_scope:to_ex_env({ Line, EvalS }),
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
@@ -259,8 +286,9 @@ load_form(Line, Forms, #elixir_scope{file=File} = S) ->
case get(elixir_compiler_pid) of
undefined -> [];
PID ->
PID ! { module_available, self(), File, Module, Binary },
receive { PID, ack } -> ok end
Ref = make_ref(),
PID ! { module_available, self(), Ref, File, Module, Binary },
receive { Ref, ack } -> ok end
end;
_ ->
[]
@@ -282,18 +310,31 @@ check_module_availability(Line, File, Module, Compiler) ->
[]
end.
warn_unused_docs(_Line, _File, 'Elixir.Kernel', _All) -> ok;
warn_unused_docs(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
warn_unused_docs(_Line, File, Module, All) ->
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel', _All) -> ok;
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
warn_invalid_clauses(_Line, File, Module, All) ->
ets:foldl(fun
({ Tuple, Line, _, _, _ }, Acc) ->
({ _, _, Kind, _, _ }, _) when Kind == type; Kind == opaque ->
ok;
({ Tuple, Line, _, _, _ }, _) ->
case lists:member(Tuple, All) of
true -> Acc;
false ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { invalid_doc, Tuple } })
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { invalid_clause, Tuple } });
true ->
ok
end
end, ok, docs_table(Module)).
warn_unused_docs(Line, File, Module) ->
lists:foreach(fun(Attribute) ->
case ets:member(data_table(Module), Attribute) of
true ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_doc, Attribute } });
_ ->
ok
end
end, [typedoc]).
% EXTRA FUNCTIONS
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
@@ -329,7 +370,10 @@ module_clause(Module) ->
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
docs_clause(Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
Docs = ordsets:from_list(
[{Tuple, Line, Kind, Sig, Doc} ||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
Kind =/= type, Kind =/= opaque]),
{ clause, 0, [{ atom, 0, docs }], [], [elixir_utils:elixir_to_erl(Docs)] };
docs_clause(_Module, _) ->
@@ -349,8 +393,7 @@ else_clause() ->
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
Binding = elixir_scope:binding_for_eval([], Module),
S = elixir_scope:vars_from_binding(RawS, Binding),
{ Binding, S } = elixir_scope:load_binding([], RawS, Module),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Meta = [{line,Line},{require,false}],
@@ -369,25 +412,34 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
catch
Kind:Reason ->
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace()))
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
end
end
end, Callbacks).
%% We've reached the elixir_dispatch internals, skip it with the rest
munge_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
%% We've reached the elixir_module or erl_eval internals, skip it with the rest
prune_stacktrace(Info, [{ erl_eval, _, _, _ }|_]) ->
[Info];
munge_stacktrace(Info, [H|T]) ->
[H|munge_stacktrace(Info, T)];
prune_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
[Info];
munge_stacktrace(Info, []) ->
prune_stacktrace(Info, [H|T]) ->
[H|prune_stacktrace(Info, T)];
prune_stacktrace(Info, []) ->
[Info].
% ERROR HANDLING
format_error({ invalid_doc, { Name, Arity } }) ->
io_lib:format("docs provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
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]);
+51 -30
View File
@@ -20,7 +20,7 @@ Nonterminals
call_args_no_parens_kw_expr call_args_no_parens_kw_comma call_args_no_parens_kw
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_bracket_identifier
var list bracket_access bit_string tuple
var list bit_string tuple
do_block fn_eol do_eol end_eol block_eol block_item block_list
.
@@ -28,7 +28,7 @@ Terminals
identifier kw_identifier bracket_identifier
paren_identifier do_identifier block_identifier
fn 'end' aliases
number signed_number atom bin_string list_string sigil
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
@@ -179,20 +179,20 @@ max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens(
max_expr -> dot_alias : '$1'.
max_expr -> base_expr : '$1'.
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
bracket_expr -> dot_bracket_identifier list : build_access(build_identifier('$1', nil), '$2').
bracket_expr -> max_expr list : build_access('$1', '$2').
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_access :
bracket_at_expr -> at_op_eol dot_bracket_identifier list :
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
bracket_at_expr -> at_op_eol max_expr bracket_access :
bracket_at_expr -> at_op_eol max_expr list :
build_access(build_unary_op('$1', '$2'), '$3').
bracket_at_expr -> bracket_at_expr bracket_access :
bracket_at_expr -> bracket_at_expr list :
build_access('$1', '$2').
base_expr -> atom : ?exprs('$1').
base_expr -> number : ?exprs('$1').
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> atom : build_atom('$1').
base_expr -> list : '$1'.
base_expr -> list : element(1, '$1').
base_expr -> tuple : '$1'.
base_expr -> 'true' : ?id('$1').
base_expr -> 'false' : ?id('$1').
@@ -200,6 +200,7 @@ base_expr -> 'nil' : ?id('$1').
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> bin_string : build_bin_string('$1').
base_expr -> list_string : build_list_string('$1').
base_expr -> atom_string : build_atom_string('$1').
base_expr -> bit_string : '$1'.
base_expr -> sigil : build_sigil('$1').
base_expr -> '&' number : { '&', [{line,?line('$1')}], [?exprs('$2')] }.
@@ -434,21 +435,17 @@ kw -> kw_expr : ['$1'].
kw -> kw_comma : lists:reverse('$1').
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : {?exprs('$1'),'$2'}.
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : { ?exprs('$1'),'$2' }.
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr : ['$1'].
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw_comma : ['$1'|'$3'].
call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
% Lists
bracket_access -> open_bracket ']' : { [], ?line('$1') }.
bracket_access -> open_bracket container_arg close_bracket : { '$2', ?line('$1') }.
bracket_access -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
list -> open_bracket ']' : [].
list -> open_bracket kw close_bracket : '$2'.
list -> open_bracket container_arg close_bracket : ['$2'].
list -> open_bracket container_expr ',' container_args close_bracket : ['$2'|'$4'].
list -> open_bracket ']' : { [], ?line('$1') }.
list -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
list -> open_bracket container_arg close_bracket : { ['$2'], ?line('$1') }.
list -> open_bracket container_expr ',' container_args close_bracket : { ['$2'|'$4'], ?line('$1') }.
% Tuple
@@ -548,32 +545,56 @@ extract_identifier(Other) -> Other.
%% Fn
build_fn(Op, Stab) ->
{ fn, [{line,?line(Op)}], [[{ do, Stab }]] }.
{ fn, [{line,?line(Op)}], [Stab] }.
%% Access
build_access(Expr, Access) ->
Meta = [{line,?line(Access)}],
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?id(Access) ] }.
build_access(Expr, { List, Line }) ->
Meta = [{line,Line}],
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [Expr, List] }.
%% Interpolation aware
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
Meta = [{line,Line}],
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, string_parts(Parts) }, Modifiers ] }.
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) ->
H;
build_bin_string({ bin_string, Line, Args }) ->
{ '<<>>', [{line,Line}], string_parts(Args) }.
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> elixir_utils:characters_to_list(H);
build_list_string({ list_string, _Line, [H] }) when is_binary(H) ->
elixir_utils:characters_to_list(H);
build_list_string({ list_string, Line, Args }) ->
Meta = [{line,Line}],
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, Args}] }.
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, string_parts(Args) }] }.
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
build_atom({ atom, Line, Args }) ->
build_atom_string({ atom_string, _Line, Safe, [H] }) when is_binary(H) ->
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
build_atom_string({ atom_string, Line, Safe, Args }) ->
Meta = [{line,Line}],
{ { '.', Meta, [erlang, binary_to_atom] }, Meta, [{ '<<>>', Meta, Args }, utf8] }.
{ { '.', Meta, [erlang, binary_to_atom_op(Safe)] }, Meta, [{ '<<>>', Meta, string_parts(Args) }, utf8] }.
binary_to_atom_op(true) -> binary_to_existing_atom;
binary_to_atom_op(false) -> binary_to_atom.
string_parts(Parts) ->
[string_part(Part) || Part <- Parts].
string_part(Binary) when is_binary(Binary) ->
Binary;
string_part({ Line, Tokens }) ->
Form = string_tokens_parse(Line, Tokens),
Meta = [{line,Line}],
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
string_tokens_parse(Line, Tokens) ->
case parse(Tokens) of
{ ok, [] } -> nil;
{ ok, [Forms] } when not is_list(Forms) -> Forms;
{ ok, Forms } -> { '__block__', [{line, Line}], Forms };
{ error, _ } = Error -> throw(Error)
end.
%% Keywords
-65
View File
@@ -1,65 +0,0 @@
%% Responsible handling Elixir partials with &1, &2 and so on.
-module(elixir_partials).
-export([handle/2, handle/3, is_sequential/1]).
-include("elixir.hrl").
handle(Original, S) ->
handle(Original, S, default).
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= match ->
case convert(Args, S, Opt) of
{ Call, Def, SC } when Def /= [] ->
Final = validate(Meta, Def, SC),
Block = setelement(3, Original, Call),
elixir_fn:fn(Meta, [{ Final, Meta, Block }], SC);
_ -> error
end;
handle(_Original, _S, _Opt) ->
error.
validate(Meta, Def, S) ->
validate(Meta, lists:sort(Def), 1, S).
validate(Meta, [{ Pos, Item }|T], Pos, S) ->
[Item|validate(Meta, T, Pos + 1, S)];
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "partial variable &~w cannot be defined without &~w", [Pos, Expected]);
validate(_Meta, [], _Pos, _S) ->
[].
%% This function receives arguments and then checks
%% the args for partial application. It returns a tuple
%% with three elements where the first element is the list
%% of call args, the second the list of def args for the
%% function definition and the third one is the new scope.
convert(List, S, Opt) -> convert(List, S, Opt, [], []).
convert([{'|', Meta, [_, _] = Args}|T], S, allow_tail, CallAcc, DefAcc) ->
{ NewArgs, NewDef, NewS } = convert(Args, S, allow_tail, [], DefAcc),
convert(T, NewS, allow_tail, [{ '|', Meta, NewArgs}|CallAcc], NewDef);
convert([{'&', Meta, [Pos]}|T], S, Opt, CallAcc, DefAcc) when is_integer(Pos) ->
case lists:keyfind(Pos, 1, DefAcc) of
false ->
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
convert(T, SC, Opt, [Var|CallAcc], [{Pos,Var}|DefAcc]);
{Pos,Var} ->
convert(T, S, Opt, [Var|CallAcc], DefAcc)
end;
convert([H|T], S, Opt, CallAcc, DefAcc) ->
convert(T, S, Opt, [H|CallAcc], DefAcc);
convert([], S, _Opt, CallAcc, DefAcc) ->
{ lists:reverse(CallAcc), lists:reverse(DefAcc), S }.
is_sequential(List) -> is_sequential(List, 1).
is_sequential([{ '&', _, [Int] }|T], Int) ->
is_sequential(T, Int + 1);
is_sequential([], Int) when Int > 1 -> true;
is_sequential(_, _Int) -> false.
+88 -42
View File
@@ -1,33 +1,47 @@
%% Implements Elixir quote.
-module(elixir_quote).
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/6, join/5]).
-export([escape/2, erl_escape/3, erl_quote/4,
linify/2, linify/3, unquote/4, tail_join/3, join/2]).
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
linify(Line, Exprs) when is_integer(Line) ->
do_linify(Line, Exprs).
do_linify(Line, nil, Exprs).
do_linify(Line, { Left, Meta, Right }) ->
linify(Line, Var, Exprs) when is_integer(Line) ->
do_linify(Line, Var, Exprs).
do_linify(Line, { Receiver, Counter } = Var, { Left, Meta, Receiver }) when is_atom(Left), is_list(Meta) ->
NewMeta = case keyfind(counter, Meta) of
{ counter, _ } -> Meta;
_ -> keystore(counter, Meta, Counter)
end,
do_tuple_linify(Line, Var, NewMeta, Left, Receiver);
do_linify(Line, Var, { Left, Meta, Right }) when is_list(Meta) ->
do_tuple_linify(Line, Var, Meta, Left, Right);
do_linify(Line, Var, { Left, Right }) ->
{ do_linify(Line, Var, Left), do_linify(Line, Var, Right) };
do_linify(Line, Var, List) when is_list(List) ->
[do_linify(Line, Var, X) || X <- List];
do_linify(_, _, Else) -> Else.
do_tuple_linify(Line, Var, Meta, Left, Right) ->
NewMeta = case ?line(Meta) of
0 -> keystore(line, Meta, Line);
_ -> Meta
end,
{ do_linify(Line, Left), NewMeta, do_linify(Line, Right) };
do_linify(Line, { Left, Right }) ->
{ do_linify(Line, Left), do_linify(Line, Right) };
do_linify(Line, List) when is_list(List) ->
[do_linify(Line, X) || X <- List];
do_linify(_, Else) -> Else.
{ do_linify(Line, Var, Left), NewMeta, do_linify(Line, Var, Right) }.
%% Some expressions cannot be unquoted at compilation time.
%% This function is responsible for doing runtime unquoting.
unquote(_File, _Line, Meta, Left, { '__aliases__', _, Args }, nil) ->
unquote(Meta, Left, { '__aliases__', _, Args }, nil) ->
{ '__aliases__', Meta, [Left|Args] };
unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
unquote(Meta, Left, Right, nil) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir." ++ _ ->
{ '__aliases__', Meta, [Left, Right] };
@@ -35,33 +49,48 @@ unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, [] }
end;
unquote(_File, _Line, Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
unquote(Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
{ { '.', Meta, [Left, Right] }, Meta, [] };
unquote(_File, _Line, Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
unquote(Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(File, Line, _Meta, _Left, Right, nil) ->
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom, "
"an alias or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]);
unquote(_Meta, _Left, Right, nil) ->
argument_error(<<"expected unquote after dot to return an atom, an alias or a quoted call, got: ",
('Elixir.Macro':to_string(Right))/binary>>);
unquote(_File, _Line, Meta, Left, Right, Args) when is_atom(Right) ->
unquote(Meta, Left, Right, Args) when is_atom(Right) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(_File, _Line, Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
unquote(Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
{ { '.', Meta, [Left, Right] }, Meta, Args };
unquote(File, Line, _Meta, _Left, Right, _Args) ->
elixir_errors:syntax_error(Line, File, "expected unquote after dot with args to return an atom "
"or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]).
unquote(_Meta, _Left, Right, _Args) ->
argument_error(<<"expected unquote after dot with args to return an atom or a quoted call, got: ",
('Elixir.Macro':to_string(Right))/binary>>).
join(_File, _Line, Left, Right, Rest) when is_list(Left), is_list(Right), is_list(Rest) ->
Rest ++ Left ++ Right;
join(Left, Right) when is_list(Right) ->
validate_join(Left),
Left ++ Right.
join(_File, _Line, Left, Right, Rest) ->
[H|T] = lists:reverse(Rest ++ Left),
tail_join(Left, Right, Tail) when is_list(Right), is_list(Tail) ->
validate_join(Left),
Tail ++ Left ++ Right;
tail_join(Left, Right, Tail) when is_list(Left) ->
validate_join(Left),
[H|T] = lists:reverse(Tail ++ Left),
lists:reverse([{ '|', [], [H, Right] }|T]).
validate_join(List) when is_list(List) ->
ok;
validate_join(List) when not is_list(List) ->
argument_error(<<"expected a list with quoted expressions in unquote_splicing/1, got: ",
('Elixir.Kernel':inspect(List))/binary>>).
argument_error(Message) ->
error('Elixir.ArgumentError':exception([{message,Message}])).
%% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Unquote) ->
@@ -113,7 +142,24 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
%% Context mark
do_quote({ Def, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
do_quote({ defmodule, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) ->
%% Only store the context if we actually have a full alias as
%% argument, otherwise the expression is being automatically
%% generated and we don't want the alias to count.
NewMeta =
case hd(Args) of
{ '__aliases__', _, Atoms } ->
case lists:all(fun is_atom/1, Atoms) of
true -> keystore(context, Meta, Q#elixir_quote.context);
false -> Meta
end;
_ ->
Meta
end,
do_quote_tuple({ defmodule, NewMeta, Args }, Q, S);
do_quote({ Def, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == alias; Def == import ->
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
@@ -128,7 +174,7 @@ do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
Atom when is_atom(Atom) -> Atom;
Aliases when is_list(Aliases) -> false
end,
@@ -214,9 +260,9 @@ do_quote(Other, Q, _) ->
%% Quote helpers
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
All = [?line(Meta), meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, TAll }, TQ }.
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
NewMeta =
@@ -264,24 +310,24 @@ do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_q
%% 1, 2 and 3, which could even be unquotes.
{ TT, QT } = do_splice(T, Q, S, [], []),
{ TR, QR } = do_quote(Right, QT, S),
%% Do the joining at runtime when we are aware of the values.
Args = [{ '__FILE__', [], nil }, ?line(Meta), Left, TR, TT],
{ { { '.', Meta, [elixir_quote, join] }, Meta, Args }, QR#elixir_quote{unquoted=true} };
{ do_runtime_join(Meta, tail_join, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
do_splice(List, Q, S) ->
do_splice(List, Q, S, [], []).
do_splice([{ unquote_splicing, _, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_splice_join(do_splice_join(Expr, Buffer), Acc));
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_runtime_join(Meta, join, [Expr, do_join(Buffer, Acc)]));
do_splice([H|T], Q, S, Buffer, Acc) ->
{ TH, TQ } = do_quote(H, Q, S),
do_splice(T, TQ, S, [TH|Buffer], Acc);
do_splice([], Q, _S, Buffer, Acc) ->
{ do_splice_join(Buffer, Acc), Q }.
{ do_join(Buffer, Acc), Q }.
do_splice_join(Left, []) -> Left;
do_splice_join([], Right) -> Right;
do_splice_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
do_join(Left, []) -> Left;
do_join([], Right) -> Right;
do_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
do_runtime_join(Meta, Fun, Args) ->
{ { '.', Meta, [elixir_quote, Fun] }, Meta, Args }.
+72 -51
View File
@@ -2,17 +2,20 @@
-module(elixir_scope).
-export([translate_var/5,
build_erl_var/2, build_ex_var/2,
serialize/1, deserialize/1,
serialize/1, deserialize/1, to_erl/1,
serialize_with_vars/2, deserialize_with_vars/2,
to_erl_env/1, to_ex_env/1,
vars_from_binding/2, binding_for_eval/2, binding_from_vars/2,
load_binding/3, dump_binding/2,
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
]).
-include("elixir.hrl").
translate_var(Meta, Name, Kind, S, Callback) ->
%% 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 },
Tuple = { Name, Kind },
case Name of
@@ -41,7 +44,7 @@ translate_var(Meta, Name, Kind, S, Callback) ->
true -> ordsets:add_element(Tuple, TempVars)
end,
clause_vars=if
ClauseVars == nil; Kind == false -> ClauseVars;
ClauseVars == nil; Temp == true -> ClauseVars;
true -> orddict:store(Tuple, RealName, ClauseVars)
end
} }
@@ -78,43 +81,54 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
Var = { ?atom_concat([Name, "@", Counter]), [{line,Line}], Context },
{ Var, NS }.
% Handle Macro.Env conversion
%% Macro.Env <-> #elixir_scope conversion
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
Requires, Functions, Macros, ContextModules, MacroAliases, Vars }) ->
Requires, Functions, Macros, ContextModules, MacroAliases, _Vars, Lexical }) ->
#elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases,
list_vars=Vars}.
lexical_tracker=Lexical}.
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros,functions=Functions,
context_modules=ContextModules,macro_aliases=MacroAliases,
vars=Vars,list_vars=ListVars} }) when is_integer(Line) ->
vars=Vars,lexical_tracker=Lexical} }) when is_integer(Line) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
Context, Requires, Functions, Macros, ContextModules, MacroAliases,
list_vars(ListVars, Vars) }.
list_vars(Vars), Lexical }.
list_vars(nil, Vars) -> [K || { K, _ } <- Vars];
list_vars(Other, _) -> Other.
list_vars(Vars) -> [Pair || { { _, K } = Pair, _ } <- Vars, is_atom(K)].
% Provides a tuple with only the scope information we want to serialize.
%% SERIALIZATION
%% When serializing scopes, we support serialization of pids.
to_erl(Structure) ->
elixir_utils:elixir_to_erl(Structure, fun
(X) when is_pid(X) ->
?wrap_call(0, erlang, binary_to_term, [elixir_utils:elixir_to_erl(term_to_binary(X))]);
(Other) ->
error({ badarg, Other })
end).
serialize(S) ->
elixir_utils:elixir_to_erl(
{ S#elixir_scope.file, S#elixir_scope.functions,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
S#elixir_scope.context_modules }
).
to_erl({ S#elixir_scope.file, S#elixir_scope.functions,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
S#elixir_scope.context_modules, S#elixir_scope.lexical_tracker }).
serialize_with_vars(Line, S) when is_integer(Line) ->
{ Vars, _ } = orddict:fold(fun({ Key, Kind }, Value, { Acc, Counter }) ->
KindKey = if
is_atom(Kind) -> atom;
is_integer(Kind) -> integer
end,
{ { cons, Line, { tuple, Line, [
{ atom, Line, Key },
{ atom, Line, Kind },
{ KindKey, Line, Kind },
{ atom, Line, ?atom_concat(["_@", Counter]) },
{ var, Line, Value }
] }, Acc }, Counter + 1 }
@@ -125,8 +139,8 @@ serialize_with_vars(Line, S) when is_integer(Line) ->
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
deserialize_with_vars({ File, Functions, Requires, Macros,
Aliases, MacroFunctions, MacroMacros, MacroAliases, FileModules }, Vars) ->
deserialize_with_vars({ File, Functions, Requires, Macros, Aliases, MacroFunctions,
MacroMacros, MacroAliases, FileModules, LexicalTracker }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
@@ -138,9 +152,12 @@ deserialize_with_vars({ File, Functions, Requires, Macros,
macro_aliases=MacroAliases,
context_modules=FileModules,
vars=orddict:from_list(Vars),
lexical_tracker=LexicalTracker,
counter=[{'',length(Vars)}]
}.
%% SCOPE MERGING
%% Receives two scopes and return a new scope based on the second
%% with their variables merged.
@@ -160,6 +177,7 @@ umergev(S1, S2) ->
umergec(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
@@ -202,39 +220,42 @@ 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)).
%% Setup the vars in scope from binding
%% BINDINGS
vars_from_binding(Scope, Binding) ->
Scope#elixir_scope{
vars=binding_dict(Binding),
load_binding(Binding, Scope, Module) ->
{ NewBinding, NewVars, NewCounter } = load_binding(Binding, [], [], 0, Module),
{ NewBinding, Scope#elixir_scope{
vars=NewVars,
temp_vars=[],
clause_vars=nil,
counter=[]
}.
counter=[{'',NewCounter}]
} }.
binding_dict(List) -> binding_dict(List, orddict:new()).
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
binding_dict([], Dict) -> Dict.
load_binding([{'_@MODULE',Value}|T], Binding, Vars, Counter, _Module) ->
load_binding(T, Binding, Vars, Counter, Value);
load_binding([{Key,Value}|T], Binding, Vars, Counter, Module) ->
Actual = case Key of
{ _Name, _Kind } -> Key;
Name when is_atom(Name) -> { Name, nil }
end,
InternalName = ?atom_concat(["_@", Counter]),
load_binding(T,
[{InternalName,Value}|Binding],
orddict:store(Actual, InternalName, Vars),
Counter + 1, Module);
load_binding([], Binding, Vars, Counter, Module) ->
{ lists:reverse([{'_@MODULE',Module}|Binding]), Vars, Counter }.
binding_for_eval(Binding, Module) ->
Keyword = orddict:from_list(Binding),
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_@MODULE', Module, Keyword)
end.
dump_binding(Binding, #elixir_scope{vars=Vars}) ->
dump_binding(Vars, Binding, []).
binding_from_vars(#elixir_scope{vars=Vars}, Binding) ->
binding_from_vars(Binding, [], Binding, Vars).
binding_from_vars([{Var,_}|T], Acc, Binding, Vars) ->
case lists:member($@, atom_to_list(Var)) of
true ->
binding_from_vars(T, Acc, Binding, Vars);
false ->
RealName = orddict:fetch({ Var, nil }, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
binding_from_vars(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
binding_from_vars([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
dump_binding([{{Var,Kind}=Key,InternalName}|T], Binding, Acc) when is_atom(Kind) ->
Actual = case Kind of
nil -> Var;
_ -> Key
end,
Value = proplists:get_value(InternalName, Binding, nil),
dump_binding(T, Binding, orddict:store(Actual, Value, Acc));
dump_binding([_|T], Binding, Acc) ->
dump_binding(T, Binding, Acc);
dump_binding([], _Binding, Acc) -> Acc.
+118 -113
View File
@@ -3,13 +3,6 @@
-export([tokenize/3]).
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
-record(scope, {
file,
terminators=[],
check_terminators=true,
existing_atoms_only=false
}).
-define(container(T1, T2),
T1 == ${, T2 == $};
T1 == $[, T2 == $]
@@ -96,6 +89,9 @@
-define(type_op(T1, T2),
T1 == $:, T2 == $:).
tokenize(String, Line, #elixir_tokenizer{} = Scope) ->
tokenize(String, Line, Scope, []);
tokenize(String, Line, Opts) ->
File = case lists:keyfind(file, 1, Opts) of
{ file, V1 } -> V1;
@@ -112,21 +108,19 @@ tokenize(String, Line, Opts) ->
false -> true
end,
Scope = #scope{
tokenize(String, Line, #elixir_tokenizer{
file=File,
existing_atoms_only=Existing,
check_terminators=Check
},
}).
tokenize(String, Line, Scope, []).
tokenize([], Line, #elixir_tokenizer{terminators=[]}, Tokens) ->
{ ok, Line, lists:reverse(Tokens) };
tokenize([], _Line, #scope{terminators=[]}, Tokens) ->
{ ok, lists:reverse(Tokens) };
tokenize([], EndLine, #scope{terminators=[{ Start, StartLine }|_]}, _Tokens) ->
tokenize([], EndLine, #elixir_tokenizer{terminators=[{ Start, StartLine }|_]}, Tokens) ->
End = terminator(Start),
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
{ error, { EndLine, Message, [] } };
{ error, { EndLine, Message, [] }, [], Tokens };
% Base integers
@@ -150,69 +144,69 @@ tokenize([$#|String], Line, Scope, Tokens) ->
% Sigils
tokenize([$%,S,H,H,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
case extract_heredoc_with_interpolation(Line, File, ?is_downcase(S), T, H) of
{ error, _ } = Error ->
Error;
tokenize([$%,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
{ error, Reason } ->
{ error, Reason, Original, Tokens };
{ Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
end;
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
case elixir_interpolation:extract(Line, File, ?is_downcase(S), T, sigil_terminator(H)) of
tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
case elixir_interpolation:extract(Line, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
{ NewLine, Parts, Rest } ->
{ Final, Modifiers } = collect_modifiers(Rest, []),
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
Error ->
{ error, Reason } ->
Sigil = [$%,S,H],
interpolation_error(Error, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
end;
% Char tokens
tokenize([$?,$\\,P,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,E,F,$}]))),
Char = escape_char([$\\,P,${,A,B,C,D,E,F,$}]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,D,E,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,E,$}]))),
Char = escape_char([$\\,P,${,A,B,C,D,E,$}]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,D,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,$}]))),
Char = escape_char([$\\,P,${,A,B,C,D,$}]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,B,C,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,$}]))),
Char = escape_char([$\\,P,${,A,B,C,$}]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,B,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,$}]))),
Char = escape_char([$\\,P,${,A,B,$}]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,${,A,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,$}]))),
Char = escape_char([$\\,P,${,A,$}]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,A,B|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,A,B]))),
Char = escape_char([$\\,P,A,B]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,P,A|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,A]))),
Char = escape_char([$\\,P,A]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,A,B,C|T], Line, Scope, Tokens) when ?is_octal(A), A =< $3,?is_octal(B), ?is_octal(C) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A,B,C]))),
Char = escape_char([$\\,A,B,C]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,A,B|T], Line, Scope, Tokens) when ?is_octal(A), ?is_octal(B) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A,B]))),
Char = escape_char([$\\,A,B]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,A|T], Line, Scope, Tokens) when ?is_octal(A) ->
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A]))),
Char = escape_char([$\\,A]),
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
@@ -266,18 +260,18 @@ tokenize([$.,T|Rest], Line, Scope, Tokens) when
tokenize([$.,$(|Rest], Line, Scope, Tokens) ->
tokenize([$(|Rest], Line, Scope, add_token_with_nl({ dot_call_op, Line, '.' }, Tokens));
tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
tokenize([$.,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, Scope, true, T, H) of
{ NewLine, [Part], Rest } when is_binary(Part) ->
case unsafe_to_atom(Part, Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
{ ok, Atom } ->
Token = check_call_identifier(identifier, Line, Atom, Rest),
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)])
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end;
Error ->
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
{ error, Reason } ->
interpolation_error(Reason, Original, Tokens, " (for function name starting at line ~B)", [Line])
end;
% Heredocs
@@ -297,30 +291,23 @@ tokenize([$'|T], Line, Scope, Tokens) ->
% Atoms
tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
tokenize([$:,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
case elixir_interpolation:extract(Line, Scope, true, T, H) of
{ NewLine, Parts, Rest } ->
SafeAtom = case unescape_tokens(Parts) of
[Part] when is_list(Part) or is_binary(Part) -> unsafe_to_atom(Part, Line, Scope);
Unescaped -> Unescaped
end,
case SafeAtom of
{ error, _ } = Error ->
Error;
Atom ->
tokenize(Rest, NewLine, Scope, [{ atom, Line, Atom }|Tokens])
end;
Error ->
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
Unescaped = unescape_tokens(Parts),
ExistingAtomsOnly = Scope#elixir_tokenizer.existing_atoms_only,
tokenize(Rest, NewLine, Scope, [{ atom_string, Line, ExistingAtomsOnly, Unescaped }|Tokens]);
{ error, Reason } ->
interpolation_error(Reason, Original, Tokens, " (for atom starting at line ~B)", [Line])
end;
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_atom_start(T) ->
tokenize([$:,T|String] = Original, Line, Scope, Tokens) when ?is_atom_start(T) ->
{ Rest, Part } = tokenize_atom([T|String], []),
case unsafe_to_atom(Part, Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens])
{ ok, Atom } ->
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end;
% Atom identifiers/operators
@@ -474,18 +461,18 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
% Aliases
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
{ Rest, Alias } = tokenize_identifier(String, []),
tokenize([H|_] = Original, Line, Scope, Tokens) when ?is_upcase(H) ->
{ Rest, Alias } = tokenize_identifier(Original, []),
case unsafe_to_atom(Alias, Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
{ ok, Atom } ->
case Rest of
[$:|T] when ?is_space(hd(T)) ->
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
_ ->
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
end
end;
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end;
% Identifier
@@ -496,7 +483,7 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
handle_terminator(Rest, Line, Scope, Check, T);
{ identifier, Rest, Token } ->
tokenize(Rest, Line, Scope, [Token|Tokens]);
{ error, _ } = Error ->
{ error, _, _, _ } = Error ->
Error
end;
@@ -519,44 +506,48 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
tokenize(Rest, Line, Scope, Tokens);
tokenize(T, Line, _Scope, _Tokens) ->
{ error, { Line, "invalid token: ", until_eol(T) } }.
tokenize(T, Line, _Scope, Tokens) ->
{ error, { Line, "invalid token: ", until_eol(T) }, T, Tokens }.
until_eol("\r\n" ++ _) -> [];
until_eol("\n" ++ _) -> [];
until_eol([]) -> [];
until_eol([H|T]) -> [H|until_eol(T)].
escape_char(List) ->
<< Char/utf8 >> = elixir_interpolation:unescape_chars(list_to_binary(List)),
Char.
%% Handlers
handle_heredocs(T, Line, H, #scope{file=File} = Scope, Tokens) ->
case extract_heredoc_with_interpolation(Line, File, true, T, H) of
{ error, _ } = Error ->
Error;
handle_heredocs(T, Line, H, Scope, Tokens) ->
case extract_heredoc_with_interpolation(Line, Scope, true, T, H) of
{ error, Reason } ->
{ error, Reason, [H, H, H] ++ T, Tokens };
{ Parts, Rest } ->
Token = { string_type(H), Line, unescape_tokens(Parts) },
tokenize(Rest, Line, Scope, [Token|Tokens])
end.
handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
case elixir_interpolation:extract(Line, File, true, T, H) of
handle_strings(T, Line, H, Scope, Tokens) ->
case elixir_interpolation:extract(Line, Scope, true, T, H) of
{ error, Reason } ->
interpolation_error(Reason, [H|T], Tokens, " (for string starting at line ~B)", [Line]);
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
case Parts of
[Bin] when is_binary(Bin) ->
case unsafe_to_atom(unescape_chars(Bin), Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens])
{ ok, Atom } ->
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
{ error, Reason } ->
{ error, Reason, [H|T], Tokens }
end;
_ ->
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
{ error, { Line, "invalid interpolation in key ", [$"|T] }, [H|T], Tokens }
end;
{ NewLine, Parts, Rest } ->
Token = { string_type(H), Line, unescape_tokens(Parts) },
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
Error ->
interpolation_error(Error, " (for string starting at line ~B)", [Line])
tokenize(Rest, NewLine, Scope, [Token|Tokens])
end.
handle_nonl_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
@@ -581,18 +572,18 @@ eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
unsafe_to_atom(Part, Line, #scope{}) when
unsafe_to_atom(Part, Line, #elixir_tokenizer{}) when
is_binary(Part) andalso size(Part) > 255;
is_list(Part) andalso length(Part) > 255 ->
{ error, { Line, "atom length must be less than system limit", ":" } };
unsafe_to_atom(Binary, _Line, #scope{existing_atoms_only=true}) when is_binary(Binary) ->
binary_to_existing_atom(Binary, utf8);
unsafe_to_atom(Binary, _Line, #scope{}) when is_binary(Binary) ->
binary_to_atom(Binary, utf8);
unsafe_to_atom(List, _Line, #scope{existing_atoms_only=true}) when is_list(List) ->
list_to_existing_atom(List);
unsafe_to_atom(List, _Line, #scope{}) when is_list(List) ->
list_to_atom(List).
unsafe_to_atom(Binary, _Line, #elixir_tokenizer{existing_atoms_only=true}) when is_binary(Binary) ->
{ ok, binary_to_existing_atom(Binary, utf8) };
unsafe_to_atom(Binary, _Line, #elixir_tokenizer{}) when is_binary(Binary) ->
{ ok, binary_to_atom(Binary, utf8) };
unsafe_to_atom(List, _Line, #elixir_tokenizer{existing_atoms_only=true}) when is_list(List) ->
{ ok, list_to_existing_atom(List) };
unsafe_to_atom(List, _Line, #elixir_tokenizer{}) when is_list(List) ->
{ ok, list_to_atom(List) }.
collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
collect_modifiers(T, [H|Buffer]);
@@ -602,14 +593,16 @@ collect_modifiers(Rest, Buffer) ->
%% Heredocs
extract_heredoc_with_interpolation(Line, File, Interpol, T, H) ->
extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
case extract_heredoc(Line, T, H) of
{ error, _ } = Error ->
Error;
{ Body, Rest } ->
case elixir_interpolation:extract(Line + 1, File, Interpol, Body, 0) of
{ _, Parts, [] } -> { Parts, Rest };
Error -> interpolation_error(Error, " (for heredoc starting at line ~B)", [Line])
case elixir_interpolation:extract(Line + 1, Scope, Interpol, Body, 0) of
{ error, Reason } ->
{ error, interpolation_format(Reason, " (for heredoc starting at line ~B)", [Line]) };
{ _, Parts, [] } ->
{ Parts, Rest }
end
end.
@@ -658,6 +651,9 @@ remove_heredoc_spaces([], Buffer, _Spaces, _Original) ->
%% the remaining of the document and the number of spaces the heredoc
%% is aligned.
extract_heredoc_body(_Line, Marker, [{line,NewLine}|Rest], Buffer) ->
extract_heredoc_body(NewLine, Marker, Rest, Buffer);
extract_heredoc_body(Line, Marker, Rest, Buffer) ->
case extract_heredoc_line(Marker, Rest, Buffer, 0) of
{ ok, NewBuffer, NewRest } ->
@@ -784,8 +780,8 @@ tokenize_identifier(Rest, Acc) ->
%% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
tokenize_any_identifier(String, Line, Scope, Tokens) ->
{ Rest, Identifier } = tokenize_identifier(String, []),
tokenize_any_identifier(Original, Line, Scope, Tokens) ->
{ Rest, Identifier } = tokenize_identifier(Original, []),
{ AllIdentifier, AllRest } =
case Rest of
@@ -794,17 +790,19 @@ tokenize_any_identifier(String, Line, Scope, Tokens) ->
end,
case unsafe_to_atom(AllIdentifier, Line, Scope) of
{ error, _ } = Error ->
Error;
Atom ->
tokenize_kw_or_other(AllRest, identifier, Line, Atom, Tokens)
{ ok, Atom } ->
tokenize_kw_or_other(AllRest, identifier, Line, Atom, Tokens);
{ error, Reason } ->
{ error, Reason, Original, Tokens }
end.
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, _Tokens) when ?is_space(H) ->
{ identifier, [H|T], { kw_identifier, Line, Atom } };
tokenize_kw_or_other([$:,H|_], _Kind, Line, Atom, _Tokens) when ?is_atom_start(H) ->
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, Tokens) when ?is_atom_start(H) ->
Original = atom_to_list(Atom) ++ [$:],
Reason = { Line, "keyword argument must be followed by space after: ", Original },
{ error, Reason, Original ++ [H|T], Tokens };
tokenize_kw_or_other(Rest, Kind, Line, Atom, Tokens) ->
case check_keyword(Line, Atom, Tokens) of
@@ -813,7 +811,7 @@ tokenize_kw_or_other(Rest, Kind, Line, Atom, Tokens) ->
{ ok, [Check|T] } ->
{ keyword, Rest, Check, T };
{ error, Token } ->
{ error, { Line, "syntax error before: ", Token } }
{ error, { Line, "syntax error before: ", Token }, atom_to_list(Atom) ++ Rest, Tokens }
end.
%% Check if it is a call identifier (paren | bracket | do)
@@ -827,23 +825,30 @@ add_token_with_nl(Left, T) -> [Left|T].
%% Error handling
interpolation_error({ error, { Line, Message, Token } }, Extension, Args) ->
{ error, { Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token } }.
interpolation_error(Reason, Rest, Tokens, Extension, Args) ->
{ error, interpolation_format(Reason, Extension, Args), Rest, Tokens }.
interpolation_format({ string, Line, Message, Token }, Extension, Args) ->
{ Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token };
interpolation_format({ _, _, _ } = Reason, _Extension, _Args) ->
Reason.
%% Terminators
handle_terminator(Rest, Line, Scope, Token, Tokens) ->
case handle_terminator(Token, Scope) of
{ error, _ } = Error -> Error;
New -> tokenize(Rest, Line, New, [Token|Tokens])
{ error, Reason } ->
{ error, Reason, atom_to_list(element(1, Token)) ++ Rest, Tokens };
New ->
tokenize(Rest, Line, New, [Token|Tokens])
end.
handle_terminator(_, #scope{check_terminators=false} = Scope) ->
handle_terminator(_, #elixir_tokenizer{check_terminators=false} = Scope) ->
Scope;
handle_terminator(Token, #scope{terminators=Terminators} = Scope) ->
handle_terminator(Token, #elixir_tokenizer{terminators=Terminators} = Scope) ->
case check_terminator(Token, Terminators) of
{ error, _ } = Error -> Error;
New -> Scope#scope{terminators=New}
New -> Scope#elixir_tokenizer{terminators=New}
end.
check_terminator({ S, Line }, Terminators) when S == 'fn' ->
+19 -50
View File
@@ -1,14 +1,12 @@
%% Module responsible for tracking invocations of locals,
%% imports and requires.
%% Module responsible for tracking invocations of module calls.
-module(elixir_tracker).
-export([
setup/1, cleanup/1,
record_local/2, record_local/3,
record_import/4, record_remote/4,
record_warn/4, record_definition/3,
record_defaults/4, record_alias/2,
ensure_no_function_conflict/4, ensure_all_imports_used/3,
warn_unused_local/3, format_error/1
record_definition/3, record_defaults/4,
ensure_no_function_conflict/4, warn_unused_local/3,
format_error/1
]).
-include("elixir.hrl").
@@ -18,9 +16,9 @@
%% RECORD
setup(Module) ->
case code:ensure_loaded(?tracker) of
{ module, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
{ error, _ } -> ok
case code:is_loaded(?tracker) of
{ file, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
false -> ok
end.
cleanup(Module) ->
@@ -39,40 +37,21 @@ record_local(Tuple, Module, Function) ->
true
end).
record_warn(Ref, Warn, Line, Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_warnable(Pid, Ref, Warn, Line),
true
end).
record_import(_Tuple, Receiver, Module, _Function)
when Module == nil; Module == Receiver -> false;
record_import(Tuple, Receiver, Module, Function) ->
try
Pid = ets:lookup_element(Module, ?attr, 2),
?tracker:add_import(Pid, Function, Receiver, Tuple)
catch
error:badarg -> false
end.
if_tracker(Module, fun(Pid) ->
?tracker:add_import(Pid, Function, Receiver, Tuple),
true
end).
record_remote(_Tuple, Receiver, Module, _Function)
when Module == nil; Module == Receiver -> false;
record_remote(Tuple, Receiver, Module, Function) ->
try
Pid = ets:lookup_element(Module, ?attr, 2),
?tracker:add_remote(Pid, Function, Receiver, Tuple)
catch
error:badarg -> false
end.
record_alias(_Receiver, nil) -> false;
record_alias(Receiver, Module) ->
try
Pid = ets:lookup_element(Module, ?attr, 2),
?tracker:add_alias(Pid, Receiver)
catch
error:badarg -> false
end.
if_tracker(Module, fun(Pid) ->
?tracker:add_remote(Pid, Function, Receiver, Tuple),
true
end).
record_definition(Tuple, Kind, Module) ->
if_tracker(Module, fun(Pid) ->
@@ -91,9 +70,10 @@ record_defaults(Tuple, Kind, Module, Defaults) ->
%% HELPERS
if_tracker(Module, Callback) ->
case ets:lookup(Module, ?attr) of
[{ ?attr, Pid }] -> Callback(Pid);
_ -> false
try ets:lookup_element(Module, ?attr, 2) of
Pid -> Callback(Pid)
catch
error:badarg -> false
end.
%% ERROR HANDLING
@@ -106,14 +86,6 @@ ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
end),
ok.
ensure_all_imports_used(_Line, File, Module) ->
if_tracker(Module, fun(Pid) ->
[ begin
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
end || { M, L } <- ?tracker:collect_unused_imports(Pid)]
end),
ok.
warn_unused_local(File, Module, Private) ->
if_tracker(Module, fun(Pid) ->
Args = [ { Fun, Kind, Defaults } ||
@@ -131,9 +103,6 @@ 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]);
format_error({unused_import, Module}) ->
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
format_error({unused_args,{Name, Arity}}) ->
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
+108 -150
View File
@@ -2,8 +2,7 @@
%% overriden are defined in this file.
-module(elixir_translator).
-export([forms/4, 'forms!'/4]).
-export([translate/2, translate_each/2, translate_arg/2,
translate_args/2, translate_apply/7]).
-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,
@@ -12,17 +11,15 @@
-include("elixir.hrl").
forms(String, StartLine, File, Opts) ->
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
{ ok, Tokens } ->
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, { _, _, _ } } = Else -> Else
catch
{ interpolation_error, { _, _, _ } = Tuple} -> { error, Tuple }
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
end.
'forms!'(String, StartLine, File, Opts) ->
@@ -36,10 +33,6 @@ forms(String, StartLine, File, Opts) ->
translate(Forms, S) ->
lists:mapfoldl(fun translate_each/2, S, Forms).
%% Those macros are "low-level". They are the basic mechanism
%% that makes the language work and cannot be partially applied
%% nor overwritten.
%% Assignment operator
translate_each({ '=', Meta, [Left, Right] }, S) ->
@@ -50,7 +43,7 @@ translate_each({ '=', Meta, [Left, Right] }, S) ->
%% Containers
translate_each({ C, _, _ } = Original, S) when C == '[]'; C == '{}'; C == '<<>>' ->
translate_each({ C, _, _ } = Original, S) when C == '{}'; C == '<<>>' ->
elixir_literal:translate(Original, S);
%% Blocks and scope rewriters
@@ -89,7 +82,7 @@ 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], no_alias_opts(KV), SR),
{ TKV, ST } = translate_opts(Meta, alias, [as, warn], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
@@ -105,7 +98,7 @@ 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], no_alias_opts(KV), SR),
{ TKV, ST } = translate_opts(Meta, require, [as, warn], no_alias_opts(KV), SR),
case TRef of
{ atom, _, Old } ->
@@ -145,7 +138,7 @@ translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
{ elixir_utils:elixir_to_erl(Env), S };
{ elixir_scope:to_erl(Env), S };
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
@@ -153,9 +146,9 @@ translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
%% Aliases
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
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_tracker:record_alias(Receiver, S#elixir_scope.module),
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
{ { atom, ?line(Meta), Receiver }, S };
Aliases ->
{ TAliases, SA } = translate_args(Aliases, S),
@@ -164,7 +157,7 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
true ->
Atoms = [Atom || { atom, _, Atom } <- TAliases],
Receiver = elixir_aliases:concat(Atoms),
elixir_tracker:record_alias(Receiver, S#elixir_scope.module),
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)],
@@ -287,7 +280,7 @@ translate_each({ '&', Meta, [Arg] }, S) ->
assert_no_match_or_guard_scope(Meta, '&', S),
elixir_fn:capture(Meta, Arg, S);
translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
translate_each({ fn, Meta, [{ '->', _, Pairs }] }, S) ->
assert_no_match_or_guard_scope(Meta, 'fn', S),
elixir_fn:fn(Meta, Pairs, S);
@@ -296,30 +289,26 @@ translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
translate_comprehension(Meta, Kind, Args, S);
%% Super (exceptionally supports partial application)
%% Super
translate_each({ super, Meta, Args } = Original, S) when is_list(Args) ->
case elixir_partials:handle(Original, S) of
error ->
assert_no_match_or_guard_scope(Meta, super, S),
Module = assert_module_scope(Meta, super, S),
Function = assert_function_scope(Meta, super, S),
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
translate_each({ super, Meta, Args }, S) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, super, S),
Module = assert_module_scope(Meta, super, S),
Function = assert_function_scope(Meta, super, S),
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
{ _, Arity } = Function,
{ _, Arity } = Function,
{ TArgs, TS } = if
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")
end,
{ TArgs, TS } = if
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")
end,
Super = elixir_def_overridable:name(Module, Function),
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
Else -> Else
end;
Super = elixir_def_overridable:name(Module, Function),
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
translate_each({ 'super?', Meta, [] }, S) ->
Module = assert_module_scope(Meta, 'super?', S),
@@ -329,9 +318,9 @@ translate_each({ 'super?', Meta, [] }, S) ->
%% Variables
translate_each({ '^', Meta, [ { Name, _, Kind } = Var ] },
translate_each({ '^', 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, Kind }, S#elixir_scope.backup_vars) of
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
{ ok, Value } ->
Line = ?line(Meta),
{ TVar, TS } = translate_each(Var, S),
@@ -341,8 +330,8 @@ translate_each({ '^', Meta, [ { Name, _, Kind } = Var ] },
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
end;
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
case orddict:find({ Name, Kind }, S#elixir_scope.backup_vars) of
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 } ->
{ { var, ?line(Meta), Value }, S };
error ->
@@ -358,7 +347,7 @@ translate_each({ '^', Meta, [ Expr ] }, S) ->
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S, fun() ->
translate_each({ Name, Meta, [] }, S)
end);
@@ -367,101 +356,99 @@ translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
translate_each({ '->', Meta, _Args }, S) ->
syntax_error(Meta, S#elixir_scope.file, "unhandled operator ->");
translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
translate_each({ Atom, Meta, Args }, S) when is_atom(Atom), is_list(Meta), is_list(Args) ->
assert_no_ambiguous_op(Atom, Meta, Args, S),
case elixir_partials:is_sequential(Args) andalso
elixir_dispatch:import_function(Meta, Atom, length(Args), S) of
false ->
case elixir_partials:handle(Original, S) of
error ->
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,
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);
Else -> Else
end;
Else -> Else
end;
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
%% Remote calls
translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_atom(Right) ->
translate_each({ { '.', _, [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)),
Fun = (element(1, TLeft) == atom) andalso elixir_partials:is_sequential(Args) andalso
elixir_dispatch:require_function(Meta, element(3, TLeft), Right, length(Args), 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,
case Fun of
false ->
case elixir_partials:handle(Original, S) of
error ->
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
Callback = fun() -> translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) 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 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
end;
Else -> Else
end;
Else -> Else
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 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
end;
%% Anonymous function calls
translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
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]);
_ ->
case elixir_partials:handle(Original, S) of
error ->
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
{ {call, ?line(Meta), TExpr, TArgs}, umergev(SE, SA) };
Else -> Else
end
{ 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) ->
syntax_error(Meta, S#elixir_scope.file, "unexpected parenthesis after ~ts",
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}])]);
%% Literals
translate_each(Literal, S) ->
@@ -469,6 +456,12 @@ translate_each(Literal, S) ->
%% Helpers
var_kind(Meta, Kind) ->
case lists:keyfind(counter, 1, Meta) of
{ counter, Counter } -> Counter;
false -> Kind
end.
%% Opts
translate_opts(Meta, Kind, Allowed, Opts, S) ->
@@ -542,7 +535,7 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
end,
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, TKV, S) }.
no_alias_opts(KV) when is_list(KV) ->
case lists:keyfind(as, 1, KV) of
@@ -603,41 +596,6 @@ translate_args(Args, S) ->
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
{ TArgs, umergea(SV, SC) }.
%% Translate apply
%% Used by both apply and external function invocation macros.
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) ->
Line = ?line(Meta),
Optimize = case (Args == []) orelse lists:last(Args) of
{ '|', _, _ } -> false;
_ ->
case TRight of
{ atom, _, _ } -> true;
_ -> false
end
end,
case Optimize of
true ->
%% Register the remote
case TLeft of
{ atom, _, Receiver } ->
Tuple = { element(3, TRight), length(Args) },
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function);
_ ->
ok
end,
{ TArgs, SA } = translate_args(Args, umergec(S, SR)),
FS = umergev(SL, umergev(SR,SA)),
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, FS };
false ->
{ TArgs, SA } = translate_each(Args, umergec(S, SR)),
FS = umergev(SL, umergev(SR,SA)),
{ ?wrap_call(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
end.
%% __op__ helpers
assert_no_match_scope_for_bin_op(Meta, Op, S)
+10 -3
View File
@@ -150,7 +150,8 @@ rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
H == 'Elixir.FunctionClauseError'; H == 'Elixir.SystemLimitError' ->
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
H == 'Elixir.SystemLimitError' ->
Expr = { 'or', Meta, [
erlang_rescue_guard_for(Meta, Var, H),
exception_compare(Meta, Var, H)
@@ -176,8 +177,8 @@ rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
erlang_rescues() ->
[
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError', 'Elixir.BadArityError',
'Elixir.BadFunctionError', 'Elixir.MatchError', 'Elixir.CaseClauseError', 'Elixir.FunctionClauseError',
'Elixir.SystemLimitError', 'Elixir.ErlangError'
'Elixir.BadFunctionError', 'Elixir.MatchError', 'Elixir.CaseClauseError', 'Elixir.TryClauseError',
'Elixir.FunctionClauseError', 'Elixir.SystemLimitError', 'Elixir.ErlangError'
].
erlang_rescue_guard_for(Meta, Var, List) when is_list(List) ->
@@ -219,6 +220,12 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
exception_compare(Meta, Var, case_clause)
] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
{ 'and', Meta, [
{ is_tuple, Meta, [Var] },
exception_compare(Meta, Var, try_clause)
] };
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
{ 'or', Meta, [
{ '==', Meta, [Var, badarg] },
+3 -3
View File
@@ -1,8 +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/3,
get_line/1, split_last/1,
-export([elixir_to_erl/1, elixir_to_erl/2,
get_line/1, split_last/1, elixir_to_erl/3,
characters_to_list/1, characters_to_binary/1,
cons_to_list/1, list_to_cons/2, list_to_cons/3,
convert_to_boolean/5, returns_boolean/1,
@@ -10,7 +10,7 @@
-include("elixir.hrl").
-include_lib("kernel/include/file.hrl").
get_line(Opts) ->
get_line(Opts) when is_list(Opts) ->
case lists:keyfind(line, 1, Opts) of
{ line, Line } when is_integer(Line) -> Line;
false -> 0
+6 -7
View File
@@ -5,17 +5,14 @@ defmodule KernelTest do
doctest Access.List
doctest String.Chars.List
doctest Inspect.Atom
doctest Inspect.BitString
doctest Inspect.List
doctest Inspect.Number
doctest Inspect.Regex
doctest Inspect.Tuple
doctest Bitwise
doctest Code
doctest Enum
doctest Exception
doctest Float
doctest IO.ANSI
doctest Inspect.Algebra
doctest Integer
doctest Keyword
doctest Kernel
doctest Kernel.SpecialForms
@@ -24,9 +21,11 @@ defmodule KernelTest do
doctest Module
doctest OptionParser
doctest Path
doctest Protocol.Consolidation
doctest Regex
doctest Set
doctest Stream
doctest String
doctest Inspect.Algebra
doctest Tuple
doctest Version
end
+29 -9
View File
@@ -5,6 +5,11 @@ defmodule CodeTest do
import PathHelpers
def one, do: 1
def genmodule(name) do
defmodule name do
Kernel.LexicalTracker.remotes(__MODULE__)
end
end
contents = quote do
defmodule CodeTest.Sample do
@@ -19,6 +24,10 @@ defmodule CodeTest do
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
end
test :eval_string_with_other_context do
assert Code.eval_string("var!(a, Sample) = 1") == { 1, [{{:a,Sample},1}] }
end
test :eval_with_unnamed_scopes do
assert { RuntimeError[], [a: RuntimeError[]] } =
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
@@ -60,15 +69,6 @@ defmodule CodeTest do
assert Code.require_file(fixture_path("code_sample.exs")) != nil
end
test :path_manipulation do
path = __DIR__
Code.prepend_path path
assert to_char_list(path) in :code.get_path
Code.delete_path path
refute to_char_list(path) in :code.get_path
end
test :file do
assert :filename.absname(__FILE__) == __FILE__
end
@@ -104,6 +104,13 @@ defmodule CodeTest do
assert Keyword.get(compile, :source) != nil
end
test :compile_string_works_accross_lexical_scopes do
assert [{ CompileCrossSample, _ }] = Code.compile_string("CodeTest.genmodule CompileCrossSample")
after
:code.purge CompileCrossSample
:code.delete CompileCrossSample
end
test :compile_string do
assert [{ CompileStringSample, _ }] = Code.compile_string("defmodule CompileStringSample, do: :ok")
after
@@ -128,3 +135,16 @@ defmodule CodeTest do
refute Code.ensure_compiled?(Unknown.Module)
end
end
defmodule Code.SyncTest do
use ExUnit.Case
test :path_manipulation do
path = Path.join(__DIR__, "fixtures")
Code.prepend_path path
assert to_char_list(path) in :code.get_path
Code.delete_path path
refute to_char_list(path) in :code.get_path
end
end
+132 -24
View File
@@ -3,8 +3,8 @@ Code.require_file "test_helper.exs", __DIR__
defmodule DictTest.Common do
defmacro __using__(module) do
quote location: :keep do
# Most underlying Dict implementations have no key order guarantees,
# sort them before we compare:
# Most underlying Dict implementations have no key
# order guarantees, sort them before we compare:
defmacrop dicts_equal(actual, expected) do
quote do
cmp = fn { k1, _ }, { k2, _ } -> k1 < k2 end
@@ -12,12 +12,31 @@ defmodule DictTest.Common do
end
end
defp empty_dict, do: unquote(module).new
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
unquote(module).new list
end
defp new_dict(list, transform) do
unquote(module).new list, transform
end
defp int_dict do
unquote(module).new [{1,1}]
end
test :access do
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
assert dict["first_key"] == 1
assert dict["third_key"] == nil
end
test :access_with_match do
assert int_dict[1] == 1
assert int_dict[1.0] == nil
end
test :new_pairs do
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
assert 2 == Dict.size dict
@@ -41,12 +60,22 @@ defmodule DictTest.Common do
assert "default" == Dict.get(empty_dict, "first_key", "default")
end
test :get_with_match do
assert Dict.get(int_dict, 1) == 1
assert Dict.get(int_dict, 1.0) == nil
end
test :fetch do
assert { :ok, 1 } == Dict.fetch(new_dict, "first_key")
assert { :ok, 2 } == Dict.fetch(new_dict, "second_key")
assert :error == Dict.fetch(new_dict, "other_key")
end
test :fetch_with_match do
assert Dict.fetch(int_dict, 1) == { :ok, 1 }
assert Dict.fetch(int_dict, 1.0) == :error
end
test :fetch! do
assert 1 == Dict.fetch!(new_dict, "first_key")
assert 2 == Dict.fetch!(new_dict, "second_key")
@@ -55,36 +84,62 @@ defmodule DictTest.Common do
end
end
test :fetch_with_match! do
assert Dict.fetch!(int_dict, 1) == 1
assert_raise KeyError, fn ->
assert Dict.fetch!(int_dict, 1.0)
end
end
test :put do
dict = Dict.put(new_dict, "first_key", {1})
assert {1} == Dict.get dict, "first_key"
assert 2 == Dict.get dict, "second_key"
end
test :put_with_match do
assert Dict.get(Dict.put(int_dict, 1, :other), 1) == :other
assert Dict.get(Dict.put(int_dict, 1.0, :other), 1) == 1
assert Dict.get(Dict.put(int_dict, 1, :other), 1.0) == nil
assert Dict.get(Dict.put(int_dict, 1.0, :other), 1.0) == :other
end
test :put_new do
dict = Dict.put_new(new_dict, "first_key", {1})
assert 1 == Dict.get dict, "first_key"
end
test :put_new_with_match do
assert Dict.get(Dict.put_new(int_dict, 1, :other), 1) == 1
assert Dict.get(Dict.put_new(int_dict, 1.0, :other), 1) == 1
assert Dict.get(Dict.put_new(int_dict, 1, :other), 1.0) == nil
assert Dict.get(Dict.put_new(int_dict, 1.0, :other), 1.0) == :other
end
test :keys do
assert ["first_key", "second_key"] == Enum.sort Dict.keys new_dict
assert [] == Dict.keys empty_dict
assert Enum.sort(Dict.keys new_dict) == ["first_key", "second_key"]
assert Dict.keys(empty_dict) == []
end
test :values do
assert [1, 2] ==Enum.sort Dict.values(new_dict)
assert [] == Dict.values empty_dict
assert Enum.sort(Dict.values(new_dict)) == [1, 2]
assert Dict.values(empty_dict) == []
end
test :delete do
mdict = Dict.delete new_dict, "second_key"
assert 1 == Dict.size mdict
assert Dict.size(mdict) == 1
assert Dict.has_key? mdict, "first_key"
refute Dict.has_key? mdict, "second_key"
mdict = Dict.delete(new_dict, "other_key")
assert mdict == new_dict
assert 0 == Dict.size Dict.delete(empty_dict, "other_key")
assert Dict.size(Dict.delete(empty_dict, "other_key")) == 0
end
test :delete_with_match do
assert Dict.get(Dict.delete(int_dict, 1), 1) == nil
assert Dict.get(Dict.delete(int_dict, 1.0), 1) == 1
end
test :merge do
@@ -123,17 +178,28 @@ defmodule DictTest.Common do
refute Dict.has_key?(dict, "b")
end
test :has_key_with_match do
assert Dict.has_key?(int_dict, 1)
refute Dict.has_key?(int_dict, 1.0)
end
test :size do
assert 2 == Dict.size new_dict
assert 0 == Dict.size empty_dict
end
test :update do
dict = Dict.update! new_dict, "first_key", fn val -> -val end
assert -1 == Dict.get dict, "first_key"
dict = Dict.update!(new_dict, "first_key", fn val -> -val end)
assert Dict.get(dict, "first_key") == -1
dict = Dict.update dict, "non-existent", "...", fn val -> -val end
assert "..." == Dict.get dict, "non-existent"
dict = Dict.update(dict, "non-existent", "...", fn val -> -val end)
assert Dict.get(dict, "non-existent") == "..."
end
test :update_with_match do
assert Dict.get(Dict.update!(int_dict, 1, &(&1 + 1)), 1) == 2
assert Dict.get(Dict.update(int_dict, 1.0, 2, &(&1 + 1)), 1) == 1
assert Dict.get(Dict.update(int_dict, 1.0, 2, &(&1 + 1)), 1.0) == 2
end
test :pop do
@@ -158,6 +224,16 @@ defmodule DictTest.Common do
assert dicts_equal actual, new_dict
end
test :pop_with_match do
{v, actual} = Dict.pop(int_dict, 1)
assert v == 1
assert dicts_equal actual, []
{v, actual} = Dict.pop(int_dict, 1.0)
assert v == nil
assert dicts_equal actual, int_dict
end
test :split do
split_keys = []
{take, drop} = Dict.split(new_dict, split_keys)
@@ -185,6 +261,16 @@ defmodule DictTest.Common do
assert dicts_equal drop, drop_expected
end
test :split_with_match do
{ take, drop } = Dict.split(int_dict, [1])
assert dicts_equal take, int_dict
assert dicts_equal drop, []
{ take, drop } = Dict.split(int_dict, [1.0])
assert dicts_equal take, []
assert dicts_equal drop, int_dict
end
test :take do
result = Dict.take(new_dict, ["unknown_key"])
assert dicts_equal result, Dict.empty(new_dict)
@@ -195,6 +281,11 @@ defmodule DictTest.Common do
assert dicts_equal result, expected
end
test :take_with_match do
assert dicts_equal Dict.take(int_dict, [1]), int_dict
assert dicts_equal Dict.take(int_dict, [1.0]), []
end
test :drop do
result = Dict.drop(new_dict, ["unknown_key"])
assert dicts_equal result, new_dict
@@ -203,17 +294,44 @@ defmodule DictTest.Common do
assert dicts_equal result, Dict.delete(new_dict, "first_key")
end
test :drop_with_match do
assert dicts_equal Dict.drop(int_dict, [1]), []
assert dicts_equal Dict.drop(int_dict, [1.0]), int_dict
end
test :empty do
assert empty_dict == Dict.empty new_dict
end
test :equal? do
dict1 = HashDict.new(a: 2, b: 3, f: 5, c: 123)
dict2 = ListDict.new(a: 2, b: 3, f: 5, c: 123)
dict1 = new_dict(a: 2, b: 3, f: 5, c: 123)
dict2 = new_dict(a: 2, b: 3, f: 5, c: 123)
assert dict_impl.equal?(dict1, dict2)
assert Dict.equal?(dict1, dict2)
dict2 = Dict.put(dict2, :a, 3)
refute dict_impl.equal?(dict1, dict2)
refute Dict.equal?(dict1, dict2)
dict3 = [a: 2, b: 3, f: 5, c: 123, z: 666]
refute Dict.equal?(dict1, dict3)
refute Dict.equal?(dict3, dict1)
end
test :equal_with_match? do
dict1 = new_dict([{1,1}])
dict2 = new_dict([{1.0,1}])
assert dict_impl.equal?(dict1, dict1)
refute dict_impl.equal?(dict1, dict2)
assert Dict.equal?(dict1, dict1)
refute Dict.equal?(dict1, dict2)
end
test :enum_member do
assert Enum.member?(new_dict([{1,1}]), { 1, 1 })
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1 })
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
@@ -221,16 +339,6 @@ defmodule DictTest.Common do
Dict.to_list :bad_dict
end
end
defp empty_dict, do: unquote(module).new
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
unquote(module).new list
end
defp new_dict(list, transform) do
unquote(module).new list, transform
end
end
end
end
+74 -23
View File
@@ -152,14 +152,14 @@ defmodule EnumTest.List do
end
test :filter_with_match do
assert Enum.filter([1, 2, 3], match?(1, &1)) == [1]
assert Enum.filter([1, 2, 3], match?(x when x < 3, &1)) == [1, 2]
assert Enum.filter([1, 2, 3], match?(_, &1)) == [1, 2, 3]
assert Enum.filter([1, 2, 3], &match?(1, &1)) == [1]
assert Enum.filter([1, 2, 3], &match?(x when x < 3, &1)) == [1, 2]
assert Enum.filter([1, 2, 3], &match?(_, &1)) == [1, 2, 3]
end
test :filter_map do
assert Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4]
assert Enum.filter_map([2, 4, 6], fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4, 8, 12]
assert Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]
assert Enum.filter_map([2, 4, 6], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
end
test :flat_map do
@@ -185,15 +185,15 @@ defmodule EnumTest.List do
end
test :map_join do
assert Enum.map_join([], " = ", &1 * 2) == ""
assert Enum.map_join([1, 2, 3], " = ", &1 * 2) == "2 = 4 = 6"
assert Enum.map_join([1, 2, 3], &1 * 2) == "246"
assert Enum.map_join([], " = ", &(&1 * 2)) == ""
assert Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) == "2 = 4 = 6"
assert Enum.map_join([1, 2, 3], &(&1 * 2)) == "246"
end
test :join_empty do
fun = fn (acc, _) -> acc end
assert Enum.join(fun, ".") == ""
assert Enum.map_join(fun, ".", &1 + 0) == ""
assert Enum.map_join(fun, ".", &(&1 + 0)) == ""
end
test :map do
@@ -229,7 +229,7 @@ defmodule EnumTest.List do
test :sort do
assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
assert Enum.sort([5, 3, 2, 4, 1], &1 > &2) == [5, 4, 3, 2, 1]
assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1]
end
test :split do
@@ -264,6 +264,13 @@ defmodule EnumTest.List do
assert Enum.take([], 3) == []
end
test :take_every do
assert Enum.take_every([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2) == [1, 3, 5, 7, 9]
assert Enum.take_every([], 2) == []
assert Enum.take_every([1, 2], 2) == [1]
assert Enum.take_every([1, 2, 3], 0) == []
end
test :take_while do
assert Enum.take_while([1, 2, 3], fn(x) -> x > 3 end) == []
assert Enum.take_while([1, 2, 3], fn(x) -> x <= 1 end) == [1]
@@ -358,6 +365,25 @@ defmodule EnumTest.List do
assert Enum.slice([1,2,3,4,5], -2, 5) == [4, 5]
assert Enum.slice([1,2,3,4,5], -3, 1) == [3]
end
test :slice_range do
assert Enum.slice([1,2,3,4,5], 0..0) == [1]
assert Enum.slice([1,2,3,4,5], 0..1) == [1, 2]
assert Enum.slice([1,2,3,4,5], 0..2) == [1, 2, 3]
assert Enum.slice([1,2,3,4,5], 1..2) == [2, 3]
assert Enum.slice([1,2,3,4,5], 1..0) == nil
assert Enum.slice([1,2,3,4,5], 2..5) == [3, 4, 5]
assert Enum.slice([1,2,3,4,5], 4..4) == [5]
assert Enum.slice([1,2,3,4,5], 5..5) == []
assert Enum.slice([1,2,3,4,5], 6..5) == nil
assert Enum.slice([1,2,3,4,5], 6..0) == nil
assert Enum.slice([1,2,3,4,5], -6..0) == nil
assert Enum.slice([1,2,3,4,5], -6..5) == nil
assert Enum.slice([1,2,3,4,5], -5..-1) == [1, 2, 3, 4, 5]
assert Enum.slice([1,2,3,4,5], -5..-3) == [1, 2, 3]
assert Enum.slice([1,2,3,4,5], -6..-1) == nil
assert Enum.slice([1,2,3,4,5], -6..-3) == nil
end
end
defmodule EnumTest.Range do
@@ -377,10 +403,10 @@ defmodule EnumTest.Range do
test :any? do
range = Range.new(first: 0, last: 5)
refute Enum.any?(range, &1 > 10)
refute Enum.any?(range, &(&1 > 10))
range = Range.new(first: 0, last: 5)
assert Enum.any?(range, &1 > 3)
assert Enum.any?(range, &(&1 > 3))
range = Range.new(first: 1, last: 0)
assert Enum.any?(range)
@@ -510,17 +536,17 @@ defmodule EnumTest.Range do
test :filter_with_match do
range = Range.new(first: 1, last: 3)
assert Enum.filter(range, match?(1, &1)) == [1]
assert Enum.filter(range, match?(x when x < 3, &1)) == [1, 2]
assert Enum.filter(range, match?(_, &1)) == [1, 2, 3]
assert Enum.filter(range, &match?(1, &1)) == [1]
assert Enum.filter(range, &match?(x when x < 3, &1)) == [1, 2]
assert Enum.filter(range, &match?(_, &1)) == [1, 2, 3]
end
test :filter_map do
range = Range.new(first: 1, last: 3)
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4]
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]
range = Range.new(first: 2, last: 6)
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4, 8, 12]
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
end
test :flat_map do
@@ -558,11 +584,11 @@ defmodule EnumTest.Range do
test :map_join do
range = Range.new(first: 1, last: 0)
assert Enum.map_join(range, " = ", &1 * 2) == "2 = 0"
assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 0"
range = Range.new(first: 1, last: 3)
assert Enum.map_join(range, " = ", &1 * 2) == "2 = 4 = 6"
assert Enum.map_join(range, &1 * 2) == "246"
assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 4 = 6"
assert Enum.map_join(range, &(&1 * 2)) == "246"
end
test :map do
@@ -597,9 +623,9 @@ defmodule EnumTest.Range do
assert Enum.sort(Range.new(first: 2, last: 1)) == [1, 2]
assert Enum.sort(Range.new(first: 1, last: 1)) == [1]
assert Enum.sort(Range.new(first: 3, last: 1), &1 > &2) == [3, 2, 1]
assert Enum.sort(Range.new(first: 2, last: 1), &1 > &2) == [2, 1]
assert Enum.sort(Range.new(first: 1, last: 1), &1 > &2) == [1]
assert Enum.sort(Range.new(first: 3, last: 1), &(&1 > &2)) == [3, 2, 1]
assert Enum.sort(Range.new(first: 2, last: 1), &(&1 > &2)) == [2, 1]
assert Enum.sort(Range.new(first: 1, last: 1), &(&1 > &2)) == [1]
end
test :split do
@@ -642,6 +668,12 @@ defmodule EnumTest.Range do
assert Enum.take(range, 3) == [1, 0]
end
test :take_every do
assert Enum.take_every(1..10, 2) == [1, 3, 5, 7, 9]
assert Enum.take_every(1..2, 2) == [1]
assert Enum.take_every(1..3, 0) == []
end
test :take_while do
range = Range.new(first: 1, last: 3)
assert Enum.take_while(range, fn(x) -> x > 3 end) == []
@@ -724,6 +756,25 @@ defmodule EnumTest.Range do
assert Enum.slice(1..5, -2, 5) == [4, 5]
assert Enum.slice(1..5, -3, 1) == [3]
end
test :slice_range do
assert Enum.slice(1..5, 0..0) == [1]
assert Enum.slice(1..5, 0..1) == [1, 2]
assert Enum.slice(1..5, 0..2) == [1, 2, 3]
assert Enum.slice(1..5, 1..2) == [2, 3]
assert Enum.slice(1..5, 1..0) == nil
assert Enum.slice(1..5, 2..5) == [3, 4, 5]
assert Enum.slice(1..5, 4..4) == [5]
assert Enum.slice(1..5, 5..5) == []
assert Enum.slice(1..5, 6..5) == nil
assert Enum.slice(1..5, 6..0) == nil
assert Enum.slice(1..5, -6..0) == nil
assert Enum.slice(1..5, -6..5) == nil
assert Enum.slice(1..5, -5..-1) == [1, 2, 3, 4, 5]
assert Enum.slice(1..5, -5..-3) == [1, 2, 3]
assert Enum.slice(1..5, -6..-1) == nil
assert Enum.slice(1..5, -6..-3) == nil
end
end
defmodule EnumTest.Others do
+25
View File
@@ -3,6 +3,9 @@ Code.require_file "test_helper.exs", __DIR__
defmodule Kernel.ExceptionTest do
use ExUnit.Case, async: true
# Ensure fields passed through an expression are valid
defexception Custom, %w(message)a
test :is_exception do
assert is_exception(RuntimeError.new)
refute is_exception(empty_tuple)
@@ -51,6 +54,7 @@ defmodule Kernel.ExceptionTest 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()"
assert Exception.format_mfa(nil, :bar, []) == "nil.bar()"
assert Exception.format_mfa(:foo, :bar, [1, 2]) == ":foo.bar(1, 2)"
assert Exception.format_mfa(Foo, :"bar baz", 1) == "Foo.\"bar baz\"/1"
end
@@ -73,6 +77,7 @@ defmodule Kernel.ExceptionTest 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
@@ -84,6 +89,26 @@ defmodule Kernel.ExceptionTest do
assert ErlangError.new(original: :sample).message == "erlang error: :sample"
end
test :raise_preserves_the_stacktrace do
stacktrace =
try do
raise "a"
rescue _ ->
[top|_] = System.stacktrace
top
end
file = to_char_list(__FILE__)
assert {Kernel.ExceptionTest, :test_raise_preserves_the_stacktrace, _,
[file: ^file, line: 95]} = stacktrace # line is sensitive
end
test :defexception do
defexception SampleError, field: :ok do
# Check do block is properly inline
def message(_), do: "hello"
end
end
defp empty_tuple, do: {}
defp a_tuple, do: { :foo, :bar, :baz }
defp a_list, do: [ :foo, :bar, :baz ]
+2 -2
View File
@@ -550,7 +550,7 @@ defmodule FileTest do
test :open_a_file_with_function do
file = fixture_path("file.txt")
assert File.open(file, IO.read(&1, :line)) == { :ok, "FOO\n" }
assert File.open(file, &IO.read(&1, :line)) == { :ok, "FOO\n" }
end
test :open_a_missing_file! do
@@ -562,7 +562,7 @@ defmodule FileTest do
test :open_a_file_with_function! do
file = fixture_path("file.txt")
assert File.open!(file, IO.read(&1, :line)) == "FOO\n"
assert File.open!(file, &IO.read(&1, :line)) == "FOO\n"
end
end
+28
View File
@@ -0,0 +1,28 @@
Code.require_file "test_helper.exs", __DIR__
defmodule FloatTest do
use ExUnit.Case, async: true
test :parse do
assert Float.parse("12") === {12.0, ""}
assert Float.parse("-12") === {-12.0, ""}
assert Float.parse("123456789") === {123456789.0, ""}
assert Float.parse("12.5") === {12.5, ""}
assert Float.parse("12.524235") === {12.524235, ""}
assert Float.parse("-12.5") === {-12.5, ""}
assert Float.parse("-12.524235") === {-12.524235, ""}
assert Float.parse("7.5e3") === {7.5e3, ""}
assert Float.parse("7.5e-3") === {7.5e-3, ""}
assert Float.parse("12x") === {12.0, "x"}
assert Float.parse("12.5x") === {12.5, "x"}
assert Float.parse("-12.32453e10") === {-1.232453e11, ""}
assert Float.parse("-12.32453e-10") === {-1.232453e-9, ""}
assert Float.parse("0.32453e-10") === {3.2453e-11, ""}
assert Float.parse("1.32453e-10") === {1.32453e-10, ""}
assert Float.parse("1.32.45") === {1.32, ".45"}
assert Float.parse("1.o") === {1.0, ".o"}
assert Float.parse("--1.2") === :error
assert Float.parse("++1.2") === :error
assert Float.parse("pi") === :error
end
end
+8 -8
View File
@@ -85,31 +85,31 @@ defmodule HashDictTest do
test :update do
dict = filled_dict(8)
dict = HashDict.update!(dict, 1, &1 * 2)
dict = HashDict.update!(dict, 1, &(&1 * 2))
assert HashDict.get(dict, 1) == 2
assert HashDict.size(dict) == 8
assert_raise KeyError, fn ->
HashDict.update!(dict, 11, &1 * 2)
HashDict.update!(dict, 11, &(&1 * 2))
end
dict = HashDict.update(dict, 3, 3, &1 * 2)
dict = HashDict.update(dict, 3, 3, &(&1 * 2))
assert HashDict.get(dict, 3) == 6
assert HashDict.size(dict) == 8
dict = HashDict.update(dict, 11, 13, &1 * 2)
dict = HashDict.update(dict, 11, 13, &(&1 * 2))
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 9
assert_raise KeyError, fn->
HashDict.update!(dict, 11.0, &1 * 2)
HashDict.update!(dict, 11.0, &(&1 * 2))
end
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
dict = HashDict.update(dict, 11.0, 15, &(&1 * 2))
assert HashDict.get(dict, 11.0) == 15
assert HashDict.size(dict) == 10
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
dict = HashDict.update(dict, 11.0, 15, &(&1 * 2))
assert HashDict.get(dict, 11.0) == 30
assert HashDict.get(dict, 11) == 13
assert HashDict.size(dict) == 10
@@ -302,6 +302,6 @@ defmodule HashDictTest do
end
defp filled_dict(range) do
Enum.reduce 1..range, HashDict.new, HashDict.put(&2, &1, &1)
Enum.reduce 1..range, HashDict.new, &HashDict.put(&2, &1, &1)
end
end
+3 -5
View File
@@ -40,10 +40,8 @@ defmodule HashSetTest do
diff = HashSet.difference(filled_set(9000), filled_set(9000))
assert HashSet.equal?(diff, HashSet.new([]))
#Ensure the trie has correctly resized
assert HashSet.inspect_depth(diff) == 0
assert HashSet.inspect_contract(diff) == 2
assert HashSet.inspect_expand(diff) == 80
# Ensure the trie has correctly resized
assert { HashSet, 0, 0, 80, 2, _ } = diff
end
test "member? with ordered sets" do
@@ -110,7 +108,7 @@ defmodule HashSetTest do
end
defp filled_set(range) do
Enum.reduce 1..range, HashSet.new, HashSet.put(&2, &1)
Enum.reduce 1..range, HashSet.new, &HashSet.put(&2, &1)
end
end
-2
View File
@@ -65,8 +65,6 @@ defmodule Inspect.BitStringTest do
assert inspect("f\no") == "\"f\\no\""
assert inspect("f\\o") == "\"f\\\\o\""
assert inspect("f\ao") == "\"f\\ao\""
assert inspect(%S{#{#a}) == "\"\\\#{#a\""
assert inspect(%S{ #{#a}) == "\" \\\#{#a\""
end
test :utf8 do
+13
View File
@@ -20,4 +20,17 @@ defmodule IntegerTest do
assert Integer.even?(-2) == true
assert Integer.even?(-3) == false
end
test :parse do
assert Integer.parse("12") === {12, ""}
assert Integer.parse("-12") === {-12, ""}
assert Integer.parse("123456789") === {123456789, ""}
assert Integer.parse("12.5") === {12, ".5"}
assert Integer.parse("7.5e-3") === {7, ".5e-3"}
assert Integer.parse("12x") === {12, "x"}
assert Integer.parse("++1") === :error
assert Integer.parse("--1") === :error
assert Integer.parse("+-1") === :error
assert Integer.parse("three") === :error
end
end
+29
View File
@@ -2,6 +2,7 @@ Code.require_file "test_helper.exs", __DIR__
defmodule IOTest do
use ExUnit.Case, async: true
import ExUnit.CaptureIO
test :read_with_count do
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
@@ -77,4 +78,32 @@ defmodule IOTest do
assert "日\n" == IO.binread(file, :line)
assert File.close(file) == :ok
end
test :puts_with_chardata do
assert capture_io(fn -> IO.puts("hello") end) == "hello\n"
assert capture_io(fn -> IO.puts('hello') end) == "hello\n"
assert capture_io(fn -> IO.puts(:hello) end) == "hello\n"
assert capture_io(fn -> IO.puts(13) end) == "13\n"
end
test :write_with_chardata do
assert capture_io(fn -> IO.write("hello") end) == "hello"
assert capture_io(fn -> IO.write('hello') end) == "hello"
assert capture_io(fn -> IO.write(:hello) end) == "hello"
assert capture_io(fn -> IO.write(13) end) == "13"
end
test :gets_with_chardata do
assert capture_io("foo\n", fn -> IO.gets("hello") end) == "hello"
assert capture_io("foo\n", fn -> IO.gets('hello') end) == "hello"
assert capture_io("foo\n", fn -> IO.gets(:hello) end) == "hello"
assert capture_io("foo\n", fn -> IO.gets(13) end) == "13"
end
test :getn_with_chardata do
assert capture_io("foo\n", fn -> IO.getn("hello", 3) end) == "hello"
assert capture_io("foo\n", fn -> IO.getn('hello', 3) end) == "hello"
assert capture_io("foo\n", fn -> IO.getn(:hello, 3) end) == "hello"
assert capture_io("foo\n", fn -> IO.getn(13, 3) end) == "13"
end
end
+2 -2
View File
@@ -29,9 +29,9 @@ defmodule Kernel.AliasTest do
test :lexical do
if true do
alias OMG, as: List
alias OMG, as: List, warn: false
else
alias ABC, as: List
alias ABC, as: List, warn: false
end
assert List.flatten([1, [2], 3]) == [1, 2, 3]

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