Compare commits

...
832 Commits
Author SHA1 Message Date
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
José Valim e654f73762 Release v0.10.2 2013-09-03 15:02:43 -03:00
José Valim 147a452e7f Fix regression on Stream nesting 2013-09-03 13:39:43 -03:00
José Valim b39a5d8632 Deprecate %b and %B in favor of %s and %S 2013-09-03 13:14:13 -03:00
José Valim 4caa2e01f1 Move is_odd and is_even to Integer 2013-09-03 09:41:55 -03:00
José Valim 71129bca33 Merge pull request #1683 from meh/fix-version
Fix handling pre/build data when minor/patch is missing in Mix.Version
2013-09-02 12:39:04 -07:00
meh a107b2ba64 Fix handling pre/build data when minor/patch is missing in Mix.Version 2013-09-02 21:35:20 +02:00
José Valim 654988fe60 Update CHANGELOG 2013-09-02 16:05:50 -03:00
José Valim 10a795ff44 Only support iolists on IO 2013-09-02 16:01:10 -03:00
José Valim ba209268ad Merge pull request #1675 from ericmj/enum-at-fetch-neg
Support negative indicies in Enum.at/fetch/fetch!
2013-08-31 05:34:32 -07:00
Eric Meadows-Jonsson 80ae944d83 Support negative indicies in Enum.at/fetch/fetch! 2013-08-31 14:23:34 +02:00
José Valim 4d3a83262c Merge pull request #1671 from vanstee/string-split-trim-default
No longer trim by default when splitting
2013-08-31 04:27:46 -07:00
José Valim 6d02cd9076 Merge pull request #1672 from meh/regex-compile
Return error on unsupported options in Regex.compile
2013-08-31 04:21:46 -07:00
José Valim 42692c27ab Merge pull request #1669 from brunoro/ipv6-uri
URI.parse supports IPv6 addresses
2013-08-31 03:16:08 -07:00
Gustavo Brunoro 2b34ae14da parsing embedded IPv4 addresses 2013-08-30 19:04:30 -03:00
meh 6e60fa160e Return error on unsupported options in Regex.compile 2013-08-30 13:35:39 +02:00
vanstee dd4a0e96dd No longer trim by default when splitting
Further discussion here:

  https://groups.google.com/forum/#!topic/elixir-lang-core/66gb3J0bnUA
2013-08-30 00:38:41 -04:00
Gustavo Brunoro f4175a1dfa more tests for IPv6 2013-08-29 16:15:12 -03:00
José Valim bcceccd85e Consider stream_lazy to always return the result, move nesting check to after reduce 2013-08-29 07:46:24 -03:00
José Valim b1cf5d9252 Merge pull request #1667 from betawaffle/lazy-throw-fix
Fix Stream.take/2 and Stream.take_while/2 for Stream.Lazy
2013-08-29 03:16:27 -07:00
José Valim 447d2215d1 Merge pull request #1668 from elia/doc-fix
Markdown rendering fix
2013-08-29 03:07:54 -07:00
Gustavo Brunoro ffbcf67431 handling IPv6 addresses on URIs
* very naïve and permissive regex to check for IPv6 addreses.
2013-08-29 00:02:23 -03:00
Gustavo Brunoro d88cd5c92c adding tests for ipv6 addresses on URIs 2013-08-28 23:51:13 -03:00
Elia Schito fd5cbd2be6 Markdown rendering fix 2013-08-28 23:55:51 +02:00
Andrew Hodges e525fb3176 Fix Stream.take/2 and Stream.take_while/2 for Stream.Lazy 2013-08-28 16:27:45 -04:00
José Valim 49aed8cb9a Make a distinction about eager and full evaluation of the collection 2013-08-28 09:25:24 -03:00
José Valim 366d9843aa out of it -> as result 2013-08-28 09:16:19 -03:00
José Valim 0f2838c98c Improve Enum docs 2013-08-28 09:14:07 -03:00
José Valim 8ae1a3b60a Merge pull request #1662 from pminten/note-about-enum-list-return
Add note to Enum to clarify what's returned
2013-08-28 04:58:58 -07:00
Peter Minten 4a405cc8f0 Add note to Enum to clarify what's returned
In particular clarify that infinite streams don't work with many of the
functions.
2013-08-28 13:54:54 +02:00
José Valim 1aca6db15a Merge pull request #1660 from betawaffle/enum-concat
Add Stream.concat/1,2 and Enum.concat/1,2
2013-08-28 04:54:39 -07:00
Andrew Hodges f7391b48fa Add tests for Stream.concat/1,2 and Enum.concat/1,2 2013-08-27 12:53:31 -04:00
Andrew Hodges d78014e7e2 Add support for enumerables of enumerables 2013-08-27 10:22:24 -04:00
Andrew Hodges b2c2a30c83 Move the List concat tests to Enum 2013-08-27 09:37:56 -04:00
Andrew Hodges 9ef2996b78 Replace occurrences of List.concat/1,2 with Enum.concat/1,2 2013-08-27 09:37:26 -04:00
Andrew Hodges 0d13468b1f Deprecate List.concat/1,2 2013-08-27 09:36:17 -04:00
Andrew Hodges 6ef031b87d Add Enum.concat/1,2 and Stream.concat/1,2 2013-08-27 09:34:24 -04:00
José Valim 5f1e5ebc5f Update record docs style 2013-08-27 07:16:25 -03:00
José Valim 9312a7d127 Merge pull request #1658 from pminten/doc-guidelines-in-contributing
Add bit about documentation style to CONTRIBUTING
2013-08-27 03:15:04 -07:00
Peter Minten c555bf934b Add bit about documentation style to CONTRIBUTING 2013-08-27 11:27:19 +02:00
José Valim d4efc7bc5d Make it clear Dict.merge expects an enum that yields two-item tuples 2013-08-26 20:13:19 -03:00
José Valim 3c7fb0da6d Optimize split implementation 2013-08-26 15:53:49 -03:00
José Valim 89bebeaeea Merge pull request #1651 from vanstee/string-split-trim
Add support for `trim` option to `String.split` and `Regex.split`
2013-08-26 11:23:41 -07:00
José Valim c826aadc0f Merge pull request #1655 from ericmj/fix-enum-slice-oob
Enum.slice returns nil when start is out of bounds
2013-08-26 11:22:49 -07:00
vanstee e304ea9543 Accept the trim option for Regex.split/3
By default `trim` will be set to true. However, since the `global` option
was implemented with the `parts` option (`{ :parts, :infinity }`), we
have to apply a filter to the final result to support `trim`.
2013-08-26 11:41:22 -04:00
vanstee 64cdc76832 Accept the trim option when splitting strings
By default `trim` will be set to true. Because `:binary.strip` only
removes empty strings from the end of the result, we have to apply a
filter to remove empty strings from the beginning before returning.
2013-08-26 11:40:32 -04:00
Eric Meadows-Jonsson dbbfa3536f Enum.slice returns nil when start is out of bounds 2013-08-26 17:15:09 +02:00
José Valim 25ffb4b4b1 Merge pull request #1652 from vanstee/stream-flat-map
Implement `Stream.flat_map/2`
2013-08-26 05:42:10 -07:00
José Valim e80dad1925 Merge pull request #1654 from ericmj/fix-enum-find
Use ifnone in Enum.find for all collections
2013-08-26 05:26:25 -07:00
Eric Meadows-Jonsson e2599beff2 Use ifnone in Enum.find for all collections 2013-08-26 10:42:42 +02:00
vanstee 8478db168a Implement Stream.flat_map/2
Fixes #1642
2013-08-25 22:44:35 -04:00
vanstee 34dade0916 Revert "Merge pull request #1645 from vanstee/string-split-repeats"
This reverts commit fb30101632, reversing
changes made to c348eb7cc5.
2013-08-25 21:27:06 -04:00
José Valim eb96d73198 Add a count guard check for slice 2013-08-25 22:16:58 -03:00
José Valim cbb1f45ede Update CHANGELOG 2013-08-25 22:10:37 -03:00
José Valim 4723bca17e Merge pull request #1650 from ericmj/enum-slice
Add Enum.slice/3
2013-08-25 18:08:48 -07:00
Eric Meadows-Jonsson 263f4bf543 Add Enum.slice/3 2013-08-25 23:14:31 +02:00
José Valim fad339957d Remove empty Tuple module 2013-08-25 10:30:15 -03:00
José Valim 488344839a Allow any function identifier as a variable, closes #1649 2013-08-25 10:28:55 -03:00
José Valim f02f90fce0 Merge pull request #1648 from pminten/quote-improper-lists
Allow Macro.escape([1|2])
2013-08-25 03:45:37 -07:00
Peter Minten c73919993d Allow Macro.escape([1|2])
This fixes #1646
2013-08-25 09:35:08 +02:00
Alexei Sholik 3768087b47 Fix failing doctest 2013-08-25 00:29:54 +03:00
José Valim 4a0db2e974 Handle more types on bitsyntax. 2013-08-24 16:07:55 -03:00
José Valim 55aad68512 Splice nested binaries with default types and size 2013-08-24 15:55:24 -03:00
José Valim 3b4954b652 Merge pull request #1643 from pminten/elixirc-silent
Add --verbose to elixirc
2013-08-24 11:15:21 -07:00
José Valim fb30101632 Merge pull request #1645 from vanstee/string-split-repeats
Keep empty strings when splitting
2013-08-24 05:21:14 -07:00
José Valim c348eb7cc5 Merge pull request #1644 from jwarwick/string_typos
Fixed doc typos in String and Node
2013-08-24 05:02:29 -07:00
José Valim 98c47e94a1 String.to_float/1 docs updated 2013-08-24 13:56:35 +02:00
Peter Minten 4c28f325e3 Supress 'Compiled' msg by default in elixirc
Adds a --verbose option to elixirc

This fixes #1618
2013-08-24 13:30:34 +02:00
José Valim da50584060 Deprecate def/4 and friends in favor of def/2 with unquote and friends 2013-08-23 19:42:20 +02:00
vanstee 891564f114 Keep empty strings when splitting
To stay consistent with both `String.split/2` and `:binary.split` we
need to keep around empty strings that are accumulated while splitting.
In the future we could support the `:trim` option to consistently remove
these in call calls to `String.split`.

Fixes #1636
2013-08-23 12:37:02 -04:00
John Warwick 22336a94d0 Fixed doc typos in String and Node 2013-08-23 10:58:38 -04:00
José Valim cae9b05d6a Merge pull request #1638 from jwarwick/enum_typo
Fixed typo in Enum docs
2013-08-23 02:31:27 -07:00
José Valim a6cdc835a1 No need to wrap quote do: nil in a block 2013-08-23 11:22:11 +02:00
John Warwick 7ae1795c62 Fixed typo in Enum docs 2013-08-22 11:36:35 -04: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
José Valim 7b3bbe3a36 Another attempt at cwd handling 2013-08-22 16:07:19 +02:00
José Valim ade2c25e08 Handle when :file.get_cwd returns a binary 2013-08-22 15:59:14 +02:00
José Valim e2ba0bd649 Properly order CHANGELOG 2013-08-22 14:27:57 +02:00
José Valim 3de4cbbdb9 Update CHANGELOG 2013-08-22 12:20:37 +02:00
José Valim 0510512375 Deprecate to_binary in favor of to_string
We also rename Binary.Chars to String.Chars.

As Binary.Chars was the protocol used for interpolation,
it is semantically correct to have a protocol that works
on strings, instead of raw binaries.
2013-08-22 12:19:30 +02:00
José Valim a3827527a8 Update CHANGELOG 2013-08-22 11:13:18 +02:00
José Valim 820d0cb9a0 Print failures for ExUnit as soon as they come 2013-08-22 11:12:18 +02:00
José Valim 0e1aac32f5 import accept :functions and :macros as values in :only
This commit removes the previous ambiguous syntax:

    import :functions, Foo

In favor of:

    import Foo, only: :functions
2013-08-22 10:34:45 +02:00
Anthony Grimes 307198c750 Add a take_every function. 2013-08-22 00:18:50 -07:00
José Valim 308d5a35eb Merge pull request #1629 from Raynes/is_it_even_odd
Add a pair of even and odd checking functions
2013-08-21 23:48:59 -07:00
José Valim 6244bdd140 Merge pull request #1634 from jwarwick/path_typo
Fixed doc typos in Code and Path
2013-08-21 13:24:23 -07:00
John Warwick 384921a905 Fixed doc typos in Code and Path 2013-08-21 15:51:57 -04:00
Anthony Grimes fcc37030e2 Add a pair of even and odd checking functions. 2013-08-21 11:08:20 -07:00
José Valim 473f3c560e Update CHANGELOG 2013-08-21 10:42:06 +02:00
José Valim fb8ad142e9 Deprecate :umbrella path opt in favor of :in_umbrella
The previous :umbrella option was ambiguous, as it was not clear
if it was a dependency inside an umbrella or an umbrella dependency.
2013-08-21 10:31:17 +02:00
José Valim 7a50baa37b Make a distinction in between overriden and converged, closes #1631 2013-08-21 10:31:17 +02:00
José Valim 2f63c745a3 Merge pull request #1630 from Raynes/chunks_by
Add chunks_by
2013-08-21 01:30:26 -07:00
José Valim fcef2d57fc Rename manifest -> manifests to support compilers with multiple manifests 2013-08-21 09:12:25 +02:00
José Valim fcabb677d5 Merge pull request #1632 from jwarwick/change_typo
Fixed typos in CHANGELOG.md
2013-08-21 00:01:22 -07:00
John Warwick de3d5cdc40 Fixed typos in CHANGELOG.md 2013-08-20 21:33:59 -04:00
José Valim 29f030bb28 Update CHANGELOG 2013-08-20 23:57:59 +02:00
José Valim 8b02a2a7a0 Convert external functions into quoted expressions 2013-08-20 23:52:00 +02:00
Anthony Grimes f7e6b29e86 Add chunks_by. 2013-08-20 14:30:45 -07:00
José Valim c6a12f5039 Improve checks of functions inside records 2013-08-20 19:31:10 +02:00
José Valim 20418a14f7 Refactor Enum.chunks to use Enumerable.reduce/3, avoiding starting lazy collections twice 2013-08-20 14:08:27 +02:00
José Valim 3e1099830a Properly handle @file that comes from warnings 2013-08-20 11:39:29 +02:00
José Valim 82aed93466 Merge pull request #1626 from ToJans/master
Refactored Mix.Tasks.Local.Install to make it more explicit
2013-08-20 02:38:44 -07:00
Tom Janssens df9098ea36 Refactored Mix.Tasks.Local.Install to make it more explicit 2013-08-20 10:15:11 +02:00
José Valim 6831c09913 Merge pull request #1624 from Raynes/chunked
Add Enum.chunks
2013-08-20 00:49:47 -07:00
Anthony Grimes 7f00aded18 Add Enum.chunks. 2013-08-20 00:48:42 -07:00
José Valim 37689946d2 Merge pull request #1625 from jwarwick/typo
Fixed doc typos in IEx.Helpers
2013-08-20 00:14:50 -07:00
John Warwick d76dba1518 Fixed doc typos in IEx.Helpers 2013-08-19 18:22:46 -04:00
José Valim baf647986f Avoid extra eval on EEx 2013-08-19 22:17:29 +02:00
José Valim 84a2b31221 Add Macro.to_string/2 2013-08-19 18:21:42 +02:00
José Valim 9385e59c29 Provide more semantic names for ExUnit.ExpectationError 2013-08-19 11:46:59 +02:00
José Valim 6d9506c012 Improve error message for h(:lists) 2013-08-19 08:41:43 +02:00
José Valim 11640d521f Merge pull request #1621 from meh/version-fix
Fix for Mix.Version comparisons with dev versions
2013-08-18 12:43:38 -07:00
meh 34e0637520 Fix for Mix.Version comparisons with dev versions 2013-08-18 17:29:32 +02:00
José Valim f84b4121ce Allow << _ :: utf8 >> <> binary in matches, closes #1619 2013-08-18 14:36:54 +02:00
José Valim ff2af55e1d Improve warning for ungrouped clauses 2013-08-18 10:05:36 +02:00
José Valim a45a2c9d93 Allow erlang options when compiling elixir regexes 2013-08-17 11:57:17 +02:00
José Valim e3dc5564e8 Merge pull request #1616 from parroty/patch-1
Add underscore for avoiding variable unused warning
2013-08-17 00:10:23 -07:00
José Valim b4a42a4f87 Remove unecessary modules from docs 2013-08-17 09:09:23 +02:00
parroty f2066e0ea9 Add underscore for avoiding variable unused warning 2013-08-17 12:02:48 +09:00
José Valim eccf6c9e6e Rely on :binary.part since we are using :binary prefix functions 2013-08-16 09:36:11 +02:00
José Valim aaebf84440 Always stop on --, even for parse 2013-08-16 09:28:58 +02:00
José Valim 9956785769 Do not show dest info when comparing deps 2013-08-16 09:22:13 +02:00
José Valim 8a69780246 Merge pull request #1610 from stratosv/patch-1
Fixed comment typo in lib/elixir/src/elixir_translator.erl
2013-08-15 23:34:34 -07:00
José Valim 7f36bf105a Merge pull request #1611 from thmzlt/patch-1
Fix typo in CHANGELOG.md
2013-08-15 23:32:18 -07:00
Thomaz Leite f832d3874b Fix typo in CHANGELOG.md 2013-08-15 17:08:38 -07:00
Stratos Voukelatos 42339679e0 Fixed comment typo in lib/elixir/src/elixir_translator.erl 2013-08-16 00:34:23 +03:00
José Valim 0edc5ac2b9 Merge pull request #1608 from jwarwick/mix_shell_process
Fixed typos in Mix.Shell.Process docs
2013-08-15 11:56:22 -07:00
John Warwick 74e194669b Fixed typos in Mix.Shell.Process docs 2013-08-15 14:38:23 -04:00
José Valim 80ce7fc787 Update LEGAL file 2013-08-15 17:04:49 +02:00
José Valim f4270a0432 Do not touch lock manifest if there is no change 2013-08-15 17:04:32 +02:00
José Valim a696b9cc11 Update Macro.to_string comments 2013-08-15 14:27:53 +02:00
José Valim ffc629a408 Merge pull request #1606 from pminten/macro-to-string-binop-parens
Macro.to_string: don't add unneeded binop parens
2013-08-15 05:24:53 -07:00
Peter Minten 081dbceeee Macro.to_string: don't add unneeded binop parens
This changes the `Macro.to_string` output of (quoted) `1+2+3+4` from
`((1 + 2) + 3) + 4` to `1 + 2 + 3 + 4`.
2013-08-15 14:05:37 +02:00
José Valim 9ec2176675 Ensure we are not allocating a new binary on each step 2013-08-15 13:51:55 +02:00
José Valim 1ec8321658 Update CHANGELOG 2013-08-15 10:40:24 +02:00
José Valim eb9753ddd1 Limit inspect by default, closes #1595 2013-08-15 10:35:04 +02:00
José Valim 5193410ba9 Rewrite IEx.Autocomplete to work on strings 2013-08-15 10:35:04 +02:00
José Valim 59013d4066 Merge pull request #1602 from jwarwick/iolist_typo
Cleaned up Kernel.iolist_to_binary/1 docs
2013-08-14 23:53:52 -07:00
José Valim a641a63f98 Merge pull request #1604 from kwijibo/master
Update file.ex
2013-08-14 23:51:56 -07:00
José Valim a86288f5e3 Merge pull request #1597 from thmzlt/doctest_indent
Improve DocTest indentation handling
2013-08-14 23:51:17 -07:00
John Warwick 4900cd84fc Cleaned up Kernel.iolist_to_binary/1 docs 2013-08-14 21:09:46 -04:00
Thomaz Leite 09b34c3aa6 Change DocTest to conform with updated specs
Updated specs are described in http://git.io/o_ccxQ
2013-08-14 17:24:05 -07:00
Keith Alexander 5c3ff52741 Update file.ex
fixed typo
2013-08-14 23:47:00 +01:00
José Valim 7ad6d8e6e2 Merge pull request #1601 from ericmj/macro-tostring-access
Macro.to_string handles access and kw-lists nicer
2013-08-14 14:04:32 -07:00
Eric Meadows-Jonsson 35af4348ae Macro.to_string handles access and kw-lists nicer 2013-08-14 22:10:48 +02:00
José Valim a60e6fab48 Merge pull request #1600 from devinus/defp-justify
do_justify should be private
2013-08-14 11:52:42 -07:00
Devin Torres d6c7e813f3 do_justify should be private 2013-08-14 13:40:33 -05:00
José Valim 5ce8f34fe6 Add missing helpers file 2013-08-14 13:32:50 +02:00
José Valim ff9aba4350 Handle unsafe vars inside or/and 2013-08-14 13:24:12 +02:00
José Valim 6ac280e17c Clean up and expose are more complete API in Exception 2013-08-14 12:55:13 +02:00
José Valim db47bd56b4 Add Path.relative_to_cwd and rely on it when generating stacktraces 2013-08-14 12:36:54 +02:00
José Valim e2ef1ae8de Improve docs for iolist_to_binary 2013-08-14 11:39:04 +02:00
José Valim d5fc147ff0 Quote arguments on elixir/iex help check 2013-08-14 11:25:28 +02:00
José Valim cea31f69dc Review and deprecate list <-> binary conversion 2013-08-14 11:16:37 +02:00
José Valim 3259599111 Update CHANGELOG 2013-08-14 08:34:32 +02:00
José Valim 8affa706b7 Merge pull request #1593 from vanstee/string-justification-functions
Implement string justification functions
2013-08-13 23:27:04 -07:00
Thomaz Leite b248822a0c Remove unused variable from DocTest 2013-08-13 23:13:45 -07:00
Thomaz Leite e4d679c039 Add "too much" and "not enough" tests to DocTest 2013-08-13 23:13:45 -07:00
Thomaz Leite b5cebbfc14 Make mismatched indentation test pass 2013-08-13 23:13:45 -07:00
Thomaz Leite 649536fc61 Add mismatched indentation test to DocTest 2013-08-13 23:13:45 -07:00
Thomaz Leite 676cab5c8f Make heredocs indentation test pass 2013-08-13 23:13:45 -07:00
Thomaz Leite bdf7bc5e6c Add heredocs indentation test to DocTest 2013-08-13 23:13:45 -07:00
José Valim d8ba3e63b3 Merge pull request #1596 from chrismccord/improve_enum_take_docs
Add example to Enum.take docs using negative count.
2013-08-13 22:50:08 -07:00
Chris McCord 8e43b4268d Add example to Enum.take docs using negative count. 2013-08-13 22:55:28 -04:00
vanstee 0850430768 Only accept a single character as padding 2013-08-13 11:07:02 -04:00
vanstee b14617ca90 Do not truncate strings when justifying 2013-08-13 10:53:27 -04:00
vanstee bc35aebd0c Include documentation for justification functions 2013-08-13 10:28:40 -04:00
vanstee 0276419f4e Implement rjust and ljust string functions
To provide string justification, the following functions were
implemented:

  * `String.rjust/2`
  * `String.rjust/3`
  * `String.ljust/2`
  * `String.ljust/3`
2013-08-13 10:28:40 -04:00
vanstee b710ebd5e6 Return an empty string from slice with valid index
To keep in line with ruby semantics of
[`String#slice`](http://ruby-doc.org/core-2.0/String.html#method-i-slice)
slice should return an empty string instead of nil when the starting
index is valid even if the length is 0.

Fixes #1591
2013-08-13 09:45:08 -04:00
José Valim ec479a6805 Do not compile behaviour docs if --docs is not set 2013-08-13 10:47:25 +02:00
José Valim 6277009d90 Update CHANGELOG 2013-08-13 08:39:16 +02:00
José Valim 52188fab89 Move from unicode to list to String.to_char_list 2013-08-13 08:39:16 +02:00
José Valim 19ac199b06 Move from unicode to binary to String.from_char_list 2013-08-13 08:39:16 +02:00
José Valim 5262cb9c57 Macro.unescape_binary -> Macro.unescape_string 2013-08-13 08:39:16 +02:00
José Valim 82d8d2ad63 Deprecate Dict.update/3 in favor of Dict.update!/3
A non-raising version that yields nil will be added in
upcoming releases.
2013-08-13 08:39:16 +02:00
Eric Meadows-Jonsson 5a620ca319 Remove sleep in recompile test 2013-08-12 22:01:20 +02:00
José Valim cbcdf411fa Merge pull request #1590 from jwarwick/record_docs
Fixed Kernel.defrecord arity in docs
2013-08-12 07:17:40 -07:00
John Warwick 40478d1014 Fixed Kernel.defrecord arity in docs 2013-08-12 10:12:08 -04:00
José Valim 7100965169 Merge pull request #1587 from meh/process-fix
Process fix
2013-08-12 00:37:03 -07:00
José Valim d8ded6ecd4 Merge pull request #1588 from vanstee/string-strip-support-unicode
Support removing unicode characters with strip
2013-08-12 00:36:50 -07:00
José Valim 7e0428f52f Merge pull request #1589 from rramsden/doc-fix/typo
Fix typo in File.rmdir/1
2013-08-11 23:07:59 -07:00
Richard Ramsden 77ddc4540d Fix typo in File.rmdir/1 2013-08-12 09:07:04 +09:00
vanstee 742219339c Support removing unicode characters with strip
Thanks for the suggested fix @josevalim
2013-08-11 16:49:15 -04:00
meh e02bf30c9d Make Process.info return nil instead of :undefined 2013-08-11 22:11:07 +02:00
meh cde006affa Fix Process.whereis documentation 2013-08-11 22:11:07 +02:00
José Valim 9daa0bc3a2 Support IO.stream/2 and IO.binstream/2 and deprecate current IO.sream/1 and IO.binstream/1
Closes #1582
2013-08-11 15:48:31 +02:00
José Valim b0317a8e18 Allow capture on anonymous function calls 2013-08-11 15:16:00 +02:00
José Valim 4eb98a83e5 Clean up option parser 2013-08-11 12:38:47 +02:00
José Valim 7e17551456 Update CHANGELOG 2013-08-11 12:31:42 +02:00
José Valim 437037cda6 Fix the build 2013-08-11 12:17:16 +02:00
José Valim d3cb41614b Merge pull request #1561 from thmzlt/option-parser-invalid-options
Improve OptionParser switch validation
2013-08-11 02:54:18 -07:00
José Valim cdef2e762f Merge pull request #1584 from vanstee/atom-system-limit-error
Return error when tokenizing oversized atom
2013-08-11 02:48:12 -07:00
José Valim a7ee38ae9e Detect windows symlinks on File.rm_rf 2013-08-11 11:43:47 +02:00
vanstee ad67e4935a Return error when tokenizing oversized atom
When "atomizing" an unsafe string, we need to make sure the atom length
is going to be less than the system limit (255). Any function calling
`unsafe_to_atom` checks for an error and returns it if present.
2013-08-10 17:10:47 -04:00
vanstee 5371101ad0 Include line number in atomization 2013-08-10 16:54:31 -04:00
vanstee d9cb9c99f0 Separate atomization from tokenization 2013-08-10 16:53:03 -04:00
José Valim 978850ec9e Do not stack rm_rf errors together 2013-08-10 20:06:46 +02:00
José Valim 1b5d81c4d4 Clean up app.start tests 2013-08-10 19:28:25 +02:00
José Valim 12d6666b54 Get rid of unused variable warning 2013-08-10 19:28:15 +02:00
José Valim e83dc3495e Pass Line and File as arguments to we don't read the table twice 2013-08-10 19:09:38 +02:00
José Valim 4ade2cbd48 Merge pull request #1577 from ericmj/mix-recompile
Force recompile of project when elixir version changes
2013-08-10 10:08:42 -07:00
Eric Meadows-Jonsson bf96705d5f Recompile project if elixir version changes 2013-08-10 17:14:41 +02:00
José Valim de72fa2012 Merge pull request #1579 from pminten/add-location-of-overridden-def
Add location of overridden def in warning message
2013-08-10 06:42:38 -07:00
José Valim 55de57ced6 Ensure proper line on interpolations inside heredocs, closes #1581 2013-08-10 15:40:02 +02:00
José Valim 70421e79b1 Fix bug on sorting algorithm on certain branches, closes #1580 2013-08-10 13:28:29 +02:00
Peter Minten a538e86c0c Put overridden def test in warning_test.exs
This removes the need for a separate fixture.
2013-08-10 13:18:34 +02:00
Peter Minten 95bffca5f0 Use relative filename in redefined def warning
The absolute filename can be a bit long, which makes the warning
slightly harder to parse for humans.
2013-08-10 13:08:26 +02:00
Peter Minten e18ae5e335 Add test for overridden def warning 2013-08-10 11:59:50 +02:00
Peter Minten 00d2ad0649 Add location of overridden def in error message
When a def is overridden, for example because there are multiple
clauses with the clauses of a different def are between them, it
is nice to know where the def that was overridden starts.

This should fix #1559.
2013-08-10 11:59:24 +02:00
José Valim 9c7e6495e9 Merge pull request #1578 from guilleiguaran/enum_map_flatten
Add Enum.flat_map
2013-08-10 01:28:43 -07:00
Guillermo Iguaran ed7965e257 Add Enum.flat_map
Returns a new collection appending the result of invoking `fun`
on each corresponding item of `collection`.
2013-08-10 02:36:16 -05:00
Thomaz Leite 0187f31520 Converge store_option and validate_option in OptionParser 2013-08-09 09:52:15 -07:00
Thomaz Leite a577051b09 Update mix tasks for OptionParser permanent invalid list 2013-08-09 09:52:15 -07:00
Thomaz Leite abbbcdf659 Update OptionParser test for permanent invalid list 2013-08-09 09:52:15 -07:00
Thomaz Leite 9fa7f0810f Change OptionParser to always permanent invalid list
The tuple returned from OptionParser now always includes the third element (invalid options list).
2013-08-09 09:52:15 -07:00
Thomaz Leite 770a7880c2 Add documentation for invalid switches 2013-08-09 09:52:15 -07:00
Thomaz Leite edfacf0c2f Use list concatenation operator instead of function 2013-08-09 09:52:15 -07:00
Thomaz Leite 06bc04a7bf Improve OptionParser switch validation
This adds a third element to the tuple returned from OptionParser.parse/2 when the value for an option doesn't match its switch type.
2013-08-09 09:52:15 -07:00
José Valim 730c0992b5 Add Dict.Behaviour to CHANGELOG 2013-08-09 08:42:09 +02:00
José Valim 8d6d4c37a4 Merge pull request #1574 from jwarwick/dict_typos
Fixed doc typos in Dict
2013-08-08 23:37:11 -07:00
José Valim bae0a46bab Merge pull request #1571 from sasa1977/dict_behaviour
added Dict.Behaviour
2013-08-08 23:37:02 -07:00
John Warwick 80caf331ec Fixed doc typos in Dict 2013-08-08 17:59:58 -04:00
sasa1977 47316bcae7 modifications according to comments from the pull request 2013-08-08 18:16:08 +02:00
José Valim b83eb85a25 Slight clean up on dict_test.exs 2013-08-08 17:28:01 +02:00
sasa1977 596f62c57e added Dict.Behaviour 2013-08-08 14:58:12 +02:00
José Valim 9d9bec0af2 Support --detached and --hidden options 2013-08-08 11:19:35 +02:00
José Valim db26fb2d18 Allow iex to --remsh into an Erlang node that has IEx 2013-08-08 10:46:17 +02:00
José Valim 23c286c4ac Merge pull request #1567 from jwarwick/doc_typos
Fixed doc typos in List and Enum
2013-08-07 23:40:01 -07:00
John Warwick 0c7995d119 Fixed doc typos in Enum 2013-08-07 17:24:25 -04:00
John Warwick 7aa28e042e Fixed doc typos in List 2013-08-07 17:09:37 -04:00
José Valim c0ad8c5539 Clean up Dict interface 2013-08-07 19:42:55 +02:00
José Valim 03d2260776 Fix warnings and update CHANGELOG 2013-08-07 12:51:31 +02:00
José Valim 8d156ba5fb Merge pull request #1562 from alexrp/master
Make System.get_env/0 behave sanely.
2013-08-07 03:49:01 -07:00
alexrp 8acdb26842 Make System.get_env/0 behave sanely. 2013-08-07 12:44:05 +02:00
José Valim bb487447ac Update CHANGELOG 2013-08-07 08:26:18 +02:00
José Valim 156905d684 Deprecate Enum.min/2 and Enum.max/2 in favor of Enum.min_by/2 and Enum.max_by/2 2013-08-07 08:26:06 +02:00
José Valim b212b036f9 Refactor Enum.min/1 and Enum.max/1 2013-08-07 08:14:23 +02:00
Guillermo Iguaran 4906f22479 Add Enum.reduce/2 that sets initial value of acc to the first element of collection
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/elixir/lib/enum.ex
2013-08-07 08:04:06 +02:00
José Valim 35282e1d35 Fix a bug in insert_at adding items to the wrong location with negative indexes 2013-08-07 07:56:03 +02:00
José Valim 2bf693292b Merge pull request #1558 from sasa1977/list_replace_at
List.replace_at
2013-08-06 22:45:58 -07:00
sasa1977 7c8b183513 List.replace_at 2013-08-06 23:34:39 +02:00
José Valim d5cfe56c67 Improve Enum.shuffle and String.replace docs 2013-08-06 21:35:04 +02:00
José Valim d74a19c887 Merge pull request #1557 from meh/string-replace
Add regex pattern support to String.replace
2013-08-06 12:15:06 -07:00
meh 09f5a84a13 Add regex pattern support to String.replace 2013-08-06 20:27:02 +02:00
José Valim 93eea5dd6d Reduce the ammount of module binaries being passing around 2013-08-06 17:21:40 +02:00
José Valim d8393f97ca Remove deprecated code 2013-08-06 17:21:40 +02:00
José Valim 905a526b18 Merge pull request #1556 from derekmcloughlin/issue-1555
Fix for #1555
2013-08-06 06:53:07 -07:00
José Valim a8cee02558 Fix regression on do/end blocks, closes #1551 2013-08-06 15:44:52 +02:00
Derek McLoughlin 08ed1f087a Fix for #1555 2013-08-06 14:28:23 +01:00
José Valim e591a0f8e8 Merge pull request #1552 from ericmj/iex-erlang-helpers
IEx helpers r and c can handle erlang sources
2013-08-06 05:34:44 -07:00
José Valim 7a7d1c54e7 Merge pull request #1554 from tristanoneil/mix-clean-all
Pass clean arguments to deps.clean
2013-08-05 14:14:09 -07:00
Tristan O'Neil 74c3ed0cfb Test mix clean with the --all flag. 2013-08-05 17:04:19 -04:00
Tristan O'Neil 5d1a6c2fc0 Original clean arguments need to be passed to deps.clean. 2013-08-05 16:15:45 -04:00
Eric Meadows-Jonsson fe65f5f1f4 IEx helpers r and c can handle erlang sources 2013-08-05 20:11:39 +02:00
José Valim b948f3b3dd Make it explicit Regex.run returns all captures in a match 2013-08-06 01:15:01 +08:00
José Valim aad42261cc Document dash to underscore in OptionParser 2013-08-04 23:17:35 +02:00
José Valim bd21251659 Merge pull request #1546 from fishcakez/supervisor-shutdown
Fix default shutdown of child supervisors to infinity
2013-08-04 11:53:06 -07:00
James Fish 5bbaeee149 Fix default shutdown of child supervisors to infinity
A supervisor should wait for a child supervisor to shutdown its
children, and not kill it. By killing a supervisor you may prevent the
correct termination of processes further down the supervision tree.
2013-08-04 19:15:34 +01:00
José Valim 206fa3f9ed Update CHANGELOG 2013-08-04 19:24:21 +02:00
José Valim 6470a36e68 Ensure watched extensions trigger full recompilation on change 2013-08-04 19:22:15 +02:00
José Valim 69754fa6b5 Raise on &0 inside capture 2013-08-04 19:04:22 +02:00
Yurii Rashkovskii 591c576307 Minor mix compile.elixir task documentation fix 2013-08-04 09:48:34 -07:00
José Valim 8eabc189b7 Merge pull request #1545 from yrashk/deps-compile-quiet
Ensure even a greater level of quiteness when --quiet is used in deps
2013-08-04 09:38:35 -07:00
Yurii Rashkovskii bb98f6a40a Ensure even a greater level of quiteness when --quiet is used in deps
Before (on deps.get --quiet):

==> project
* Compiling project_p1
==> project_p2
* Compiling project_p1
* Compiling project
* Compiling project
==> project_p3
* Compiling project_p1
* Compiling project
* Compiling project
==> project_p4
* Compiling project_p1
* Compiling project
* Compiling project
==> project_p5
* Compiling project_p1
* Compiling project
* Compiling project

Now:

(empty)
2013-08-04 09:34:56 -07:00
José Valim 9861d8c55c Only touch look if a dependency possibly changed 2013-08-04 17:51:49 +02:00
José Valim eb3d720556 Use a specific .compile.deps flag for tracking deps changes 2013-08-04 16:32:42 +02:00
José Valim 17eb7320ac Merge pull request #1542 from thmzlt/iex-h-helper-string
Allow for strings in iex h helper
2013-08-04 00:46:34 -07:00
Thomaz Leite 3d147b1f4e Use list argument in h helper for strings
This will check the function name in three different places (__MODULE__, Kernel, and Kernel.SpecialForms), allowing one to ask the docs for the new operator &.
2013-08-03 14:41:15 -07:00
Thomaz Leite fb05c7e5a7 Rephrase h helper doc for string arguments 2013-08-03 14:21:04 -07:00
Thomaz Leite f55f4d4a4e Add h helper doc for string arguments 2013-08-03 13:56:30 -07:00
Thomaz Leite 0f0efbc13c Allow for strings in iex h helper 2013-08-03 13:44:38 -07:00
José Valim 99394d2a75 Mention capture operator in CHANGELOG 2013-08-03 21:07:23 +02:00
José Valim bac39e4a8d Bump to 0.10.2-dev 2013-08-03 18:20:15 +02:00
José Valim 0523789c33 Release v0.10.1 2013-08-03 18:08:26 +02:00
José Valim 6d62983581 Capture now works with macros, closes #1534 2013-08-03 16:15:29 +02:00
José Valim 2e8cb67880 Remove weird chars from bat, closes #1539 2013-08-03 15:55:13 +02:00
José Valim 37a1a232b3 Merge pull request #1532 from derekmcloughlin/issue-1531
Issue 1531 - fix issue with slow batch files.
2013-08-03 06:43:35 -07:00
Derek McLoughlin b92099f5bd Fix issue with enableddelayedexpansion 2013-08-02 12:26:56 +01:00
Eric Meadows-Jonsson 15236c34e3 Fix String.contains? not raising on non-string arg 2013-08-02 13:08:14 +02:00
José Valim 34b6e17a1b Fix shadowing warning in case with function, closes #1524 2013-08-02 13:04:03 +02:00
Derek McLoughlin c374b357bb Issue #1531 - fix slow running of batch file in Windows 2013-08-02 11:20:58 +01:00
José Valim 31818d677e Stop checking for matches once we reach a fun 2013-08-02 10:35:52 +02:00
José Valim c6f9287ce7 Only raise on local macro conflicts if it was previously defined 2013-08-02 09:53:30 +02:00
José Valim 09b1216c54 Merge pull request #1529 from jwarwick/macro_typos
Fixed doc typos in Macro
2013-08-01 22:56:04 -07:00
Eric Meadows-Jonsson f7f486c7d1 Fix String.starts_with? and String.ends_with? non-raising edge case 2013-08-01 23:57:02 +02:00
José Valim 7df5d157af Remove space excess in compile.elixir docs 2013-08-01 22:42:08 +02:00
José Valim d7ff228b14 Update docs for Mix.project/0 2013-08-01 22:35:51 +02:00
John Warwick 934b47f2d3 Fixed doc typos in Macro 2013-08-01 14:52:53 -04:00
José Valim ec02c65af5 Fix issue with nested binaries in Macro.to_string/1, closes #1522 2013-08-01 20:48:22 +02:00
José Valim ff0304cac7 Merge pull request #1528 from jwarwick/regex_typos
Fixed doc typos in Regex
2013-08-01 11:37:01 -07:00
John Warwick 660740bee0 Fixed doc typos in Regex 2013-08-01 14:36:08 -04:00
José Valim 4068160a76 Normalize and allow captures in Regex.scan/3, closes #1526 2013-08-01 19:52:32 +02:00
José Valim 2a672faca1 Merge pull request #1521 from ericmj/stream-fixes
Fix Stream bugs and add more composing tests
2013-07-31 23:15:03 -07:00
Eric Meadows-Jonsson d454aaf155 Fix Stream bugs and add more composing tests
Fix Stream.take_while looping infinitely when given lazy stream.
Fix Stream.take and Stream.take_while calling fun one extra time.
Add more tests where Stream functions are given streams.
2013-08-01 01:20:59 +02:00
José Valim a154a9e99a Allow &1.foo syntax inside capture 2013-07-31 16:03:52 +02:00
José Valim 2ed1c373e2 Fix Macro.to_string with & 2013-07-31 15:52:53 +02:00
José Valim 46fae17b7e Allow &() as a general capture mechanism 2013-07-31 14:53:22 +02:00
José Valim fe9ae95bdc Merge pull request #1519 from jwarwick/file_typos
Fixing doc typos in File
2013-07-31 03:58:31 -07:00
José Valim 6a68ca5eef Deprecate function/1 and function/3 2013-07-31 12:58:07 +02:00
José Valim b5428adda6 Add &local/arity and &Remote.fun/arity 2013-07-31 12:58:07 +02:00
John Warwick 05a20e3bc7 Fixing doc typos in File 2013-07-30 14:08:03 -04:00
José Valim 11093b7e27 Merge pull request #1517 from ToJans/master
Fixed problem with elixir.bat removing exclamation marks from command line
2013-07-30 05:42:10 -07:00
Tom Janssens 6d57915496 Fixed problem with elixir.bat removing exclamation marks from command line 2013-07-30 12:05:51 +02:00
Kevin Liddle 55623c1dcb always remove unmatched beam files when compiling 2013-07-30 11:50:02 +02:00
José Valim 2e348288a1 Remove old mix artifacts without a need for force or changes 2013-07-30 11:47:29 +02:00
José Valim fb5c67bf99 Update CONTRIBUTING.md
Feature requests should be discussed over the core mailing list.
2013-07-30 09:03:41 +02:00
José Valim d63d2c93e5 Get rid of buggy System.halt/2
Closes #1515, thanks to @AtKaaZ for debugging and pinpointing
this function as the root cause for output issues on Windows.
2013-07-30 00:50:10 +02:00
José Valim abb2ada8b1 Make & an unary operator 2013-07-29 14:08:33 +02:00
José Valim 5dfb2c33e2 Merge pull request #1514 from jwarwick/enum_docs
Fixed typos in Enum docs
2013-07-28 23:35:39 -07:00
José Valim 1c2f82362e Merge pull request #1509 from jwarwick/issue_1506
Return values for Code.eval_string and Code.eval_quoted
2013-07-28 23:34:43 -07:00
José Valim 73645f05f0 Merge pull request #1513 from ericmj/enum-at-docs
Fix Enum.at docs
2013-07-28 23:34:29 -07:00
John Warwick cda0c6c3d6 Fixed typos in Enum docs 2013-07-28 21:03:35 -04:00
John Warwick 933d4b88ee Using 'binding' to refer to a keyword list of variable bindings 2013-07-28 20:34:51 -04:00
Eric Meadows-Jonsson 26efdaf931 Fix Enum.at docs 2013-07-29 00:18:24 +02:00
José Valim e6b075f52f Remove another invalid syntax sample 2013-07-28 23:25:05 +02:00
José Valim a453ea1afa Fix usage of char_list and fix the build 2013-07-28 22:57:46 +02:00
José Valim 61fb890d01 Merge pull request #1511 from guilleiguaran/with_index
Move definition of with_index below
2013-07-28 13:50:32 -07:00
Guillermo Iguaran d1e26eafaa Move definition of with_index below
Right now is defined in the middle of two definitions of max function.
2013-07-28 15:26:48 -05:00
José Valim 7bb592cccf Merge pull request #1507 from AtKaaZ/reborntest1
revive a non-functioning test
2013-07-28 10:55:04 -07:00
José Valim a24b9c4afc Merge pull request #1510 from ericmj/mix-cmd-win
Dont escape " in shell commands on windows
2013-07-28 10:54:33 -07:00
Eric Meadows-Jonsson 4c7ee1e65c Dont escape " in shell commands on windows 2013-07-28 18:29:17 +02:00
José Valim f2addbb7b8 Improve docs for Enum.all?/2, closes #1508 2013-07-28 17:22:36 +02:00
José Valim b5e57970fe Remove undocumented Record.new(tuple) feature 2013-07-28 17:19:24 +02:00
AtKaaZ eee3d3f757 revive a non-functioning test
tested on Windows
2013-07-28 18:13:41 +03:00
John Warwick 835403af54 Fixed additional doc typos in Code module 2013-07-27 23:12:40 -04:00
John Warwick 9200327f77 Fixed Issue #1506, added description of return values for Code.eval_string and Code.eval_quoted 2013-07-27 22:59:03 -04:00
José Valim 12f6a9aa86 Fix some invalid error messages from Mix 2013-07-27 12:21:50 +02:00
José Valim fe340feb06 test_helper files still need to be required upfront
Since they have to be required upfront, it makes no sense to have
a Code.require_file("test_helper.exs", __DIR__) in Mix projects
since the files can't run in isolation anyway.
2013-07-27 12:15:00 +02:00
José Valim 1e90076738 Make it explicit that some tasks require a project to be defined 2013-07-27 10:43:41 +02:00
José Valim 456b84616c mix run now properly sets System.argv 2013-07-27 10:17:02 +02:00
José Valim ac666a9731 Improve docs for EEx.AssignsEngine, closes #1499 2013-07-27 09:55:24 +02:00
José Valim cca4ac9052 Merge pull request #1502 from jwarwick/code_formatting
Fixed docs typos and added code markers around functions in docs
2013-07-26 13:52:58 -07:00
John Warwick a700e23d90 Fixed docs typos and added markers around functions in docs 2013-07-26 16:28:55 -04:00
José Valim eee8880930 Merge pull request #1501 from AtKaaZ/mixenvWindows1
fixing a test to also work in Windows
2013-07-26 13:11:56 -07:00
AtKaaZ 004bfc43a4 fixing a test to also work in Windows
used to do this:
'MIX_ENV' is not recognized as an internal or external command,
operable program or batch file.
Tested to work on both Windows and Linux.
However the following is seen on console:
Mix requires the compile path "ebin" to be inside a directory with the
 same name as the application name :no_mixfile,
got "s:/workspace.elixir.2013/elixir/lib/mix/tmp/Mix.CLITest/test env/ebin"

quick test with `make test_mix`
2013-07-26 22:02:29 +03:00
José Valim 318716afcd Return :ok from FileCase callbacks 2013-07-26 18:37:24 +02:00
José Valim ca6b8d356e No longer clean or unlock deps unless --all is given 2013-07-26 17:25:53 +02:00
José Valim 178b9370d0 Ensure path dependencies are compiled on mix deps.get 2013-07-26 17:18:08 +02:00
José Valim e49baddffa Update @on_definition docs 2013-07-26 08:40:12 +02:00
José Valim a1c7aa1c7a Merge pull request #1497 from pragdave/master
Trivial wording changes in the Changelog
2013-07-25 23:24:09 -07:00
Dave Thomas 973df580a1 Trivial wording changes in the Changelog 2013-07-25 20:41:38 -05:00
José Valim 91d5ee1d8f Clean up Elixir path related tests 2013-07-25 21:49:54 +02:00
José Valim 5116e28aac Avoid echo with no args on windows 2013-07-25 19:59:54 +02:00
José Valim 751ebc8c63 Revert "Update ExUnit.Assertions docs, closes #1493"
This reverts commit 655a3c7d70.
2013-07-25 15:36:20 +02:00
José Valim 7f18e1426e Only set preferred cli env if MIX_ENV is nil 2013-07-25 14:55:46 +02:00
José Valim a013b39b8f Do not double escape strings inside interpolation, closes #1494 2013-07-25 14:34:13 +02:00
José Valim 4e4c9f13a0 Properly treat :<<>>, :[] and :{} as atoms 2013-07-25 14:13:12 +02:00
José Valim 3ebd7bbdb7 Allow defmacrocallback to be specified in Behaviour 2013-07-25 13:05:26 +02:00
José Valim 7672e7740d Update CHANGELOG 2013-07-25 12:39:57 +02:00
José Valim d77d013b32 Support --color option in ExUnit
The option can also be passed via mix, closes #1364
2013-07-25 12:31:18 +02:00
José Valim 1db491eb49 Add mix cmd 2013-07-25 11:32:21 +02:00
José Valim 4d70494474 Use %B on docs 2013-07-25 11:06:23 +02:00
José Valim 954310ae2c Get rid of Mix.Tasks.IEx 2013-07-25 11:02:29 +02:00
José Valim f86c99c91a Add umbrella: true to supported deps options 2013-07-25 11:00:50 +02:00
José Valim 408ef28da2 Group ExUnit.start calls into one, closes #1489 2013-07-25 10:38:28 +02:00
José Valim 151e49ef49 Ensure environment is set before any task is executed, closes #1490 2013-07-25 10:34:53 +02:00
José Valim 655a3c7d70 Update ExUnit.Assertions docs, closes #1493 2013-07-25 10:21:58 +02:00
José Valim b52c3c7b52 Merge pull request #1492 from jwarwick/kernel_typo
Fixed Kernel.match? example. Added formatting to Kernel docs.
2013-07-24 23:30:58 -07:00
John Warwick 38ea253d02 Fixed Kernel.match? example. Added formatting to Kernel docs. 2013-07-24 23:23:22 -04:00
José Valim 182e11f059 Update new app docs 2013-07-24 23:48:39 +02:00
José Valim fe67ecd2b6 Mix retriever should use the .app set at compile_path 2013-07-24 23:38:00 +02:00
José Valim b628956310 Update deps docs 2013-07-24 21:22:59 +02:00
José Valim 0fdc15ccdf Merge pull request #1487 from jwarwick/typos
Fixed docs typos in recent commits
2013-07-24 08:52:43 -07:00
John Warwick 514be83b63 Fixed docs typos in recent commits 2013-07-24 11:44:29 -04:00
José Valim 867a3ab4d2 A test case changing compiler options should not be async 2013-07-24 17:32:09 +02:00
José Valim 888155ee08 Do not re-use stacktraces on IEx, closes #1486
This commit also cleans up error normalization throughout Elixir.
2013-07-24 16:43:54 +02:00
José Valim 540d3151b4 Update CHANGELOG 2013-07-23 23:40:10 +02:00
José Valim 089f82205f Clean up IEx helpers to not contain spurious functions 2013-07-23 23:37:37 +02:00
José Valim 9cd6b528c9 Get rid of unmatched docs warnings 2013-07-23 23:37:37 +02:00
José Valim eff2fecf5a binary_size => byte_size 2013-07-23 23:37:37 +02:00
José Valim f944694b2e Rename Protocol.UndefinedError keys 2013-07-23 23:37:37 +02:00
José Valim 36f50af6e0 Give more information on invalid versions 2013-07-23 23:37:37 +02:00
José Valim 155398c445 Warn for unmatched docs 2013-07-23 23:37:36 +02:00
José Valim 73506d2051 Merge pull request #1485 from jwarwick/test_typo
Fixed typos in Mix.Tasks.Test docs
2013-07-23 13:51:08 -07:00
John Warwick 477fdc7053 Fixed typos in Mix.Tasks.Test docs 2013-07-23 16:49:11 -04:00
José Valim 424bbbaf74 Fix the build 2013-07-23 14:07:57 +02:00
José Valim fdb8e8d137 Improve inspect for functions 2013-07-23 13:11:03 +02:00
José Valim c4b656eba6 Do not show internal compiler output in stacktraces 2013-07-23 13:11:03 +02:00
José Valim b6e0e2fdba Merge pull request #1481 from meh/protocol
Split @protocol into @protocol and @for and add __impl__(:for)
2013-07-23 04:10:47 -07:00
meh ba967a0834 Split @protocol into @protocol and @for and add __impl__(:for) 2013-07-23 11:41:08 +02:00
José Valim 587bb9a075 Merge pull request #1477 from jwarwick/genfsm_docs
Updated GenFSM
2013-07-22 23:43:51 -07:00
José Valim 7c789afcb5 Merge pull request #1478 from ericmj/mix-cmd-quotes
Wrap Mix.Shell.cmd with " instead of '
2013-07-22 23:43:36 -07:00
Eric Meadows-Jonsson e922847115 Wrap Mix.Shell.cmd with " instead of '
Single quotes can't be escaped in the shell so commands containing '
will not work. Instead wrap the command in double quotes that can be
escaped and escape " in the command.
2013-07-23 01:02:30 +02:00
John Warwick 407b0519cf Updated GenFSM example code 2013-07-22 17:48:55 -04:00
John Warwick 8f686815b3 Fixed GenFSM example code and typos. 2013-07-22 12:00:04 -04:00
José Valim 214bfbbfdf mix deps.update no longer update all dependencies unless --all is given 2013-07-22 13:22:12 +02:00
José Valim e14d689e0e Normalize windows home, closes #1462 2013-07-22 13:09:13 +02:00
José Valim c294a7f507 Print detailed information on conversion errors 2013-07-22 13:02:01 +02:00
José Valim de7cf1c093 Merge pull request #1476 from meh/string-list
Add String.to_char_list and String.from_char_list
2013-07-22 03:46:09 -07:00
José Valim 989fc155de Check ExUnit.Server is registered before adding cases, closes #1472 2013-07-22 12:45:10 +02:00
meh 69386027ac Add String.to_char_list and String.from_char_list 2013-07-22 12:41:50 +02:00
Eric Meadows-Jonsson 157173d000 Add record type Record.t 2013-07-22 12:38:15 +02:00
José Valim f1e8c05fef Allow coverage tool to be pluggable 2013-07-22 11:08:57 +02:00
José Valim bcf0c26224 Update CHANGELOG 2013-07-22 09:49:29 +02:00
José Valim 48c67c131b Include test time on trace results 2013-07-22 09:41:32 +02:00
José Valim 7f147a2b89 Clean up make clean 2013-07-22 09:11:25 +02:00
José Valim dc7490eb46 Tidy up IEx docs and tests 2013-07-22 08:40:21 +02:00
José Valim 14f8b20084 Merge pull request #1473 from pragdave/iex-cls
Add the clear helper, which cleared the screen. Also introduced the ...
2013-07-21 23:23:20 -07:00
José Valim c816380a4f Merge pull request #1474 from jwarwick/mix_typos
Fixed typos in Mix.Tasks.Compile.Erlang and Mix.Tasks.Test
2013-07-21 23:16:45 -07:00
John Warwick d4e2502c70 Fixed typos in Mix.Tasks.Compile.Erlang and Mix.Tasks.Test 2013-07-21 21:47:04 -04:00
Dave Thomas 02f85d32a9 Forgot the changelog 2013-07-21 17:19:41 -05:00
Dave Thomas fb18f51b95 Add the clear helper, which cleared the screen. Also introduced the convention that if a helper returns dont_display_result/0, then we skip the inspecting of the result. Changed the introspection helpers h, s, and t to use this. 2013-07-21 17:13:21 -05:00
José Valim 0cd1be1439 Update mix test docs 2013-07-22 00:08:24 +02:00
José Valim 37589f99b2 Improve docs for ExUnit 2013-07-21 23:58:56 +02:00
José Valim 4bc2bf3a6d Ensure sigils with no interpolation are fully expanded at compile time, closes #1471 2013-07-21 22:21:04 +02:00
José Valim 1b79a1782c Remove duplicate in erlang, leex and yecc compilers 2013-07-21 22:08:32 +02:00
José Valim 4aab8656d8 Only clean up manifest for files that were actually removed, closes #1440 2013-07-21 18:01:04 +02:00
José Valim 391276762a Merge pull request #1470 from ToJans/master
Updated windows elixir.bat to use -pa instead of -env ERL_LIBS
2013-07-21 02:53:11 -07:00
Tom Janssens ce86541135 Updated windows elixir.bat to use -pa instead of -env ERL_LIBS
Fixes #1469
2013-07-21 11:44:29 +02:00
José Valim 4fcc14041c Merge pull request #1460 from guilleiguaran/string_reverse
Add String.reverse to reverse the given string.
2013-07-21 01:04:58 -07:00
Guillermo Iguaran 0bcf0571d9 Add String.reverse to reverse the given string. 2013-07-21 02:29:15 -05:00
José Valim fffafb84bc Merge pull request #1468 from jwarwick/mix_typos
Fixed doc typos in Mix.Projects and Mix.Utils
2013-07-21 00:17:47 -07:00
John Warwick 7b1f194b8d Fixed doc typos in Mix.Projects and Mix.Utils 2013-07-20 23:46:17 -04:00
José Valim a517a1a72a Merge pull request #1467 from guilleiguaran/enum_shuffle2
Add Enum.shuffle
2013-07-20 13:32:26 -07:00
Guillermo Iguaran 6d03230aaf Add Enum.shuffle
Enum.shuffle returns a list of collection elements shuffled.
2013-07-20 14:46:34 -05:00
José Valim 83d0581370 Merge pull request #1466 from ericmj/mix-git-force-https
Add MIX_GIT_FORCE_HTTPS system environment var
2013-07-20 12:23:01 -07:00
Eric Meadows-Jonsson 89e7fe4039 Add MIX_GIT_FORCE_HTTPS system environment var 2013-07-20 21:11:59 +02:00
Eric Meadows-Jonsson e31294863e Merge pull request #1465 from ericmj/mix-rebar-script
Dont raise on erroneous rebar.config.script
2013-07-20 08:56:09 -07:00
Eric Meadows-Jonsson 3368578316 Dont raise on erroneous rebar.config.script 2013-07-20 17:55:24 +02:00
José Valim 5aab6da4f9 Merge pull request #1464 from ericmj/mix-dep-req-regex
Remove deprecation warn on regex dep requirement
2013-07-20 08:35:08 -07:00
José Valim fecace0a77 Update dyalizer task and fix some of the reports 2013-07-20 17:21:21 +02:00
Eric Meadows-Jonsson c5ca8f31ea Remove deprecation warn on regex dep requirement 2013-07-20 17:04:03 +02:00
José Valim 7ed1f2cbef EEx exprs should receive binaries and use Behaviour 2013-07-20 16:35:32 +02:00
José Valim 40ee57534a Update typespecs for Enum and Inspect.Algebra 2013-07-20 15:20:24 +02:00
José Valim 843343c7ad The manifest variable is already set 2013-07-20 14:11:21 +02:00
José Valim 0ac743e6aa Update CHANGELOG 2013-07-20 13:56:14 +02:00
José Valim 268443edb5 Reverse behaviour docs at compilation time 2013-07-20 13:56:08 +02:00
José Valim b878ec25bb Merge pull request #1458 from guilleiguaran/list_keysort
Add List.keysort
2013-07-20 04:28:13 -07:00
José Valim 90d404334e Merge pull request #1461 from madlep/fix_backwards_behaviour_docs
reverse @__behaviour_docs__
2013-07-20 04:28:03 -07:00
Julian Doherty (madlep) 7ab19565da reverse @__behaviour_docs__
These are top accumlated as module attributes, so are therefore stored
in reverse order to how they were added. When we get them back out,
reverse them again so they're back in the right order.

This is used by ex_doc, and callbacks are listed backwards otherwise
2013-07-20 21:04:20 +10:00
José Valim 7fc787bdcc Improve docs for Mix 2013-07-20 13:01:58 +02:00
José Valim 1f76fb5246 Fix a bug on OptionParser where missing options appeared on the final result 2013-07-20 13:01:44 +02:00
Guillermo Iguaran 4e20aee0d1 Add List.keysort
Receives a list of tuples and sorts the items at position `pos` of the tuples
2013-07-20 01:55:42 -05:00
José Valim 4df054c1ad Record aliases separatedly from remotes 2013-07-19 23:10:03 +02:00
José Valim e32593aa30 Merge branch 'jv-watch-manifest'
This brings smart compilation to mix compile, where
we only recompile files that changed and their dependencies.
2013-07-19 22:55:10 +02:00
José Valim 21d9690faf Simplify translate_apply 2013-07-19 22:54:23 +02:00
José Valim 5da9c83588 Ensure no duplicate keys in the compile.elixir manifest 2013-07-19 22:16:32 +02:00
José Valim 8be887bff7 Also record aliases as dependencies 2013-07-19 22:16:32 +02:00
José Valim 1ff2326755 Just preload files if something is stale 2013-07-19 22:16:32 +02:00
José Valim fa0c9dfc7c Put old beam files back if compilation fails 2013-07-19 22:16:31 +02:00
José Valim 16d3a84637 Improve ManifestCompiler to track more dependencies 2013-07-19 22:16:31 +02:00
José Valim cec35782ef Initial work on ManifestCompiler that tracks dependencies in the manifest for quick compilation 2013-07-19 22:14:04 +02:00
José Valim 2e3c4ee1c9 Support each_module callback on ParallelCompiler 2013-07-19 22:10:30 +02:00
José Valim af454d3f90 Support for multiple callbacks in parallel compiler 2013-07-19 22:10:30 +02:00
José Valim 4eeef37495 Remove deprecations pre 0.10.0 2013-07-19 22:10:30 +02:00
José Valim afbfbab6a6 Watch the target directory on compilation 2013-07-19 22:10:30 +02:00
José Valim c8f1123ee9 Merge pull request #1454 from jwarwick/issue_1448
Issue #1448, Mix.Tasks.Compiler.*.manifests returns complete path
2013-07-19 13:09:05 -07:00
John Warwick 44ddd520fb Issue #1448, Mix.Tasks.Compiler.*.manifests returns complete path 2013-07-19 15:54:50 -04:00
José Valim 3ecc226d7a Merge pull request #1453 from jwarwick/escript_docs
Fixed doc typos in Mix.Tasks.Escriptize
2013-07-19 10:51:31 -07:00
John Warwick 9a51189dbd Fixed doc typos in Mix.Tasks.Escriptize 2013-07-19 13:48:44 -04:00
José Valim 94d3f187ad Slight clean up on mix tests
We don't need to test all scenarios because guaranteeing that
a failure in one compiler is enough to abort the others is enough
for an integration test.
2013-07-19 18:52:31 +02:00
José Valim 2ebd1cf5ea Merge pull request #1449 from jwarwick/issue_1441
Possible fix for Issue #1441
2013-07-19 09:35:24 -07:00
José Valim 0c7d12f0da Document mix loadpaths and app.start options 2013-07-19 16:46:53 +02:00
José Valim 8f4bc95b2d Merge pull request #1452 from meh/mix-version
Move the Elixir version requirement check to a better place
2013-07-19 07:10:08 -07:00
meh e7354614c1 Move the Elixir version requirement check to a better place 2013-07-19 15:57:29 +02:00
José Valim 2a57338f06 Always load paths on app.start, even with --no-compile 2013-07-19 15:17:40 +02:00
John Warwick f8bf2824c8 Updated tests to verify exceptions raised 2013-07-19 08:07:30 -04:00
John Warwick 1c914621a4 Upated test cases to support compilation halting on first failed compiler. Updated existing tests to handle CompileError being thrown on failure.
Updated compile tasks to raise exceptions after compiling all files
2013-07-19 08:06:34 -04:00
José Valim c78e47a42a Merge pull request #1450 from meh/mix-elixir
Make Mix.SystemVersionError message more meaningful
2013-07-18 22:39:53 -07:00
meh 233319df89 Make Mix.SystemVersionError message more meaningful 2013-07-19 07:38:47 +02:00
José Valim 669a8867bd Merge pull request #1447 from jwarwick/compiler_typos
Fixed typos in Kernel.ParallelCompiler docs
2013-07-17 23:03:20 -07:00
John Warwick 11c2854a85 Fixed typos in Kernel.ParallelCompiler docs 2013-07-17 23:17:43 -04:00
José Valim 40b8e8b4ac Merge pull request #1444 from jwarwick/doc_typos
Fixed doc typos in Mix.deps and IO
2013-07-17 06:08:47 -07:00
John Warwick 229b1919d3 Fixed doc typos and formatting in IO 2013-07-17 07:53:06 -04:00
John Warwick 83ff73806c Fixed minor typo in Mix.deps doc 2013-07-17 07:43:37 -04:00
José Valim 01e2e974b8 Reduce cases where IEx parser can get stuck, closes #1443 2013-07-17 13:02:11 +02:00
José Valim 498e8038fc Only delete beam files with no source 2013-07-17 11:58:46 +02:00
José Valim 79222a45d1 Merge pull request #1435 from ericmj/mix-force-path-deps
Check mix path deps to see if we need recompile project
2013-07-17 02:18:21 -07:00
Eric Meadows-Jonsson 6e3a64a1d0 Check path deps to see if we should recompile
'mix compile' will check the manifest files' mtime of all mix path
dependencies - if they are newer then the local .compile.elixir file
we need to recompile.
2013-07-17 11:14:15 +02:00
José Valim 37992017ea Improve docs for Mix.Deps 2013-07-17 10:01:06 +02:00
José Valim 267a68c399 Remove test at the wrong location 2013-07-17 09:05:20 +02:00
José Valim a46b78a353 Ensure __impl__ follows the same convention as other dynamically generated info functions 2013-07-17 09:00:27 +02:00
José Valim e93dfe6de2 Merge pull request #1439 from meh/protocol
Add @protocol attribute to defimpl
2013-07-16 23:50:28 -07:00
meh 6031c27aa1 Add @protocol attribute to defimpl 2013-07-17 08:42:48 +02:00
José Valim 83ac67565b Merge pull request #1438 from jwarwick/mix_tasks
Fixed doc typos in Mix.Tasks.Compile.*
2013-07-16 23:11:55 -07:00
John Warwick 660f7d0dd7 Fixed doc typos in Mix.Tasks.Compile.* 2013-07-16 22:19:09 -04:00
John Warwick 6160fd2669 Fixed typos and formatting issues in Mix.Tasks.CompileElixir 2013-07-16 16:10:23 -04:00
José Valim 2c457d47f2 No need to define a new compilation error 2013-07-16 15:43:06 +02:00
José Valim 121d16dc87 Update CHANGELOG 2013-07-16 12:57:22 +02:00
José Valim 6034738d48 Document that on_definition can only be a function, closes #1431 2013-07-16 12:56:29 +02:00
José Valim 740b724f07 Merge pull request #1433 from ericmj/mix-deps-check
Rename mix flag --no-check -> --no-deps-check, document it and use it everywhere
2013-07-16 03:55:30 -07:00
Eric Meadows-Jonsson 9304aae28a If no deps.check is done use all available deps
Add all available deps to loadpaths even if they didn't pass the deps
check.
2013-07-16 12:41:26 +02:00
Eric Meadows-Jonsson a26a55876d Rename mix flag --no-check -> --no-deps-check
Document it and check the flag everytime another task call deps.check.
Do a deps.check after deps.update.
2013-07-16 12:39:54 +02:00
Eric Meadows-Jonsson b45620d85d Remove hard-coded project defaults 2013-07-16 12:37:38 +02:00
José Valim 9c86fb8df1 Update CHANGELOG 2013-07-16 11:56:41 +02:00
José Valim 0d5d955ada Clean up GenFSM docs 2013-07-16 11:53:52 +02:00
Torben Hoffmann cfd546b42b Created 1st version of GenFsm 2013-07-16 11:53:51 +02:00
José Valim 716890be3c Merge pull request #1422 from ericmj/mix-manifest
Add manifests for yecc, leex and erlang compilers
2013-07-16 02:45:17 -07:00
José Valim 55ca2c199e Merge pull request #1425 from tonini/mix-help-default-task-description
Mix.Tasks.Help.run([]) outputs information about current default task
2013-07-16 02:42:56 -07:00
José Valim 095ac686cc Add expansion callsite to the end of the stacktrace, closes #1426 2013-07-16 11:40:18 +02:00
José Valim 67e9cb5b05 Improve use of operator has no effect messages, closes #1431 2013-07-16 11:23:55 +02:00
José Valim 8d3c78bd40 Merge pull request #1427 from jwarwick/doc_typos
Fixed doc typos in Kernel.Special_Forms
2013-07-16 02:04:45 -07:00
José Valim 0675ecbbe4 Merge pull request #1429 from jwarwick/ex_unit_docs
Fixed doc typos in ExUnit
2013-07-16 02:04:19 -07:00
José Valim 392697d64b Remove unused test_helper project configuration from mix 2013-07-16 11:00:36 +02:00
John Warwick 3f8a8c547e Fixed docs as recommended by @alco 2013-07-15 17:15:36 -04:00
John Warwick 9f8601d5ac Fixed doc typos in ExUnit.DocTest 2013-07-15 14:58:57 -04:00
John Warwick c29dfb01ed Fixed doc typos in ExUnit.Assertions 2013-07-15 14:53:20 -04:00
John Warwick e3ea93f8de Fixed doc typos in ExUnit 2013-07-15 14:38:48 -04:00
John Warwick 888d7c285b Fixed doc typos in Kernel.Special_Forms 2013-07-15 11:54:29 -04:00
José Valim dbf1e25e15 Bump to 0.10.1-dev 2013-07-15 11:34:00 +02:00
Samuel Tonini d88138a8af Mix.Tasks.Help.run([]) outputs information about current default task 2013-07-15 10:45:47 +02:00
Eric Meadows-Jonsson 7ec5ec80b8 Use all compilers manifests when cleaning
A compiler can expose a `manifest/0` function providing the filename of
its manifest filename.
A manifest file should contain a list of files that was generated by the
compiler. They should be absolute paths relative to the project root and
be separated by newlines.
2013-07-13 11:27:48 +02:00
Eric Meadows-Jonsson 071988e8b1 Check .compile.erlang mtime when compiling elixir
Consider erlang files when checking if elixir files needs to be
recompiled.
2013-07-12 17:09:25 +02:00
Eric Meadows-Jonsson e820fb27b1 Add manifest files to erlang compiler
Makes sure that the correct generated files are deleted and
cleaned even when the source files are moved or renamed.
2013-07-12 17:04:04 +02:00
Eric Meadows-Jonsson f4b329a796 Add manifest files to yecc and leex compilers
Makes sure that the correct generated files are deleted and cleaned
even when the source files are moved or renamed.
2013-07-12 17:04:04 +02:00
288 changed files with 16812 additions and 8930 deletions
+240 -45
View File
@@ -1,6 +1,201 @@
# 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 `Enum.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
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
* [Enum] Support negative indices in `Enum.at/fetch/fetch!`
* [ExUnit] Show failures on CLIFormatter as soon as they pop up
* [IEx] Allow for strings in `h` helper
* [IEx] Helpers `r` and `c` can handle erlang sources
* [Integer] Add `odd?/1` and `even?/1`
* [IO] Added support to specifying a number of bytes to stream to `IO.stream`, `IO.binstream`, `File.stream!` and `File.binstream!`
* [Kernel] Include file and line on error report for overriding an existing function/macro
* [Kernel] Convert external functions into quoted expressions. This allows record fields to contain functions as long as they point to an `&Mod.fun/arity`
* [Kernel] Allow `foo?` and `bar!` as valid variable names
* [List] Add `List.replace_at/3`
* [Macro] Improve printing of the access protocol on `Macro.to_string/1`
* [Macro] Add `Macro.to_string/2` to support annotations on the converted string
* [Mix] Automatically recompile a project if the Elixir version changes
* [Path] Add `Path.relative_to_cwd/2`
* [Regex] Allow erlang `re` options when compiling Elixir regexes
* [Stream] Add `Stream.concat/1`, `Stream.concat/2` and `Stream.flat_map/2`
* [String] Add regex pattern support to `String.replace/3`
* [String] Add `String.ljust/2`, `String.rjust/2`, `String.ljust/3` and `String.rjust/3`
* [URI] `URI.parse/1` supports IPv6 addresses
* 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
* [Kernel] Limit `inspect` results to 50 items by default to avoid printing too much data
* [Kernel] Return a readable error on oversized atoms
* [Kernel] Allow functions ending with `?` or `!` to be captured
* [Kernel] Fix default shutdown of child supervisors to `:infinity`
* [Kernel] Fix regression when calling a function/macro ending with bang, followed by `do/end` blocks
* [List] Fix bug on `List.insert_at/3` that added the item at the wrong position for negative indexes
* [Macro] `Macro.escape/2` can now escape improper lists
* [Mix] Fix `Mix.Version` matching on pre-release info
* [Mix] Ensure `watch_exts` trigger full recompilation on change with `mix compile`
* [Mix] Fix regression on `mix clean --all`
* [String] `String.strip/2` now supports removing unicode characters
* [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
* [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
* [IO] `IO.stream(device)` and `IO.binstream(device)` are deprecated in favor of `IO.stream(device, :line)` and `IO.binstream(device, :line)`
* [Kernel] `list_to_binary/1`, `binary_to_list/1` and `binary_to_list/3` are deprecated in favor of `String.from_char_list!/1` and `String.to_char_list!/1` for characters and `:binary.list_to_bin/1`, `:binary.bin_to_list/1` and `:binary.bin_to_list/3` for bytes
* [Kernel] `to_binary/1` is deprecated in favor of `to_string/1`
* [Kernel] Deprecate `def/4` and friends in favor of `def/2` with unquote and friends
* [Kernel] Deprecate `%b` and `%B` in favor of `%s` and `%S`
* [List] `List.concat/2` is deprecated in favor of `Enum.concat/2`
* [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
* [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`
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
# v0.10.1 (2013-08-03)
* Enhancements
* [Behaviour] Add support for `defmacrocallback/1`
* [Enum] Add `Enum.shuffle/1`
* [ExUnit] The `:trace` option now also reports run time for each test
* [ExUnit] Add support for `:color` to enable/disable ANSI coloring
* [IEx] Add the `clear` helper to clear the screen.
* [Kernel] Add the capture operator `&`
* [Kernel] Add support for `GenFSM.Behaviour`
* [Kernel] Functions now points to the module and function they were defined when inspected
* [Kernel] A documentation attached to a function that is never defined now prints warnings
* [List] Add `List.keysort/2`
* [Mix] `:test_helper` project configuration did not affect `mix test` and was therefore removed. A `test/test_helper.exs` file is still necessary albeit it doesn't need to be automatically required in each test file
* [Mix] Add manifests for yecc, leex and Erlang compilers, making it easier to detect dependencies in between compilers and providing a more useful clean behaviour
* [Mix] `mix help` now outputs information about the default mix task
* [Mix] Add `--no-deps-check` option to `mix run`, `mix compile` and friends to not check dependency status
* [Mix] Add support for `MIX_GIT_FORCE_HTTPS` system environment that forces HTTPS for known providers, useful when the regular git port is blocked. This configuration does not affect the `mix.lock` results
* [Mix] Allow coverage tool to be pluggable via the `:test_coverage` configuration
* [Mix] Add `mix cmd` as a convenience to run a command recursively in child apps in an umbrella application
* [Mix] Support `umbrella: true` in dependencies as a convenience for setting up umbrella path deps
* [Mix] `mix run` now behaves closer to the `elixir` command and properly mangles the ARGV
* [String] Add `Regex.scan/3` now supports capturing groups
* [String] Add `String.reverse/1`
* 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
* [Kernel] Improve error messages when the use of an operator has no effect
* [Kernel] Fix a bug where warnings were not being generated when imported macros conflicted with local functions or macros
* [Kernel] Document that `on_definition` can only be a function as it is evaluated inside the function context
* [Kernel] Ensure `%w` sigils with no interpolation are fully expanded at compile time
* [Mix] `mix deps.update`, `mix deps.clean` and `mix deps.unlock` no longer change all dependencies unless `--all` is given
* [Mix] Always run ` mix loadpaths` on `mix app.start`, even if `--no-compile` is given
* [OptionParser] Do not add boolean flags to the end result if they were not given
* [OptionParser] Do not parse non-boolean flags as booleans when true or false are given
* [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
* [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
* [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
@@ -13,7 +208,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
@@ -22,7 +217,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`
@@ -31,7 +226,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:
@@ -44,7 +239,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
@@ -58,7 +253,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
@@ -67,12 +262,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
@@ -82,7 +277,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
@@ -90,7 +285,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
@@ -98,21 +293,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
@@ -123,12 +318,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
@@ -136,7 +331,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`
@@ -159,7 +354,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
@@ -171,7 +366,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`
@@ -180,7 +375,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`
@@ -188,7 +383,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
@@ -201,7 +396,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
@@ -209,11 +404,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
@@ -228,7 +423,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
@@ -241,13 +436,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
@@ -255,7 +450,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
@@ -278,16 +473,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 `|>`
@@ -297,7 +492,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
@@ -310,12 +505,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
@@ -323,7 +518,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`
@@ -341,20 +536,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
@@ -380,7 +575,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
@@ -388,12 +583,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`
@@ -403,14 +598,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`
@@ -422,7 +617,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
+78 -11
View File
@@ -5,13 +5,18 @@ process easy and effective for everyone involved!
## Using the issue tracker
The issue tracker is the preferred channel for [bug reports](#bugs-reports),
[features requests](#feature-requests) and [submitting pull
requests](#pull-requests), but please **do not** use the issue tracker for
personal support requests. Instead, use
[the mailing list](http://groups.google.com/group/elixir-lang-talk),
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
Use the issues tracker for:
* [bug reports](#bugs-reports)
* [submitting pull requests](#pull-requests)
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
* [#elixir-lang](irc://chat.freenode.net/elixir-lang)
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
We do our best to keep the issues tracker tidy and organized, making it useful
for everyone. For example, we classify open issues per application and perceived
@@ -60,10 +65,10 @@ Example:
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible.
Feature requests are welcome and should be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). But take a moment to find
out whether your idea fits with the scope and aims of the project. It's up to *you*
to make a strong case to convince the project's developers of the merits of this
feature. Please provide as much detail and context as possible.
## Contributing
@@ -86,9 +91,71 @@ You can run all tests in the root directory with `make test` and you can
also run tests for a specific framework `make test_#{NAME}`, for example,
`make test_ex_unit`.
From time to time, your tests may fail in an existing Elixir checkout and
may require a clean start by running `make clean compile`. You can always
check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
With tests running and passing, you are ready to contribute to Elixir and
send your pull requests.
## Contributing Documentation
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
the first paragraph is considered to be a short summary.
For functions, macros and callbacks say what it will do. For example write
something like:
```elixir
@doc """
Return only those elements for which `fun` is true.
...
"""
def filter(collection, fun) ...
```
For modules, records, protocols and types say what it is. For example write
something like:
```elixir
defrecord File.Stat, [...] do
@moduledoc """
Information about a file.
...
"""
end
```
Keep in mind that the first paragraph might show up in a summary somewhere, long
texts in the first paragraph create very ugly summaries. As a rule of thumb
anything longer than 80 characters is too long.
Try to keep unneccesary details out of the first paragraph, it's only there to
give a user a quick idea of what the documented "thing" does/is. The rest of the
documentation string can contain the details, for example when a value and when
`nil` is returned.
If possible include examples, preferably in a form that works with doctests. For
example:
```elixir
@doc """
Return only those elements for which `fun` is true.
## Examples
iex> Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
[2]
"""
def filter(collection, fun) ...
```
This makes it easy to test the examples so that they don't go stale and examples
are often a great help in explaining what a function does.
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
-2
View File
@@ -5,6 +5,4 @@ All the files in this distribution are covered under either Elixir's
license (see the file LICENSE) except the files mentioned below that
contains sections that are under Erlang's License (EPL):
lib/elixir/src/elixir_glob.erl
lib/elixir/src/elixir_parser.erl (generated by build scripts)
+14 -16
View File
@@ -1,5 +1,5 @@
REBAR := "$(shell echo `pwd`/rebar)"
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
VERSION := $(strip $(shell cat VERSION))
@@ -11,7 +11,6 @@ INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
.NOTPARALLEL: compile
@@ -20,11 +19,12 @@ INSTALL_PROGRAM = $(INSTALL) -m755
define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
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)
@@ -66,10 +66,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/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/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/priv/unicode.ex -o lib/elixir/ebin --no-debug-info;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
@@ -90,16 +90,14 @@ install: compile
done
clean:
$(Q) cd lib/elixir && $(REBAR) clean
cd lib/elixir && $(REBAR) clean
rm -rf ebin
rm -rf lib/*/ebin
rm -rf lib/*/test/tmp
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/*/tmp
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/mix/test/fixtures/deps_on_git_repo
rm -rf lib/mix/test/fixtures/git_rebar
rm -rf lib/elixir/src/elixir.app.src
rm -rf lib/elixir/src/*_lexer.erl
rm -rf lib/elixir/src/*_parser.erl
rm -rf lib/elixir/test/ebin
clean_exbeam:
$(Q) rm -f lib/*/ebin/Elixir.*.beam
@@ -124,7 +122,7 @@ release_zip: compile
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
release_docs: docs
cd ../elixir-lang.github.com && git checkout master
cd ../elixir-lang.github.com
rm -rf ../elixir-lang.github.com/docs/master
mv docs ../elixir-lang.github.com/docs/master
@@ -141,7 +139,7 @@ test_erlang: compile
$(Q) mkdir -p lib/elixir/test/ebin
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo
@ echo ""
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
@@ -155,12 +153,12 @@ test_kernel: compile
.dialyzer.base_plt:
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler syntax_tools inets crypto ssl
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
dialyze: .dialyzer.base_plt
$(Q) rm -f .dialyzer_plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
@ echo "==> Dialyzing Elixir..."
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
+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]
+6 -4
View File
@@ -8,9 +8,9 @@ This document simply outlines the release process:
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
4) Commit changes above and update stable branch
4) Commit changes above with title "Release vVERSION"
5) Create tag from master branch
5) Push master and create tag from master branch
6) Release new docs with `make release_docs`, move docs to `docs/stable`
@@ -18,9 +18,11 @@ This document simply outlines the release process:
8) Push package to expm with `expm publish package.exs`
9) After release, bump versions and add `-dev` back
9) Merge master into stable branch and push it
10) `make release_docs` once again and push `elixir-lang.github.com`
10) After release, bump versions and add `-dev` back
11) `make release_docs` once again and push `elixir-lang.github.com`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
0.10.0
0.11.0
+1 -3
View File
@@ -1,5 +1,5 @@
#!/bin/sh
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version and exit
@@ -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" ]
+32 -63
View File
@@ -1,5 +1,4 @@
@echo off
SETLOCAL enabledelayedexpansion
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc%== 0 (
@@ -13,7 +12,7 @@ echo.
echo -v Prints version and exit
echo -e command Evaluates the given command (*)
echo -r file Requires the given files/patterns (*)
echo -S script   Finds and executes the given script (*)
echo -S script Finds and executes the given script (*)
echo -pr file Requires the given files/patterns in parallel (*)
echo -pa path Prepends the given path to Erlang code path (*)
echo -pz path Appends the given path to Erlang code path (*)
@@ -51,69 +50,39 @@ set par="%1"
shift
if "%par%"=="" (
rem if no parameters defined
goto :run
goto :expand_erl_libs
)
if "%par%"=="""" (
rem if no parameters defined - special case for parameter that is already quoted
goto :expand_erl_libs
)
rem ******* ERLANG PARAMETERS **********************
for /f "usebackq" %%m in (`echo %par%^|findstr \--detached`) do (
set parsErlang=%parsErlang% -detached
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--hidden`) do (
set parsErlang=%parsErlang% -hidden
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--cookie`) do (
set parsErlang=%parsErlang% -setcookie %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--sname`) do (
set parsErlang=%parsErlang% -sname %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--name`) do (
set parsErlang=%parsErlang% -name %1
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--erl`) do (
set beforeExtra=%beforeExtra% %~1
shift
goto:startloop
)
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
rem ******* elixir parameters **********************
for /f "usebackq" %%m in (`echo %par%^|findstr \--v`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--compile`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--no-halt`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \+iex`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \+compile`) do (
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \-[er]`) do (
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \-p[raz]`) do (
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--app`) do (
shift
goto:startloop
)
for /f "usebackq" %%m in (`echo %par%^|findstr \--remsh`) do (
shift
goto:startloop
)
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
IF NOT "%par%"=="%par:-r=%" (shift)
IF NOT "%par%"=="%par:-pr=%" (shift)
IF NOT "%par%"=="%par:-pa=%" (shift)
IF NOT "%par%"=="%par:-pz=%" (shift)
IF NOT "%par%"=="%par:--app=%" (shift)
IF NOT "%par%"=="%par:--remsh=%" (shift)
goto:startloop
rem ******* assume all pre-params are parsed ********************
:expand_erl_libs
rem ******* expand all ebin paths as Windows does not support the ..\*\ebin wildcard ********************
SETLOCAL enabledelayedexpansion
set ext_libs=
for /d %%d in ("%originPath%..\lib\*.") do (
set ext_libs=!ext_libs! -pa "%%~fd\ebin"
)
SETLOCAL disabledelayedexpansion
:run
erl -env ERL_LIBS %ERL_LIBS%;"%originPath%\..\lib" -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
+2 -1
View File
@@ -1,5 +1,5 @@
#!/bin/sh
if [ $# -eq 0 ]; then
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
-o The directory to output compiled files
@@ -7,6 +7,7 @@ if [ $# -eq 0 ]; then
--no-debug-info Do not attach debug info to compiled modules
--ignore-module-conflict
--warnings-as-errors Treat warnings as errors and return non-zero exit code
--verbose Print informational messages.
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
+1
View File
@@ -14,6 +14,7 @@ echo --no-docs Do not attach documentation to compiled modules
echo --no-debug-info Do not attach debug info to compiled modules
echo --ignore-module-conflict
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
echo --verbose Print informational messages.
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
+1 -1
View File
@@ -1,5 +1,5 @@
#!/bin/sh
if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version
+1 -1
View File
@@ -1,3 +1,3 @@
#!/usr/bin/env elixir
Mix.start
Mix.CLI.run
Mix.CLI.main
+19 -21
View File
@@ -1,7 +1,7 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx do
@moduledoc %B"""
@moduledoc %S"""
EEx stands for Embedded Elixir. It allows you to embed
Elixir code inside a string in a robust way:
@@ -93,11 +93,15 @@ defmodule EEx do
"""
defmacro function_from_string(kind, name, source, args // [], options // []) do
info = [file: __CALLER__.file, line: __CALLER__.line + 1]
quote do
info = Keyword.merge unquote(info), unquote(options)
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
unquote(args), EEx.compile_string(unquote(source), info), info)
quote bind_quoted: binding do
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
compiled = EEx.compile_string(source, info)
case kind do
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
end
end
end
@@ -124,14 +128,17 @@ defmodule EEx do
Sample.sample(1, 2) #=> "3"
"""
defmacro function_from_file(kind, name, filename, args // [], options // []) do
quote do
file = unquote(filename)
info = Keyword.merge unquote(options), [file: file, line: 1]
defmacro function_from_file(kind, name, file, args // [], options // []) do
quote bind_quoted: binding do
info = Keyword.merge options, [file: file, line: 1]
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
compiled = EEx.compile_file(file, info)
@file file
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
unquote(args), EEx.compile_file(file, info), info)
case kind do
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
end
end
end
@@ -187,15 +194,6 @@ defmodule EEx do
### Helpers
@doc false
def function_from_quoted(module, kind, name, args, source, info) do
args = Enum.map args, fn arg -> { arg, [], nil } end
quote = quote do
unquote(kind)(unquote(name)(unquote_splicing(args)), do: unquote(source))
end
Module.eval_quoted module, quote, [], info
end
defp do_eval(compiled, bindings, options) do
{ result, _ } = Code.eval_quoted(compiled, bindings, options)
result
+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
+9 -8
View File
@@ -1,5 +1,5 @@
defmodule EEx.Engine do
@moduledoc %B"""
@moduledoc %S"""
This is the basic EEx engine that ships with Elixir.
An engine needs to implement two functions:
@@ -20,6 +20,11 @@ defmodule EEx.Engine do
implemented by default by this engine.
"""
use Behaviour
defcallback handle_text(Macro.t, binary) :: Macro.t
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
@doc """
The default implementation simply concatenates text to the buffer.
"""
@@ -35,22 +40,18 @@ defmodule EEx.Engine do
All other markers are not implemented by this engine.
"""
def handle_expr(buffer, '=', expr) do
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> to_binary(unquote(expr))
tmp <> to_string(unquote(expr))
end
end
def handle_expr(buffer, '', expr) do
def handle_expr(buffer, "", expr) do
quote do
tmp = unquote(buffer)
unquote(expr)
tmp
end
end
def behaviour_info(:callbacks) do
[handle_text: 2, handle_expr: 3]
end
end
+16 -1
View File
@@ -67,6 +67,21 @@ defmodule EEx.AssignsEngine do
a template, after compiled, may receive different assigns
and the developer don't want to recompile it for each
variable set.
Assigns can also be used when compiled to a function:
# sample.eex
<%= @a + @b %>
# sample.ex
defmodule Sample do
require EEx
EEx.function_from_file :def, :sample, "sample.eex", [:assigns]
end
# iex
Sample.sample(a: 1, b: 2) #=> "3"
"""
@doc false
@@ -91,7 +106,7 @@ defmodule EEx.SmartEngine do
@moduledoc """
An engine meant for end-user usage that includes
`AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
+8 -8
View File
@@ -12,7 +12,7 @@ defmodule EEx.Tokenizer do
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(:unicode.characters_to_list(bin), line)
tokenize(String.to_char_list!(bin), line)
end
def tokenize(list, line) do
@@ -54,11 +54,11 @@ defmodule EEx.Tokenizer do
# Retrieve marker for <%
defp retrieve_marker('=' ++ t) do
{ '=', t }
{ "=", t }
end
defp retrieve_marker(t) do
{ '', t }
{ "", t }
end
# Tokenize an expression until we find %>
@@ -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)
@@ -128,15 +128,15 @@ defmodule EEx.Tokenizer do
end
defp fn_index(tokens) do
Enum.find_index(tokens, function do
Enum.find_index tokens, fn
{ :fn_paren, _ } -> true
{ :fn, _ } -> true
_ -> false
end)
end
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
@@ -154,6 +154,6 @@ defmodule EEx.Tokenizer do
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, :unicode.characters_to_binary(Enum.reverse(buffer)) } | acc]
[{ :text, line, String.from_char_list!(Enum.reverse(buffer)) } | acc]
end
end
+14 -14
View File
@@ -13,15 +13,15 @@ defmodule EEx.TokenizerTest do
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, [], ' bar ' } ]
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "", ' bar ' } ]
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, '=', ' bar ' } ]
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "=", ' bar ' } ]
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, '=', ' bar ' } ]
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, "=", ' bar ' } ]
end
test "strings with more than one line and expression with more than one line" do
@@ -34,9 +34,9 @@ baz %>
assert T.tokenize(string, 1) == [
{:text, 1, "foo "},
{:expr, 1, '=', ' bar\n\nbaz '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, 3, "\n"},
{:expr, 4, [], ' foo '},
{:expr, 4, "", ' foo '},
{:text, 4, "\n"}
]
end
@@ -68,32 +68,32 @@ baz %>
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' if true do ' },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 1, "bar" },
{ :end_expr, 1, '', ' end ' }
{ :end_expr, 1, "", ' end ' }
]
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' cond do ' },
{ :middle_expr, 1, '', ' false -> ' },
{ :start_expr, 1, "", ' cond do ' },
{ :middle_expr, 1, "", ' false -> ' },
{ :text, 1, "bar" },
{ :middle_expr, 1, '', ' true -> ' },
{ :middle_expr, 1, "", ' true -> ' },
{ :text, 1, "baz" },
{ :end_expr, 1, '', ' end ' }
{ :end_expr, 1, "", ' end ' }
]
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' if true do ' },
{ :start_expr, 1, "", ' if true do ' },
{ :text, 1, "bar" },
{ :middle_expr, 1, '', ' else ' },
{ :middle_expr, 1, "", ' else ' },
{ :text, 1, "baz" },
{ :end_expr, 1, '', ' end ' }
{ :end_expr, 1, "", ' end ' }
]
end
+17 -17
View File
@@ -25,12 +25,6 @@ defmodule EExText.Compiled do
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
@file __ENV__
def other do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
defp fill_in_stacktrace do
try do
:erlang.error "failed"
@@ -270,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 = """
• <%= "•" %> •
@@ -312,7 +321,7 @@ foo
{ EExText.Compiled,
:before_compile,
0,
[file: binary_to_list(__FILE__), line: 7]
[file: to_char_list(__FILE__), line: 7]
}
}
@@ -321,7 +330,7 @@ foo
{ EExText.Compiled,
:after_compile,
0,
[file: binary_to_list(__FILE__), line: 18]
[file: to_char_list(__FILE__), line: 18]
}
}
@@ -333,15 +342,6 @@ foo
[file: 'unknown', line: 24]
}
}
assert EExText.Compiled.other ==
{ 31,
{ EExText.Compiled,
:other,
0,
[file: binary_to_list(__FILE__), line: 30]
}
}
end
defp assert_eval(expected, actual) do
+12 -3
View File
@@ -6,17 +6,17 @@
-define(atom_concat(Atoms), list_to_atom(lists:concat(Atoms))).
-define(elixir_macro(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
-define(line(Opts), elixir_tree_helpers:get_line(Opts)).
-define(line(Opts), elixir_utils:get_line(Opts)).
-record(elixir_scope, {
context=nil, %% can be assign, guards or nil
extra=nil, %% extra information about the context, like fn_match for fns
noname=false, %% when true, don't add new names (used by try)
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
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
@@ -25,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
@@ -35,7 +37,7 @@
}).
-record(elixir_quote, {
line=0,
line=nil,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
@@ -45,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
+45 -19
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__)
@@ -53,15 +54,36 @@ defmodule Behaviour do
do_defcallback(fun, quote(do: term), __CALLER__)
end
defp do_defcallback(fun, return, caller) do
@doc """
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return] }) do
do_defmacrocallback(fun, return, __CALLER__)
end
defmacro defmacrocallback(fun) do
do_defmacrocallback(fun, quote(do: Macro.t), __CALLER__)
end
defp do_defcallback(fun, spec, caller) do
case Macro.extract_args(fun) do
{ name, args } -> :ok
{ name, args } ->
do_callback(:def, name, args, name, length(args), spec, caller)
:error ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
end
end
arity = length(args)
defp do_defmacrocallback(fun, spec, caller) do
case Macro.extract_args(fun) do
{ name, args } ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args], name, length(args), spec, caller)
:error ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
end
end
defp do_callback(kind, name, args, docs_name, docs_arity, return, caller) do
Enum.each args, fn
{ :::, _, [left, right] } ->
ensure_not_default(left)
@@ -74,7 +96,7 @@ defmodule Behaviour do
quote do
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(name), unquote(arity)
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(kind), unquote(docs_name), unquote(docs_arity)
end
end
@@ -85,23 +107,27 @@ defmodule Behaviour do
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, name, arity) do
def store_docs(module, line, kind, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :__behaviour_docs__, { { name, arity }, line, doc }
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
end
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :__behaviour_docs__, accumulate: true)
Module.register_attribute(__MODULE__, :behaviour_docs, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
end
end
@doc false
defmacro __before_compile__(_) do
defmacro __before_compile__(env) do
docs = if Code.compiler_options[:docs] do
Enum.reverse Module.get_attribute(env.module, :behaviour_docs)
end
quote do
@doc false
def __behaviour__(:callbacks) do
@@ -109,7 +135,7 @@ defmodule Behaviour do
end
def __behaviour__(:docs) do
@__behaviour_docs__
unquote(Macro.escape(docs))
end
end
end
-84
View File
@@ -1,84 +0,0 @@
import Kernel, except: [to_binary: 1]
defprotocol Binary.Chars do
@moduledoc %B"""
The Binary.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
`to_binary` which does the conversion.
The `to_binary` function automatically imported
by Kernel invokes this protocol. String
interpolation also invokes to_binary in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_binary(bar)`.
"""
@only [BitString, List, Number, Atom, Record]
def to_binary(thing)
end
defimpl Binary.Chars, for: Atom do
@doc """
Convert the atom literally to a binary, except
`nil` which is converted to an empty string.
"""
def to_binary(nil) do
""
end
def to_binary(atom) do
atom_to_binary(atom, :utf8)
end
end
defimpl Binary.Chars, for: BitString do
@doc """
Simply returns the binary itself.
"""
def to_binary(thing) when is_binary(thing) do
thing
end
end
defimpl Binary.Chars, for: List do
@doc """
Consider the list is an iolist and converts it
to a binary. This allows a list of binaries, or
a charlist, or a mix of both, to be converted
successfully.
## Examples
iex> to_binary('foo')
"foo"
iex> to_binary(["foo", 'bar'])
"foobar"
"""
def to_binary(thing) do
try do
iolist_to_binary(thing)
rescue
ArgumentError ->
raise Protocol.UndefinedError,
protocol: __MODULE__,
structure: thing,
extra: "Only iolists are supported"
end
end
end
defimpl Binary.Chars, for: Number do
@doc """
Simply converts the number (integer or a float) to a binary.
"""
def to_binary(thing) when is_integer(thing) do
integer_to_binary(thing)
end
def to_binary(thing) do
list_to_binary(:io_lib_format.fwrite_g(thing))
end
end
+129 -100
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 the 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,33 +32,39 @@ defmodule Code do
end
@doc """
Appends a path to Erlang VM code path.
The path is expanded with `Path.expand` before being appended.
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
:code.add_pathz(Path.expand to_char_list(path))
end
@doc """
Prepends a path to Erlang VM code path.
The path is expanded with `Path.expand` before being prepended.
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
:code.add_patha(Path.expand to_char_list(path))
end
@doc """
Deletes a path from Erlang VM code path.
The path is expanded with `Path.expand` before being deleted.
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
:code.del_path(Path.expand to_char_list(path))
end
@doc """
Evaluates the contents given by string. The second argument is the
binding (which should be a keyword), 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
@@ -71,28 +77,42 @@ defmodule Code do
* `:requires` - a list of modules required
* `:functions` - a list of tuples where the first element is a module
and the second a list of imported function names and arity. The list
of function names and arity must be sorted;
of function names and arity must be sorted
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity. The list
of function names and arity must be sorted;
of function names and arity must be sorted
Notice that setting any of the values above overrides Elixir default
Notice that setting any of the values above overrides Elixir's default
values. For example, setting `:requires` to `[]`, will no longer
automatically require the `Kernel` module; in the same way setting
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
etc.
Returns a tuple of the form `{ value, binding }`,
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
iex> Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{ 3, [ {:a, 1}, {:b, 2} ] }
{3, [a: 1, b: 2]}
For convenience, you can pass `__ENV__` as an argument and
all imports, requires and aliases will be automatically carried
over:
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])
{3, [a: 3, b: 2]}
For convenience, you can pass `__ENV__` as the `opts` argument and
all imports, requires and aliases defined in the current environment
will be automatically carried over:
iex> Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
{ 3, [ {:a, 1}, {:b, 2} ] }
{3, [a: 1, b: 2]}
"""
def eval_string(string, binding // [], opts // [])
@@ -107,29 +127,27 @@ defmodule Code do
end
defp do_eval_string(string, binding, opts) when is_list(binding) do
{ value, binding, _scope } =
:elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding, _scope } = :elixir.eval to_char_list(string), binding, opts
{ value, binding }
end
@doc """
Evaluates the quoted contents.
Evaluate the quoted contents.
This function accepts a list of environment options.
Check `Code.eval_string` for more information.
See `eval_string/3` for a description of arguments and return values.
## Examples
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{ 3, [ {:a, 1}, {:b, 2} ] }
{3, [a: 1, b: 2]}
For convenience, you can pass `__ENV__` as an argument and
all options will be automatically extracted from the environment:
For convenience, you can pass `__ENV__` as the `opts` argument and
all options will be automatically extracted from the current environment:
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
{ 3, [ {:a, 1}, {:b, 2} ] }
{3, [a: 1, b: 2]}
"""
def eval_quoted(quoted, binding // [], opts // [])
@@ -157,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
@@ -170,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
@@ -182,40 +200,36 @@ 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 false
def string_to_ast(string, opts // []) do
IO.write "[WARNING] Code.string_to_ast is deprecated, please use Code.string_to_quoted instead\n#{Exception.format_stacktrace}"
string_to_quoted(string, opts)
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
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
and the file reported in the `__ENV__` variable.
* `:line` - The line reported in the __ENV__ variable.
* `:line` - The line reported in the `__ENV__` variable.
* `:existing_atoms_only` - When true, raises an error
* `: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`, 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 // []) do
def string_to_quoted(string, opts // []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
res = :elixir_translator.forms(to_char_list(string), line, file, opts)
case res do
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
@@ -223,24 +237,20 @@ defmodule Code do
end
end
@doc false
def string_to_ast!(string, opts // []) do
IO.write "[WARNING] Code.string_to_ast! is deprecated, please use Code.string_to_quoted! instead\n#{Exception.format_stacktrace}"
string_to_quoted!(string, opts)
end
@doc """
Converts the given string to its quoted form. It returns the ast if it succeeds,
raises an exception otherwise. The exception is a TokenMissingError
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.
`SyntaxError` otherwise.
Check `Code.string_to_quoted/2` for options information.
Check `string_to_quoted/2` for options information.
"""
def string_to_quoted!(string, opts // []) do
def string_to_quoted!(string, opts // []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
res = :elixir_translator.forms!(to_char_list(string), line, file, opts)
unpack_quote(line, res)
end
@@ -249,16 +259,18 @@ 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
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
module defined in the file.
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
many times. I.e. if `load_file` is called N times with
a given file, the given file will be loaded N times. Check
`require_file` if you don't want a file to be loaded concurrently.
`require_file/2` if you don't want a file to be loaded concurrently.
"""
def load_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
@@ -269,17 +281,19 @@ defmodule Code do
end
@doc """
Requires 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`. If
the file was already required/loaded, doesn't do anything and returns nil.
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`.
Notice that if `require_file` is invoked by different processes concurrently,
the first process to invoke `require_file` acquires a lock and the remaining
ones will block until the file is available. I.e. if `require_file` is called
N times with a given file, it will be loaded only once. The first process to
call `require_file` will get the list of loaded modules, others will get nil.
call `require_file` will get the list of loaded modules, others will get `nil`.
Check `load_file` if you want a file to be loaded concurrently.
Check `load_file/2` if you want a file to be loaded concurrently.
"""
def require_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
@@ -297,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
@@ -305,22 +320,23 @@ 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:
* `:docs` - when true, retain documentation in the compiled module,
true by default;
* `:docs` - when `true`, retain documentation in the compiled module,
`true` by default;
* `:debug_info` - when true, retain debug information in the compiled module.
* `: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;
* `:ignore_module_conflict` - when `true`, override modules that were already defined
without raising errors, `false` by default;
* `:warnings_as_errors` - cause compilation to fail when warnings are spewed;
* `:warnings_as_errors` - cause compilation to fail when warnings are generated;
"""
def compiler_options(opts) do
@@ -328,18 +344,22 @@ defmodule Code do
end
@doc """
Compiles the given string and returns a list of tuples where
Compile the given string.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
For compiling many files at once, check `Kernel.ParallelCompiler`.
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
:elixir_compiler.string :unicode.characters_to_list(string), file
:elixir_compiler.string String.to_char_list!(string), file
end
@doc """
Compiles the quoted expression and returns a list of tuples where
Compile the quoted expression.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
"""
@@ -348,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 loaded yet,
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
@@ -364,9 +386,9 @@ defmodule Code do
are loaded as needed. In embedded mode the opposite happens, as all
modules need to be loaded upfront or explicitly.
Therefore, this function is useful to check if a module is loaded
before using it and react accordingly. For example, the `URI` module
uses this function to check if a specific parser exists for a given
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`
module uses this function to check if a specific parser exists for a given
URI scheme.
## Code.ensure_compiled
@@ -375,32 +397,36 @@ defmodule Code do
superset of `ensure_loaded/1`.
Since Elixir's compilation happens in parallel, in some situations
you may need to use a module but it was not compiled yet, therefore
you may need to use a module but that was not yet compiled, therefore
it can't even be loaded.
`ensure_compiled/1` puts a halt in the current process until the
`ensure_compiled/1` halts the current process until the
module we are depending on is available.
In most of the cases, `ensure_loaded` is enough. `ensure_compiled`
must be used just in same rare conditions, usually involving macros
that needs to invoke a module for callback information.
In most cases, `ensure_loaded` is enough. `ensure_compiled`
must be used in some rare cases, usually involving macros
that need to invoke a module for callback information.
"""
def ensure_loaded(module) when is_atom(module) do
:code.ensure_loaded(module)
end
@doc """
Similar to `ensure_loaded/1`, but returns a boolean in case
it could be ensured or not.
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 just
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
@@ -428,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))
+86 -64
View File
@@ -1,5 +1,5 @@
defmodule Dict do
@moduledoc %B"""
@moduledoc %S"""
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict, allowing
@@ -11,30 +11,31 @@ defmodule Dict do
HashDict.new #=> creates an empty HashDict
For simplicity's sake, in the examples below everytime
`new` is used, it implies one of the module-specific
calls like above. Likewise, when the result of a function
invocation is shown in the form `[a: 1, b: 2]`, it implies
that the returned value is actually of the same dict type
as the input one.
In the examples below, `dict_impl` means a specific
`Dict` implementation, for example `HashDict` or `ListDict`.
## Protocols
Besides implementing the functions in this module, all
dictionaries are also required to implement the `Access`
dictionaries are required to implement the `Access`
protocol:
iex> dict = HashDict.new
iex> dict = dict_impl.new
...> dict = Dict.put(dict, :hello, :world)
...> dict[:hello]
:world
And also the `Enumerable` protocol, allowing one to write:
And the `Enumerable` protocol, allowing one to write:
Enum.each(dict, fn ({ k, v }) ->
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
@@ -44,12 +45,16 @@ defmodule Dict do
@type keys :: [ key ]
@type t :: tuple | list
defcallback new :: t
defcallback new(Keyword.t) :: t
defcallback delete(t, key) :: t
defcallback drop(t, keys) :: t
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
defcallback get(t, key, value) :: value
defcallback fetch(t, key) :: { :ok, value } | :error
defcallback fetch!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback merge(t, t) :: t
@@ -62,9 +67,10 @@ defmodule Dict do
defcallback split(t, keys) :: {t, t}
defcallback take(t, keys) :: t
defcallback to_list(t) :: list()
defcallback update(t, key, (value -> value)) :: t | no_return
defcallback update(t, key, value, (value -> value)) :: t
defcallback update!(t, key, (value -> value)) :: t | no_return
defcallback values(t) :: list(value)
defcallback reduce(t, any, ({key, value}, any -> any)) :: any
defmacrop target(dict) do
quote do
@@ -80,13 +86,12 @@ defmodule Dict do
end
@doc """
Returns a list containing all dict's keys.
The keys are not guaranteed to be sorted, unless
the underlying dict implementation defines so.
Returns a list of all keys in `dict`.
The keys are not guaranteed to be in any order.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.keys(d))
[:a,:b]
@@ -97,11 +102,12 @@ defmodule Dict do
end
@doc """
Returns a list containing all dict's values.
Returns a list of all values in `dict`
The values are not guaranteed to be in any order.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.values(d))
[1,2]
@@ -116,7 +122,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> Dict.size(d)
2
@@ -127,11 +133,11 @@ defmodule Dict do
end
@doc """
Returns whether the given key exists in the given dict.
Returns whether the given `key` exists in the given `dict`.
## Examples
iex> d = HashDict.new([a: 1])
iex> d = dict_impl.new([a: 1])
iex> Dict.has_key?(d, :a)
true
iex> Dict.has_key?(d, :b)
@@ -145,11 +151,11 @@ defmodule Dict do
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, returns `default` (or nil if not provided).
contain `key`, returns `default` (or `nil` if not provided).
## Examples
iex> d = HashDict.new([a: 1])
iex> d = dict_impl.new([a: 1])
iex> Dict.get(d, :a)
1
iex> Dict.get(d, :b)
@@ -162,18 +168,13 @@ defmodule Dict do
target(dict).get(dict, key, default)
end
@doc false
def get!(dict, key) do
target(dict).get!(dict, key)
end
@doc """
Returns the `{ :ok, value }` associated with `key` in `dict`.
Returns `{ :ok, value }` associated with `key` in `dict`.
If `dict` does not contain `key`, returns `:error`.
## Examples
iex> d = HashDict.new([a: 1])
iex> d = dict_impl.new([a: 1])
iex> Dict.fetch(d, :a)
{ :ok, 1 }
iex> Dict.fetch(d, :b)
@@ -191,7 +192,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1])
iex> d = dict_impl.new([a: 1])
iex> Dict.fetch!(d, :a)
1
iex> Dict.fetch!(d, :b)
@@ -209,7 +210,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put(d, :a, 3)
...> Dict.get(d, :a)
3
@@ -225,7 +226,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put_new(d, :a, 3)
...> Dict.get(d, :a)
1
@@ -237,17 +238,17 @@ defmodule Dict do
end
@doc """
Removes the entry stored under the given key from `dict`.
Removes the entry stored under the given `key` from `dict`.
If `dict` does not contain `key`, returns the dictionary unchanged.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.delete(d, :a)
...> Dict.get(d, :a)
nil
iex> d = HashDict.new([b: 2])
iex> d = dict_impl.new([b: 2])
...> Dict.delete(d, :a) == d
true
@@ -258,13 +259,17 @@ defmodule Dict do
end
@doc """
Merges the given enum into the dict. In case one of the enum entries
alread exist in the dict, it is given higher preference.
Merges the given `enum` into `dict`. If one of the `enum` keys
already exists in `dict`, the `dict` value is replaced by the `enum`
value.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
## Examples
iex> d1 = HashDict.new([a: 1, b: 2])
...> d2 = HashDict.new([a: 3, d: 4])
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 3, b: 2, d: 4]
@@ -276,14 +281,17 @@ defmodule Dict do
end
@doc """
Merges the given enum into the dict. In case one of the enum entries
alread exist in the dict, the given function is invoked to solve
conflicts.
Merges the given `enum` into `dict`. If one of the `enum` entries
already exists in `dict`, the given function is invoked to resolve
the conflict.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
## Examples
iex> d1 = HashDict.new([a: 1, b: 2])
...> d2 = HashDict.new([a: 3, d: 4])
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
...> v1 + v2
...> end)
@@ -302,17 +310,17 @@ defmodule Dict do
## Examples
iex> dict = HashDict.new [a: 1]
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :a
...> {v, Enum.sort(d)}
{1,[]}
iex> dict = HashDict.new [a: 1]
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b
...> {v, Enum.sort(d)}
{nil,[a: 1]}
iex> dict = HashDict.new [a: 1]
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b, 3
...> {v, Enum.sort(d)}
{3,[a: 1]}
@@ -329,13 +337,18 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> d = Dict.update(d, :a, fn(val) -> -val end)
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update!(d, :a, fn(val) -> -val end)
...> Dict.get(d, :a)
-1
"""
@spec update(t, key, (value -> value)) :: t
@spec update!(t, key, (value -> value)) :: t
def update!(dict, key, fun) do
target(dict).update!(dict, key, fun)
end
@doc false
def update(dict, key, fun) do
target(dict).update(dict, key, fun)
end
@@ -347,7 +360,7 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
...> Dict.get(d, :c)
3
@@ -367,17 +380,17 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :c])
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1], [b: 2] }
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = HashDict.new([])
iex> d = dict_impl.new([])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1, b: 2], [] }
@@ -394,12 +407,12 @@ defmodule Dict do
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:a, :c, :d])
...> Dict.to_list(d)
[b: 2]
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d)
[a: 1, b: 2]
@@ -412,11 +425,12 @@ defmodule Dict do
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included. Any non-member keys are ignored.
included.
Any non-member keys are ignored.
## Examples
iex> d = HashDict.new([a: 1, b: 2])
iex> d = dict_impl.new([a: 1, b: 2])
...>
...> d = Dict.take(d, [:a, :c, :d])
...> Dict.to_list(d)
@@ -434,6 +448,14 @@ defmodule Dict do
@doc """
Returns an empty dict of the same type as `dict`.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> e = Dict.empty(d)
...> Dict.to_list(e)
[]
"""
@spec empty(t) :: t
def empty(dict) do
@@ -441,17 +463,17 @@ defmodule Dict do
end
@doc """
Check if two dicts are equal, if the dicts are of different types they're
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
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
...> Dict.equal?(a, b)
true
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = []
...> Dict.equal?(a, b)
false
+125
View File
@@ -0,0 +1,125 @@
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
use Dict.Behaviour
# implement required functions (see below)
# override default implementations if needed
end
The client module must contain following functions: `size/1`, `fetch/2`,
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
the Dict behaviour, so no extra functions are actually required.
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
module via `Dict` doctests:
defmodule MyDict do
def new(keywords // []) do
...
end
end
defmodule MyTests do
use ExUnit.Case
doctest Dict
defp dict_impl, do: MyDict
end
"""
defmacro __using__(_) do
quote do
@behaviour Dict
def get(dict, key, default // nil) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> default
end
end
defoverridable get: 2, get: 3
def fetch!(dict, key) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
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
{ :ok, value } -> take(dict, keys, put(acc, key, value))
: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
case Dict.size(dict1) == Dict.size(dict2) do
false -> false
true ->
try do
reduce(dict1, nil, fn({ k, v }, _acc) ->
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
end)
true
catch
:error -> false
end
end
end
defoverridable equal?: 2
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
end)
end
defoverridable merge: 2, merge: 3
end
end
end
+571 -249
View File
File diff suppressed because it is too large Load Diff
+101 -46
View File
@@ -5,19 +5,19 @@ defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
@@ -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)}"
@@ -48,7 +54,7 @@ end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
@@ -59,7 +65,7 @@ end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
@@ -67,11 +73,11 @@ defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
defexception Protocol.UndefinedError, [protocol: nil, value: nil, description: nil] do
def message(exception) do
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
if exception.extra do
msg <> ". " <> exception.extra
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
if exception.description do
msg <> ", " <> exception.description
else
msg
end
@@ -96,12 +102,21 @@ defexception Enum.EmptyError, message: "empty error"
defmodule Exception do
@moduledoc """
Several convenience functions to work and pretty print
Several convenience functions to work with and pretty print
exceptions and stacktraces.
"""
@doc """
Normalizes an exception converting Erlang exceptions
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions. It takes the kind spilled by
`catch` as an argument as a convenience for converting only
`:errors`, ignorning the others.
"""
def normalize(:error, exception), do: normalize(exception)
def normalize(_kind, other), do: other
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions. Useful when interfacing Erlang
code with Elixir code.
"""
@@ -137,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]
@@ -157,39 +176,40 @@ defmodule Exception do
@doc """
Receives a tuple representing a stacktrace entry and formats it.
The current working directory may be given as argument, which
is used to prettify the stacktrace.
The current working directory may be given as an argument,
otherwise one is automatically retrieved.
"""
def format_stacktrace_entry(entry, cwd // nil)
# From Macro.Env.stacktrace
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}#{inspect module} (module)"
"#{format_location(file_line, cwd)}#{inspect module} (module)"
end
# From :elixir_compiler
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(module)"
"#{format_location(file_line, cwd)}(module)"
end
# From :elixir_compiler
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
"#{format_file_line(file_line, cwd)}(file)"
"#{format_location(file_line, cwd)}(file)"
end
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_module_fun_arity(module, fun, arity)}"
"#{format_location(file_line, cwd)}#{format_mfa(module, fun, arity)}"
end
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
"#{format_file_line(file_line, cwd)}#{format_fun_arity(fun, arity)}"
"#{format_location(file_line, cwd)}#{format_fa(fun, arity)}"
end
@doc """
Formats the stacktrace.
A stacktrace must be given as argument. If not, this function
calculates the current stacktrace and formats it. As consequence,
A stacktrace must be given as an argument. If not, this function
calculates the current stacktrace and formats it. As a consequence,
the value of `System.stacktrace` is changed.
"""
def format_stacktrace(trace // nil) do
@@ -199,15 +219,17 @@ defmodule Exception do
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
cwd = System.cwd
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace_entry(&1)) <> "\n"
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1, cwd)) <> "\n"
end
end
@doc """
Formats the caller, i.e. the first entry in the stacktrace.
Notice that doing to tail call optimization, the stacktrace
Notice that due to tail call optimization, the stacktrace
may not report the direct caller of the function.
"""
def format_caller(trace // nil) do
@@ -224,11 +246,17 @@ defmodule Exception do
end
end
## Helpers
@doc """
Receives an anonymous function and arity and formats it as
shown in stacktraces. The arity may also be a list of arguments.
# Format fun and arity
@doc false
def format_fun_arity(fun, arity) do
## Examples
Exception.format_fa(fn -> end, 1)
#=> "#Function<...>/1"
"""
def format_fa(fun, arity) do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
@@ -237,11 +265,22 @@ defmodule Exception do
end
end
# Receives a module, fun and arity and returns a string
# representing such invocation. Arity may also be a list
# of arguments. It follows the same syntax as in stacktraces.
@doc false
def format_module_fun_arity(module, fun, arity) do
@doc """
Receives a module, fun and arity and formats it
as shown in stacktraces. The arity may also be a list
of arguments.
## Examples
iex> Exception.format_mfa Foo, :bar, 1
"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
fun =
case inspect(fun) do
<< ?:, erl :: binary >> -> erl
@@ -250,25 +289,37 @@ 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
# Format file and line for exception printing.
@doc false
def format_file_line(opts, cwd) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
end
defp format_module(mod), do: "#{inspect mod}."
def format_file_line(file, line, cwd) do
@doc """
Formats the given file and line as shown in stacktraces.
If any of the values are nil, they are omitted.
The current working directory may be given as an argument,
otherwise one is automatically retrieved.
## Examples
iex> Exception.format_file_line("foo", 1)
"foo:1: "
iex> Exception.format_file_line("foo", nil)
"foo: "
iex> Exception.format_file_line(nil, nil)
""
"""
def format_file_line(file, line, cwd // nil) do
if file do
file = to_binary(file)
if cwd do
file = Path.relative_to(file, cwd)
end
file = to_string(file)
file = if cwd, do: Path.relative_to(file, cwd), else: Path.relative_to_cwd(file)
if line && line != 0 do
"#{file}:#{line}: "
@@ -280,6 +331,10 @@ defmodule Exception do
end
end
defp format_location(opts, cwd) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
{ module, function, length(args) }
end
+140 -137
View File
@@ -33,45 +33,38 @@ end
defexception File.Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = list_to_binary(:file.format_error(reason exception))
formatted = iolist_to_binary(:file.format_error(reason exception))
"could not #{action exception} #{path exception}: #{formatted}"
end
end
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
def message(exception) do
formatted = list_to_binary(:file.format_error(reason exception))
formatted = iolist_to_binary(:file.format_error(reason exception))
location = if on = on(exception), do: ". #{on}", else: ""
"could not #{action exception} from #{source exception} to " <>
"#{destination exception}#{location}: #{formatted}"
end
end
defexception File.IteratorError, reason: nil do
def message(exception) do
formatted = list_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.
Some of those functions are low-level, allowing the user
to interact with the file or IO devices, like `File.open/2`,
`File.copy/3` and others. This module also provides higher
to interact with the file or IO devices, like `open/2`,
`copy/3` and others. This module also provides higher
level functions that works with filenames and have their naming
based on UNIX variants. For example, one can copy a file
via `File.cp/3` and remove files and directories recursively
via `File.rm_rf/2`
via `cp/3` and remove files and directories recursively
via `rm_rf/1`
In order to write and read files, one must use the functions
in the IO module. By default, a file is opened in binary mode
which requires the functions `IO.binread` and `IO.binwrite`
in the `IO` module. By default, a file is opened in binary mode
which requires the functions `IO.binread/2` and `IO.binwrite/2`
to interact with the file. A developer may pass `:utf8` as an
option when opening the file and then all other functions
from IO are available, since they work directly with Unicode
from `IO` are available, since they work directly with Unicode
data.
Most of the functions in this module return `:ok` or
@@ -104,7 +97,7 @@ defmodule File do
alias :filelib, as: FL
@doc """
Returns true if the path is a regular file.
Returns `true` if the path is a regular file.
## Examples
@@ -116,14 +109,14 @@ defmodule File do
end
@doc """
Returns true if the path is a directory.
Returns `true` if the path is a directory.
"""
def dir?(path) do
FL.is_dir(path)
end
@doc """
Returns true if the given path exists.
Returns `true` if the given path exists.
It can be regular file, directory, socket,
symbolic link, named pipe or device file.
@@ -161,13 +154,13 @@ defmodule File do
end
@doc """
Same as `mkdir`, but raises an exception in case of failure. Otherwise `:ok`.
Same as `mkdir/1`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def mkdir!(path) do
case mkdir(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "make directory", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "make directory", path: to_string(path)
end
end
@@ -186,13 +179,13 @@ defmodule File do
end
@doc """
Same as `mkdir_p`, but raises an exception in case of failure. Otherwise `:ok`.
Same as `mkdir_p/1`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def mkdir_p!(path) do
case mkdir_p(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "make directory (with -p)", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "make directory (with -p)", path: to_string(path)
end
end
@@ -210,7 +203,7 @@ defmodule File do
On some platforms, `:enoent` is returned instead.
* :enomem - There is not enough memory for the contents of the file.
You can use `:file.format_error(reason)` to get a descriptive string of the error.
You can use `:file.format_error/1` to get a descriptive string of the error.
"""
def read(path) do
F.read_file(path)
@@ -218,14 +211,14 @@ defmodule File do
@doc """
Returns binary with the contents of the given filename or raises
File.Error if an error occurs.
`File.Error` if an error occurs.
"""
def read!(path) do
case read(path) do
{ :ok, binary } ->
binary
{ :error, reason } ->
raise File.Error, reason: reason, action: "read file", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "read file", path: to_string(path)
end
end
@@ -233,34 +226,34 @@ defmodule File do
Returns information about the `path`. If it exists, it
returns a `{ :ok, info }` tuple, where info is a
`File.Info` record. Retuns `{ :error, reason }` with
the same reasons as `File.read` if a failure occurs.
the same reasons as `read/1` if a failure occurs.
## Options
The accepted options are:
* `:time` if the time should be local, universal or posix.
Default is local.
* `:time` if the time should be `:local`, `:universal` or `:posix`.
Default is `:local`.
"""
def stat(path, opts // []) do
case F.read_file_info(path, opts) do
{:ok, fileinfo} ->
{:ok, File.Stat.new fileinfo}
{:ok, set_elem(fileinfo, 0, File.Stat) }
error ->
error
end
end
@doc """
Same as `stat` but returns the `File.Stat` directly and
Same as `stat/2` but returns the `File.Stat` directly and
throws `File.Error` if an error is returned.
"""
def stat!(path, opts // []) do
case stat(path, opts) do
{:ok, info} -> info
{:error, reason} ->
raise File.Error, reason: reason, action: "read file stats", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "read file stats", path: to_string(path)
end
end
@@ -280,7 +273,7 @@ defmodule File do
case write_stat(path, stat, opts) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "write file stats", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "write file stats", path: to_string(path)
end
end
@@ -290,20 +283,23 @@ defmodule File do
"""
def touch(path, time // :calendar.local_time) do
case F.change_time(path, time) do
{ :error, :enoent } -> write(path, "")
other -> other
{ :error, :enoent } ->
write(path, "")
F.change_time(path, time)
other ->
other
end
end
@doc """
Same as `touch/1` but raises an exception if it fails.
Same as `touch/2` but raises an exception if it fails.
Returns `:ok` otherwise.
"""
def touch!(path, time // :calendar.local_time) do
case touch(path, time) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "touch", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "touch", path: to_string(path)
end
end
@@ -311,7 +307,7 @@ defmodule File do
Copies the contents of `source` to `destination`.
Both parameters can be a filename or an io device opened
with `File.open/2`. `bytes_count` specifies the number of
with `open/2`. `bytes_count` specifies the number of
bytes to copy, the default being `:infinity`.
If file `destination` already exists, it is overwritten
@@ -320,14 +316,14 @@ defmodule File do
Returns `{ :ok, bytes_copied }` if successful,
`{ :error, reason }` otherwise.
Compared to the `File.cp/3`, this function is more low-level,
Compared to the `cp/3`, this function is more low-level,
allowing a copy from device to device limited by a number of
bytes. On the other hand, `File.cp/3` performs more extensive
bytes. On the other hand, `cp/3` performs more extensive
checks on both source and destination and it also preserves
the file mode after copy.
Typical error reasons are the same as in `open/2`,
`read/1` and `write/2`.
`read/1` and `write/3`.
"""
def copy(source, destination, bytes_count // :infinity) do
F.copy(source, destination, bytes_count)
@@ -342,7 +338,7 @@ defmodule File do
{ :ok, bytes_count } -> bytes_count
{ :error, reason } ->
raise File.CopyError, reason: reason, action: "copy",
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
source: to_string(source), destination: to_string(destination)
end
end
@@ -355,15 +351,15 @@ defmodule File do
copies the contents of `source` to `destination/source`.
If a file already exists in the destination, it invokes a
callback which should return true if the existing file
should be overwritten, false otherwise. It defaults to return true.
callback which should return `true` if the existing file
should be overwritten, `false` otherwise. It defaults to return `true`.
It returns `:ok` in case of success, returns
`{ :error, reason }` otherwise.
If you want to copy contents from an io device to another device
or do a straight copy from a source to a destination without
preserving modes, check `File.copy/3` instead.
preserving modes, check `copy/3` instead.
"""
def cp(source, destination, callback // fn(_, _) -> true end) do
output =
@@ -381,7 +377,7 @@ defmodule File do
end
@doc """
The same as `cp/3`, but raises File.CopyError if it fails.
The same as `cp/3`, but raises `File.CopyError` if it fails.
Returns the list of copied files otherwise.
"""
def cp!(source, destination, callback // fn(_, _) -> true end) do
@@ -389,12 +385,12 @@ defmodule File do
:ok -> :ok
{ :error, reason } ->
raise File.CopyError, reason: reason, action: "copy recursively",
source: :unicode.characters_to_binary(source),
destination: :unicode.characters_to_binary(destination)
source: to_string(source),
destination: to_string(destination)
end
end
@doc %B"""
@doc %S"""
Copies the contents in source to destination.
Similar to the command `cp -r` in Unix systems,
this function behaves differently depending
@@ -407,8 +403,8 @@ defmodule File do
If a file already exists in the destination,
it invokes a callback which should return
true if the existing file should be overwritten,
false otherwise. It defaults to return true.
`true` if the existing file should be overwritten,
`false` otherwise. It defaults to return `true`.
If a directory already exists in the destination
where a file is meant to be (or otherwise), this
@@ -456,7 +452,7 @@ defmodule File do
end
@doc """
The same as `cp_r/3`, but raises File.CopyError if it fails.
The same as `cp_r/3`, but raises `File.CopyError` if it fails.
Returns the list of copied files otherwise.
"""
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
@@ -464,8 +460,8 @@ defmodule File do
{ :ok, files } -> files
{ :error, reason, file } ->
raise File.CopyError, reason: reason, action: "copy recursively",
source: :unicode.characters_to_binary(source),
destination: :unicode.characters_to_binary(destination),
source: to_string(source),
destination: to_string(destination),
on: file
end
end
@@ -473,28 +469,28 @@ defmodule File do
# src may be a file or a directory, dest is definitely
# a directory. Returns nil unless an error is found.
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
case :elixir.file_type(src) do
case :elixir_utils.file_type(src) do
{ :ok, :regular } ->
do_cp_file(src, dest, callback, acc)
{ :ok, :symlink } ->
case F.read_link(src) do
{ :ok, link } -> do_cp_link(link, src, dest, callback, acc)
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
{ :ok, :directory } ->
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)
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(dest) }
{ :error, reason } -> { :error, reason, to_string(dest) }
end
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
{ :ok, _ } -> { :error, :eio, src }
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
end
@@ -523,12 +519,12 @@ defmodule File do
{ :ok, _ } ->
copy_file_mode!(src, dest)
[dest|acc]
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
else
acc
end
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
end
@@ -542,12 +538,12 @@ defmodule File do
rm(dest)
case F.make_symlink(link, dest) do
:ok -> [dest|acc]
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
else
acc
end
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
{ :error, reason } -> { :error, reason, to_string(src) }
end
end
@@ -576,7 +572,7 @@ defmodule File do
case F.write_file(path, content, modes) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "write to file", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "write to file", path: to_string(path)
end
end
@@ -607,13 +603,13 @@ defmodule File do
end
@doc """
Same as `rm`, but raises an exception in case of failure. Otherwise `:ok`.
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
"""
def rm!(path) do
case rm(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "remove file", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "remove file", path: to_string(path)
end
end
@@ -623,7 +619,7 @@ defmodule File do
## Examples
File.rddir('tmp_dir')
File.rmdir('tmp_dir')
#=> :ok
File.rmdir('file.txt')
@@ -641,7 +637,7 @@ defmodule File do
case rmdir(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "remove directory", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "remove directory", path: to_string(path)
end
end
@@ -651,7 +647,7 @@ defmodule File do
files are simply ignored (i.e. doesn't make this function fail).
Returns `{ :ok, files_and_directories }` with all files and
directories removed in no specific order, `{ :error, reason }`
directories removed in no specific order, `{ :error, reason, file }`
otherwise.
## Examples
@@ -669,7 +665,7 @@ defmodule File do
defp do_rm_rf(path, { :ok, acc } = entry) do
case safe_list_dir(path) do
{ :ok, files } ->
{ :ok, files } when is_list(files) ->
res =
Enum.reduce files, entry, fn(file, tuple) ->
do_rm_rf(FN.join(path, file), tuple)
@@ -680,18 +676,15 @@ defmodule File do
case rmdir(path) do
:ok -> { :ok, [path|acc] }
{ :error, :enoent } -> res
reason -> { :error, reason, :unicode.characters_to_binary(path) }
{ :error, reason } -> { :error, reason, to_string(path) }
end
reason -> { :error, reason, :unicode.characters_to_binary(path) }
reason ->
reason
end
{ :error, reason } when reason in [:enotdir, :eio] ->
case rm(path) do
:ok -> { :ok, [path|acc] }
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
reason -> { :error, reason, :unicode.characters_to_binary(path) }
end
{ :error, :enoent } -> entry
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(path) }
{ :ok, :directory } -> do_rm_directory(path, entry)
{ :ok, :regular } -> do_rm_regular(path, entry)
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
{ :error, reason } -> { :error, reason, to_string(path) }
end
end
@@ -699,10 +692,40 @@ defmodule File do
reason
end
defp do_rm_regular(path, { :ok, acc } = entry) do
case rm(path) do
:ok -> { :ok, [path|acc] }
{ :error, :enoent } -> entry
{ :error, reason } -> { :error, reason, to_string(path) }
end
end
# On windows, symlinks are treated as directory and must be removed
# with rmdir/1. But on Unix, we remove them via rm/1. So we first try
# to remove it as a directory and, if we get :enotdir, we fallback to
# a file removal.
defp do_rm_directory(path, { :ok, acc } = entry) do
case rmdir(path) do
:ok -> { :ok, [path|acc] }
{ :error, :enotdir } -> do_rm_regular(path, entry)
{ :error, :enoent } -> entry
{ :error, reason } -> { :error, reason, to_string(path) }
end
end
defp safe_list_dir(path) do
case F.read_link(path) do
{ :ok, _ } -> { :error, :enotdir }
_ -> F.list_dir(path)
case :elixir_utils.file_type(path) do
{ :ok, :symlink } ->
case :elixir_utils.file_type(path, :read_file_info) do
{ :ok, :directory } -> { :ok, :directory }
_ -> { :ok, :regular }
end
{ :ok, :directory } ->
F.list_dir(path)
{ :ok, _ } ->
{ :ok, :regular }
{ :error, reason } ->
{ :error, reason }
end
end
@@ -716,7 +739,7 @@ defmodule File do
{ :error, reason, _ } ->
raise File.Error, reason: reason,
action: "remove files and directories recursively from",
path: :unicode.characters_to_binary(path)
path: to_string(path)
end
end
@@ -724,8 +747,8 @@ defmodule File do
Opens the given `path` according to the given list of modes.
In order to write and read files, one must use the functions
in the IO module. By default, a file is opened in binary mode
which requires the functions `IO.binread` and `IO.binwrite`
in the `IO` module. By default, a file is opened in binary mode
which requires the functions `IO.binread/2` and `IO.binwrite/2`
to interact with the file. A developer may pass `:utf8` as an
option when opening the file and then all other functions from
`IO` are available, since they work directly with Unicode data.
@@ -756,10 +779,10 @@ defmodule File do
or if data is read by a function that returns data in a format that cannot cope
with the character range of the data, an error occurs and the file will be closed.
If a function is given to modes (instead of a list), it dispatches to `open/3`.
If a function is given two modes (instead of a list), it dispatches to `open/3`.
Check `http://www.erlang.org/doc/man/file.html#open-2` for more information about
other options as `read_ahead` and `delayed_write`.
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
other options like `read_ahead` and `delayed_write`.
This function returns:
@@ -830,7 +853,7 @@ defmodule File do
case open(path, modes) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "open", path: to_string(path)
end
end
@@ -842,7 +865,7 @@ defmodule File do
case open(path, modes, function) do
{ :ok, device } -> device
{ :error, reason } ->
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "open", path: to_string(path)
end
end
@@ -854,7 +877,7 @@ defmodule File do
"""
def cwd() do
case F.get_cwd do
{ :ok, cwd } -> { :ok, :unicode.characters_to_binary(cwd) }
{ :ok, base } -> { :ok, String.from_char_list!(base) }
{ :error, _ } = error -> error
end
end
@@ -864,7 +887,7 @@ defmodule File do
"""
def cwd!() do
case F.get_cwd do
{ :ok, cwd } -> :unicode.characters_to_binary(cwd)
{ :ok, cwd } -> to_string(cwd)
{ :error, reason } ->
raise File.Error, reason: reason, action: "get current working directory"
end
@@ -879,13 +902,13 @@ defmodule File do
end
@doc """
The same as `cd/0`, but raises an exception if it fails.
The same as `cd/1`, but raises an exception if it fails.
"""
def cd!(path) do
case F.set_cwd(path) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "set current working directory to", path: :unicode.characters_to_binary(path)
raise File.Error, reason: reason, action: "set current working directory to", path: to_string(path)
end
end
@@ -915,7 +938,7 @@ defmodule File do
"""
def ls(path // ".") do
case F.list_dir(path) do
{ :ok, file_list } -> { :ok, Enum.map(file_list, :unicode.characters_to_binary(&1)) }
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
{ :error, _ } = error -> error
end
end
@@ -928,7 +951,7 @@ defmodule File do
case ls(dir) do
{ :ok, value } -> value
{ :error, reason } ->
raise File.Error, reason: reason, action: "list directory", path: :unicode.characters_to_binary(dir)
raise File.Error, reason: reason, action: "list directory", path: to_string(dir)
end
end
@@ -944,64 +967,44 @@ defmodule File do
F.close(io_device)
end
@doc false
def iterator(device) do
IO.write "[WARNING] File.iterator/1 is deprecated, please use IO.stream/1 instead\n#{Exception.format_stacktrace}"
IO.stream(device)
end
@doc """
Opens the given `file` with the given `mode` and
returns its stream. The returned stream will
fail for the same reasons as `File.open!`. Note
that the file is opened when the iteration begins.
Opens the given `file` with the given `mode` and returns
a stream for each `:line` (default) or for a given number
of bytes given by `line_or_bytes`.
The returned stream will fail for the same reasons as `File.open!`.
Note that the file is opened only and every time streaming begins.
"""
def stream!(file, mode // []) do
def stream!(file, mode // [], line_or_bytes // :line) do
fn(acc, fun) ->
device = open!(file, mode)
try do
IO.stream(device, acc, fun)
IO.stream(device, line_or_bytes, acc, fun)
after
F.close(device)
end
end
end
@doc false
def iterator!(device, mode // []) do
IO.write "[WARNING] File.iterator!/2 is deprecated, please use File.stream!/2 instead\n#{Exception.format_stacktrace}"
stream!(device, mode)
end
@doc false
def biniterator(device) do
IO.write "[WARNING] File.biniterator/1 is deprecated, please use IO.binstream/1 instead\n#{Exception.format_stacktrace}"
IO.binstream(device)
end
@doc """
Opens the given `file` with the given `mode` and
returns its binstream. The returned stream will
fail for the same reasons as `File.open!`. Note
that the file is opened when the iteration begins.
Opens the given `file` with the given `mode` and returns
a binstream for each `:line` (default) or for a given number
of bytes given by `line_or_bytes`.
The returned stream will fail for the same reasons as `File.open!`.
Note that the file is opened only and every time streaming begins.
"""
def binstream!(file, mode // []) do
def binstream!(file, mode // [], line_or_bytes // :line) do
fn(fun, acc) ->
device = open!(file, mode)
try do
IO.binstream(device, fun, acc)
IO.binstream(device, line_or_bytes, fun, acc)
after
F.close(device)
end
end
end
@doc false
def biniterator!(device, mode // []) do
IO.write "[WARNING] File.binstream!/2 is deprecated, please use File.binstream!/2 instead\n#{Exception.format_stacktrace}"
binstream!(device, mode)
end
@doc """
Changes the unix file `mode` for a given `file`.
Returns `:ok` on success, or `{:error, reason}`
@@ -1018,7 +1021,7 @@ defmodule File do
case chmod(file, mode) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change mode for", path: :unicode.characters_to_binary(file)
raise File.Error, reason: reason, action: "change mode for", path: to_string(file)
end
end
@@ -1038,7 +1041,7 @@ defmodule File do
case chgrp(file, gid) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change group for", path: :unicode.characters_to_binary(file)
raise File.Error, reason: reason, action: "change group for", path: to_string(file)
end
end
@@ -1058,7 +1061,7 @@ defmodule File do
case chown(file, gid) do
:ok -> :ok
{ :error, reason } ->
raise File.Error, reason: reason, action: "change owner for", path: :unicode.characters_to_binary(file)
raise File.Error, reason: reason, action: "change owner for", path: to_string(file)
end
end
+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
+211
View File
@@ -0,0 +1,211 @@
defmodule GenFSM.Behaviour do
@moduledoc """
This module is a convenience for defining GenFSM callbacks in Elixir.
A finite state machine (FSM) is responsible for reacting to events received;
GenFSM is an OTP behaviour that encapsulates common FSM
functionalities.
## Example
Below is an example of a GenFSM that runs a very simple minded
coffee vending machine (CVM). The CVM treats all coins the same. If
you press the request button then the CVM will brew coffee, if you
have paid enough coins; if not, it will wait until you have inserted
enough coins and then it will instantly brew the coffee, since you
had already pressed the request button. As we told you - a very
simple minded CVM! And greedy too. If you insert more coins than you
need it will gladly eat them until you press the request button.
We will leave it to the service-minded reader to improve the way the CVM
works - we hereby declare a full disclaimer for any lawsuits that the
behaviour of the CVM in its original state might encur.
defmodule MyFsm do
use GenFSM.Behaviour
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
#
# API functions
#
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
#
# Callbacks
#
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p])
when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{:next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
end
{ :ok, _pid } = MyFsm.start_link()
MyFsm.insert_coin
#=> :ok
MyFsm.insert_coin
#=> :ok
MyFsm.request_coffee
#=> :ok
MyFsm.insert_coin
#=> :ok
#=> Here's your coffee!
Notice we never call the GenFSM callbacks directly; they are called by
OTP whenever we interact with the server throught the API. `send_event` is
asynchronous, whereas `sync_send_event` is synchronous. For
a GenFSM, the different values a callback can return depend
on the type of callback.
State handling returns for `send_event` callbacks:
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, new_state_data }
State handling returns for `sync_send_event` callbacks:
{ :reply, reply, next_state_name, new_state_data }
{ :reply, reply, next_state_name, new_state_data, timeout }
{ :reply, reply, next_state_name, new_state_data, :hibernate }
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, reply, new_state_data }
{ :stop, reason, new_state_date }
There are 6 callbacks required to be implemented in a GenFsm plus 1
or 2 for each state. The `GenFSM.Behaviour` module defines
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
for you. The list of callbacks are:
* `init(args)` - invoked when the FSM is started;
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
handle `sync_send_all_state_event` messages;
* `handle_event(event, state_name, state_data)` - invoked to handle
`send_all_state_event` messages;
* `handle_info(msg, state_name, state_data)` - handle all other
messages which are normally received by processes;
* `terminate(reason, state_name, state_data)` - called when the FSM
is about to terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
implemented by default, as it requires the next state to be returned.
For each state you need to define either or both of these:
* `state_name(event, state_data)` - invoked to handle
`send_event` messages;
* `state_name(event, from, state_data)`- invoked to handle
`sync_send_event` messages;
If you send asynchronous events you only need to implement the
`state_name/2` variant and vice-versa for synchronous events and
`state_name/3`. Keep in mind that if you mix `send_event` and
`sync_send_event` the best thing to do is to implement both
callbacks for all states.
Starting and sending messages to the GenFSM is done via Erlang's
`:gen_fsm` module. For more information, please refer to the
following:
* http://www.erlang.org/doc/man/gen_fsm.html
* http://www.erlang.org/doc/design_principles/fsm.html
* http://learnyousomeerlang.com/finite-state-machines
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_fsm
@doc false
def handle_event(_event, state_name, state_data) do
{ :next_state, state_name, state_data }
end
@doc false
def handle_sync_event(_event, from, state_name, state_data) do
{ :reply, :ok, state_name, state_data }
end
@doc false
def handle_info(_msg, state_name, state_data) do
{ :next_state, state_name, state_data }
end
@doc false
def terminate(_reason, _state_name, _state_data) do
:ok
end
@doc false
def code_change(_old, state_name, state_data, _extra) do
{ :ok, state_name, state_data }
end
defoverridable [handle_event: 3, handle_sync_event: 4,
handle_info: 3, terminate: 3, code_change: 4]
end
end
end
+6 -89
View File
@@ -8,7 +8,7 @@ defmodule HashDict do
their APIs, please consult the `Dict` module.
"""
@behaviour Dict
use Dict.Behaviour
# A dictionary (key-value) implementation based on dynamic hashing.
#
@@ -107,11 +107,7 @@ defmodule HashDict do
dict
end
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
end
def update(dict, key, fun) when is_function(fun, 1) do
def update!(dict, key, fun) when is_function(fun, 1) do
case dict_put(dict, key, { :update, nil, fun }) do
{ dict, 0 } ->
dict
@@ -125,13 +121,6 @@ defmodule HashDict do
dict
end
def get(dict, key, default // nil) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> default
end
end
def fetch(ordered(bucket: bucket), key) do
bucket_get(bucket, key)
end
@@ -140,17 +129,6 @@ defmodule HashDict do
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
def fetch!(dict, key) when is_tuple(dict) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
end
end
def has_key?(dict, key) do
match? { :ok, _ }, fetch(dict, key)
end
def pop(dict, key, default // nil) do
case dict_delete(dict, key) do
{ dict, _, 0 } -> { default, dict }
@@ -171,50 +149,22 @@ defmodule HashDict do
ordered()
end
def equal?(dict1, dict2) do
size = elem(dict1, 1)
case elem(dict2, 1) do
^size ->
dict_equal?(dict1, dict2)
_ ->
false
end
end
def to_list(ordered(bucket: bucket)) do
lc [k|v] inlist bucket, do: { k, v }
end
def to_list(dict) do
reduce(dict, [], [&1|&2]) |> :lists.reverse
end
def keys(dict) do
dict_fold(dict, [], fn [k|_], acc -> [k|acc] end)
end
def values(dict) do
dict_fold(dict, [], fn [_|v], acc -> [v|acc] end)
end
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
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
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) do
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
update(acc, k, v2, callback.(k, &1, v2))
end
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
super(dict, enumerable, callback)
end
def split(dict, keys) do
@@ -232,24 +182,6 @@ defmodule HashDict do
end
end
def take(dict, keys) do
take(dict, keys, new)
end
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
{ :ok, value } -> take(dict, keys, put(acc, key, value))
:error -> take(dict, keys, acc)
end
end
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
bucket_reduce(bucket, acc, fun)
@@ -321,21 +253,6 @@ defmodule HashDict do
end
end
defp dict_equal?(dict1, dict2) do
try do
dict_fold(dict1, true, fn [key|value], acc ->
case fetch(dict2, key) do
{ :ok, ^value } ->
acc
_ ->
throw(:error)
end
end)
catch
:error -> false
end
end
## Bucket helpers
# Get value from the bucket
+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
+134 -49
View File
@@ -1,7 +1,7 @@
import Kernel, except: [inspect: 1]
import Inspect.Algebra
defrecord Inspect.Opts, raw: false, limit: :infinity, pretty: false, width: 80
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
defprotocol Inspect do
@moduledoc """
@@ -19,8 +19,8 @@ defprotocol Inspect do
receives an `Inspect.Opts` record as the second argument, it returns
the underlying algebra document instead of the formatted string.
Many times, inspecting a structure can be implemented using functions
of the existing entities. For example, here is `HashSet`'s `inspect`
Many times, inspecting a structure can be implemented in function
of existing entities. For example, here is `HashSet`'s `inspect`
implementation:
defimpl Inspect, for: HashSet do
@@ -33,13 +33,13 @@ defprotocol Inspect do
The `concat` function comes from `Inspect.Algebra` and it
concatenates algebra documents together. In the example above,
it is concatenating the string `"HashSet<"` (all strings are
it is concatenating the string `"HashSet<"` (all strings are
valid algebra documents that keep their formatting when pretty
printed), the document returned by `Kernel.inspect/2` and the
other string `">"`.
Since regular strings are valid entities in an algebra document,
an implementation of inspect may simply return a binary,
an implementation of inspect may simply return a string,
although that will devoid it of any pretty-printing.
"""
@@ -86,10 +86,12 @@ defimpl Inspect, for: Atom do
valid_ref_identifier?(binary) ->
"Elixir." <> rest = binary
rest
atom in [:{}, :[], :<<>>] ->
":" <> binary
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
<< ?:, ?", String.escape(binary, ?") :: binary, ?" >>
<< ?:, ?", Inspect.BitString.escape(binary, ?") :: binary, ?" >>
end
end
@@ -133,11 +135,10 @@ defimpl Inspect, for: Atom do
end
defimpl Inspect, for: BitString do
@doc %B"""
@doc %S"""
Represents a string as itself escaping all necessary
characters. Bitstrings and strings that contain non-
printable characters are printed using the bitstring
syntax.
characters. Binaries that contain non-printable characters
are printed using the bitstring syntax.
## Examples
@@ -145,46 +146,96 @@ defimpl Inspect, for: BitString do
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
iex> inspect(<<0,1,2>>)
"<<0, 1, 2>>"
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
<< ?", String.escape(thing, ?") :: binary, ?" >>
<< ?", escape(thing, ?") :: binary, ?" >>
else
as_bitstring(thing, opts)
inspect_bitstring(thing, opts)
end
end
def inspect(thing, opts) do
as_bitstring(thing, opts)
inspect_bitstring(thing, opts)
end
## Escaping
@doc false
def escape(other, char) do
escape(other, char, <<>>)
end
defp escape(<< char, t :: binary >>, char, binary) do
escape(t, char, << binary :: binary, ?\\, char >>)
end
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
end
defp escape(<<?\a, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?a >>)
end
defp escape(<<?\b, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?b >>)
end
defp escape(<<?\d, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?d >>)
end
defp escape(<<?\e, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?e >>)
end
defp escape(<<?\f, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?f >>)
end
defp escape(<<?\n, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?n >>)
end
defp escape(<<?\r, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?r >>)
end
defp escape(<<?\\, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
end
defp escape(<<?\t, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?t >>)
end
defp escape(<<?\v, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, h >>)
end
defp escape(<<>>, _char, binary), do: binary
## Bitstrings
defp as_bitstring(bitstring, Inspect.Opts[] = opts) do
"<<" <> each_bit(bitstring, opts.limit) <> ">>"
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
each_bit(bitstring, opts.limit, "<<") <> ">>"
end
defp each_bit(_, 0) do
"..."
defp each_bit(_, 0, acc) do
acc <> "..."
end
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
integer_to_binary(h) <> ", " <> each_bit(t, decrement(counter))
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
each_bit(t, decrement(counter), acc <> integer_to_binary(h) <> ", ")
end
defp each_bit(<<h :: size(8)>>, _counter) do
integer_to_binary(h)
defp each_bit(<<h :: size(8)>>, _counter, acc) do
acc <> integer_to_binary(h)
end
defp each_bit(<<>>, _counter) do
<<>>
defp each_bit(<<>>, _counter, acc) do
acc
end
defp each_bit(bitstring, _counter) do
defp each_bit(bitstring, _counter, acc) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
@@ -192,7 +243,7 @@ defimpl Inspect, for: BitString do
end
defimpl Inspect, for: List do
@doc %B"""
@doc %S"""
Represents a list, checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used. Keywords are
@@ -214,11 +265,11 @@ defimpl Inspect, for: List do
def inspect(thing, Inspect.Opts[] = opts) do
cond do
:io_lib.printable_list(thing) ->
<< ?', String.escape(:unicode.characters_to_binary(thing), ?') :: binary, ?' >>
<< ?', 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
@@ -266,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
@@ -280,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
@@ -300,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
@@ -324,21 +388,17 @@ 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
list_to_binary(:io_lib_format.fwrite_g(thing))
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
end
defimpl Inspect, for: Regex do
@doc %B"""
@doc %S"""
Represents the Regex using the `%r""` syntax.
## Examples
@@ -359,31 +419,56 @@ end
defimpl Inspect, for: Function do
def inspect(function, _opts) do
fun_info = :erlang.fun_info(function)
mod = fun_info[:module]
if fun_info[:type] == :external and fun_info[:env] == [] do
"function(#{Inspect.Atom.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
else
'#Fun' ++ rest = :erlang.fun_to_list(function)
"#Function" <> list_to_binary(rest)
case atom_to_list(mod) do
'elixir_compiler_' ++ _ ->
if function_exported?(mod, :__RELATIVE__, 0) do
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
else
default_inspect(mod, fun_info)
end
_ ->
default_inspect(mod, fun_info)
end
end
end
defp default_inspect(mod, fun_info) do
"#Function<#{uniq(fun_info)} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
end
defp extract_name(name) do
case :binary.split(atom_to_binary(name), "-", [:global]) do
["", name | _] -> name
_ -> name
end
end
defp uniq(fun_info) do
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> list_to_binary(pid_to_list(pid))
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
end
end
defimpl Inspect, for: Port do
def inspect(port, _opts) do
list_to_binary :erlang.port_to_list(port)
iolist_to_binary :erlang.port_to_list(port)
end
end
defimpl Inspect, for: Reference do
def inspect(ref, _opts) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> list_to_binary(rest)
"#Reference" <> iolist_to_binary(rest)
end
end
+74 -40
View File
@@ -1,5 +1,5 @@
defmodule Inspect.Algebra do
@moduledoc %B"""
@moduledoc %S"""
A set of functions for creating and manipulating algebra
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
@@ -76,11 +76,33 @@ defmodule Inspect.Algebra do
# Functional interface to `doc` records
@type doc :: :doc_nil | :doc_cons_t | :doc_nest_t | :doc_break_t | :doc_group_t | binary
defrecordp :doc_cons, [left: :doc_nil, right: :doc_nil]
defrecordp :doc_nest, [indent: 1, doc: :doc_nil]
defrecordp :doc_break, [str: " "]
defrecordp :doc_group, [doc: :doc_nil]
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
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
@@ -106,14 +128,18 @@ defmodule Inspect.Algebra do
"TastelessArtosis"
"""
@spec concat(doc, doc) :: :doc_cons_t
def concat(x, y), do: doc_cons(left: x, right: y)
@spec concat(t, t) :: doc_cons_t
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([doc]) :: :doc_cons_t
def concat(docs), do: folddoc(docs, concat(&1, &2))
@spec concat([t]) :: doc_cons_t
def concat(docs) do
folddoc(docs, &concat(&1, &2))
end
@doc """
Nests document entity `x` positions deep. Nesting will be
@@ -126,11 +152,16 @@ defmodule Inspect.Algebra do
" 6"
"""
@spec nest(doc, 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)
@spec nest(t, non_neg_integer) :: doc_nest_t
def nest(x, 0) when is_doc(x) do
x
end
@doc %B"""
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
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
@@ -151,26 +182,26 @@ defmodule Inspect.Algebra do
"aaaaaaaaaaaaaaaaaaaa\nb"
"""
@spec break(binary) :: :doc_break_t
@spec break(binary) :: doc_break_t
def break(s) when is_binary(s), do: doc_break(str: s)
@spec break() :: :doc_break_t
@spec break() :: doc_break_t
def break(), do: doc_break(str: @break)
@doc """
Inserts a break between two docs. See `break/1` for more info.
"""
@spec glue(doc, doc) :: :doc_cons_t
@spec glue(t, t) :: doc_cons_t
def glue(x, y), do: concat(x, concat(break, y))
@doc """
Inserts a break, passed as the second argument, between two docs,
the first and the third arguments.
"""
@spec glue(doc, binary, doc) :: :doc_cons_t
@spec glue(t, binary, t) :: doc_cons_t
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
@doc %B"""
@doc %S"""
Returns a group containing the specified document.
## Examples
@@ -197,8 +228,10 @@ defmodule Inspect.Algebra do
"Hello,\nA B"
"""
@spec group(doc) :: :doc_group_t
def group(d), do: doc_group(doc: d)
@spec group(t) :: doc_group_t
def group(d) when is_doc(d) do
doc_group(doc: d)
end
@doc """
Inserts a mandatory single space between two document entities.
@@ -210,10 +243,10 @@ defmodule Inspect.Algebra do
"Hughes Wadler"
"""
@spec space(doc, doc) :: :doc_cons_t
@spec space(t, t) :: doc_cons_t
def space(x, y), do: concat(x, concat(" ", y))
@doc %B"""
@doc %S"""
Inserts a mandatory linebreak between two document entities.
## Examples
@@ -223,7 +256,7 @@ defmodule Inspect.Algebra do
"Hughes\nWadler"
"""
@spec line(doc, doc) :: :doc_cons_t
@spec line(t, t) :: doc_cons_t
def line(x, y), do: concat(x, concat(@newline, y))
@doc """
@@ -240,14 +273,14 @@ defmodule Inspect.Algebra do
"A!B"
"""
@spec folddoc([any], ((doc, [doc]) -> doc)) :: doc
@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))
# Elixir conveniences
@doc %B"""
@doc %S"""
Surrounds a document with characters.
Puts the document between left and right enclosing and nesting it.
@@ -261,28 +294,28 @@ defmodule Inspect.Algebra do
"[a\n b]"
"""
@spec surround(binary, doc, binary) :: doc
@spec surround(binary, t, binary) :: t
def surround(left, doc, right) do
group concat [left, nest(doc, @nesting), right]
end
@doc %B"""
@doc %S"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
## 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, ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> doc)) :: doc
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
def surround_many(left, [], right, _, _fun) do
concat(left, right)
end
@@ -323,7 +356,7 @@ defmodule Inspect.Algebra do
and returns the string representation of the best layout for the
document to fit in the given width.
"""
@spec pretty(doc, non_neg_integer) :: binary
@spec pretty(t, non_neg_integer | :infinity) :: binary
def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
render(sdoc)
@@ -336,15 +369,15 @@ defmodule Inspect.Algebra do
# Records representing __simple__ documents, already on a fixed layout
# Those are generalized by `sdoc` type.
@type sdoc :: :s_nil | :s_text_t | :s_line_t
defrecordp :s_text, [str: "", sdoc: :s_nil]
defrecordp :s_line, [indent: 1, sdoc: :s_nil] # newline + spaces
@type sdoc :: :s_nil | s_text_t | s_line_t
defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc
# Record representing the document mode to be rendered: __flat__ or __broken__
@type mode :: :flat | :break
# Record representing the document mode to be rendered: flat or broken
@typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{ integer, mode, doc }]) :: boolean
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
def fits?(:infinity, _), do: true # no pretty printing
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
@@ -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, doc }]) :: 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))
+70
View File
@@ -0,0 +1,70 @@
defmodule Integer do
@moduledoc """
Functions for working with integers.
"""
import Bitwise
@doc """
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: (unquote(n) &&& 1) == 1
end
@doc """
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: (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
+92 -77
View File
@@ -1,50 +1,61 @@
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
shortcut to Erlang's `:standard_io` and `:standard_error`.
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_binary` is invoked in 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
quote do
is_list(unquote(data)) or is_binary(unquote(data))
end
end
@doc """
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* `:eof` - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
* `{:error, reason}` - Other (rare) error condition,
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
:io.get_line(map_dev(device), "")
:io.get_line(map_dev(device), '')
end
def read(device, count) when count >= 0 do
:io.get_chars(map_dev(device), "", count)
:io.get_chars(map_dev(device), '', count)
end
@doc """
@@ -53,12 +64,13 @@ defmodule IO do
* `data` - The input characters.
* :eof - End of file was encountered.
* `:eof` - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
* `{:error, reason}` - Other (rare) error condition,
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
@@ -75,21 +87,6 @@ defmodule IO do
end
end
@doc false
def readline(device // group_leader()) do
IO.write "[WARNING] IO.readline(device) is deprecated, please use IO.read(device, :line) instead\n#{Exception.format_stacktrace}"
:io.get_line(map_dev(device), "")
end
@doc false
def binreadline(device // group_leader()) do
IO.write "[WARNING] IO.binreadline(device) is deprecated, please use IO.binread(device, :line) instead\n#{Exception.format_stacktrace}"
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
other -> other
end
end
@doc """
Writes the given argument to the given device.
By default the device is the standard output.
@@ -107,8 +104,9 @@ defmodule IO do
#=> "error"
"""
@spec write(device, chardata) :: :ok
def write(device // group_leader(), item) do
:io.put_chars map_dev(device), to_iodata(item)
:io.put_chars map_dev(device), to_chardata(item)
end
@doc """
@@ -117,23 +115,25 @@ defmodule IO do
Check `write/2` for more information.
"""
def binwrite(device // group_leader(), item) do
:file.write map_dev(device), to_iodata(item)
@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
@doc """
Writes the argument to the device, similarly to write
but adds a new line at the end. The argument is expected
Writes the argument to the device, similar to `write/2`,
but adds a newline at the end. The argument is expected
to be a chardata.
"""
@spec puts(device, chardata) :: :ok
def puts(device // group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_iodata(item), ?\n]
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@doc """
Inspects and writes the given argument to the device
followed by a new line. A set of options can be given.
followed by a newline. A set of options can be given.
It sets by default pretty printing to true and the
width to be the width of the device, with a minimum
@@ -144,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
@@ -151,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)
@@ -167,18 +169,21 @@ defmodule IO do
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, count implies
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, the number of raw bytes. It returns:
Otherwise, `count` is the number of raw bytes to be retrieved.
It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* `:eof` - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
* `{:error, reason}` - Other (rare) error condition,
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
@@ -191,12 +196,13 @@ defmodule IO do
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, count implies
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, the number of raw bytes.
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), to_iodata(prompt), count)
:io.get_chars(map_dev(device), to_chardata(prompt), count)
end
@doc """
@@ -205,66 +211,75 @@ defmodule IO do
* `data` - The characters in the line terminated
by a LF (or end of file).
* :eof - End of file was encountered.
* `:eof` - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
* `{:error, reason}` - Other (rare) error condition,
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), to_iodata(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.
iterated line by line if `:line` is given or by a given
number of codepoints.
This reads the io as utf-8. Check out
`IO.binstream/1` to handle the IO as a raw binary.
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), IO.write(&1)
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
"""
def stream(device) do
stream(map_dev(device), &1, &2)
@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.
"""
def binstream(device) do
binstream(map_dev(device), &1, &2)
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
end
@doc false
def stream(device, acc, fun) do
case read(device, :line) do
def stream(device, what, acc, fun) do
case read(device, what) do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
raise IO.StreamError, reason: reason
data ->
stream(device, fun.(data, acc), fun)
stream(device, what, fun.(data, acc), fun)
end
end
@doc false
def binstream(device, acc, fun) do
case binread(device, :line) do
def binstream(device, what, acc, fun) do
case binread(device, what) do
:eof ->
acc
{ :error, reason } ->
raise File.IteratorError, reason: reason
raise IO.StreamError, reason: reason
data ->
binstream(device, fun.(data, acc), fun)
binstream(device, what, fun.(data, acc), fun)
end
end
@@ -273,6 +288,6 @@ defmodule IO do
defp map_dev(:stderr), do: :standard_error
defp map_dev(other), do: other
defp to_iodata(io) when is_list(io) or is_binary(io), do: io
defp to_iodata(other), do: to_binary(other)
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
end
+32 -24
View File
@@ -1,14 +1,14 @@
defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code) do
quote bind_quoted: [name: name, code: code] do
defmacro defsequence(name, code // "", terminator // "m") do
quote bind_quoted: [name: name, code: code, terminator: terminator] do
def unquote(name)() do
"\e[#{unquote(code)}m"
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
{ "\e[#{unquote(code)}m", rest }
{ "\e[#{unquote(code)}#{unquote(terminator)}", rest }
end
end
end
@@ -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
@@ -121,6 +121,13 @@ defmodule IO.ANSI do
@doc "Not overlined"
defsequence :not_overlined, 55
@doc "Send cursor home"
defsequence :home, "", "H"
@doc "Clear screen"
defsequence :clear, "2", "J"
# Catch spaces between codes
defp escape_sequence(<< ?\s, rest :: binary >>) do
escape_sequence(rest)
@@ -131,24 +138,25 @@ defmodule IO.ANSI do
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
end
@doc %B"""
@doc %S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
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
@@ -160,23 +168,23 @@ defmodule IO.ANSI do
end
end
@doc %B"""
@doc %S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
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
@@ -201,7 +209,7 @@ defmodule IO.ANSI do
do_escape(rest, false, emit, emitted, [x|acc])
end
defp do_escape("", false, _emit, emitted, acc) do
{list_to_binary(Enum.reverse(acc)), emitted}
{iolist_to_binary(Enum.reverse(acc)), emitted}
end
defp do_escape_sequence(rest, emit, acc) do
+469 -545
View File
File diff suppressed because it is too large Load Diff
+93 -68
View File
@@ -1,26 +1,26 @@
defmodule Kernel.CLI do
@moduledoc """
Module responsible for controlling Elixir's CLI
"""
@moduledoc false
defrecord Config, commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: []
halt: true, compiler_options: [], errors: [],
verbose_compile: false
# This is the API invoked by Elixir boot process.
@doc false
@doc """
This is the API invoked by Elixir boot process.
"""
def main(argv) do
argv = lc arg inlist argv, do: :unicode.characters_to_binary(arg)
argv = lc arg inlist argv, do: String.from_char_list!(arg)
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
:elixir_code_server.cast({ :argv, argv })
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
@@ -41,13 +41,6 @@ defmodule Kernel.CLI do
at_exit(0)
System.halt(0)
end
rescue
exception ->
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, Exception.format_stacktrace(trace)
System.halt(1)
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
@@ -57,9 +50,7 @@ defmodule Kernel.CLI do
System.halt(0)
kind, reason ->
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
IO.puts :stderr, Exception.format_stacktrace(trace)
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
System.halt(1)
end
end
@@ -72,16 +63,9 @@ defmodule Kernel.CLI do
lc hook inlist hooks do
try do
hook.(status)
rescue
exception ->
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, Exception.format_stacktrace(trace)
catch
kind, reason ->
trace = System.stacktrace
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts :stderr, Exception.format_stacktrace(trace)
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
end
end
@@ -93,13 +77,45 @@ 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)
end
end
defp print_error(:error, exception, trace) do
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, format_stacktrace(trace)
end
defp print_error(kind, reason, trace) do
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts :stderr, format_stacktrace(trace)
end
defp format_stacktrace(stack) do
Exception.format_stacktrace(prune_stacktrace(stack))
end
@elixir_internals [:elixir_compiler, :elixir_module]
defp prune_stacktrace([{ mod, _, _, _ }|t]) when mod in @elixir_internals do
prune_stacktrace(t)
end
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
[]
end
defp prune_stacktrace([h|t]) do
[h|prune_stacktrace(t)]
end
defp prune_stacktrace([]) do
[]
end
# Process shared options
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
@@ -108,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
@@ -126,21 +142,25 @@ 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
process_shared t, config
end
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
process_shared t, config
end
defp process_shared(list, config) do
{ list, config }
end
@@ -160,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
@@ -187,33 +207,37 @@ 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
process_compiler t, config.verbose_compile(true)
end
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
@@ -233,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
@@ -253,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
@@ -276,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
@@ -285,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
@@ -295,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
@@ -308,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
@@ -321,19 +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.uniq(List.concat(files))
files = Enum.filter files, :filelib.is_regular(&1)
files = Enum.map patterns, &Path.wildcard(&1)
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
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
+169
View File
@@ -0,0 +1,169 @@
# 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(pid) do
ets = :gen_server.call(to_pid(pid), :ets, @timeout)
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
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
+52 -35
View File
@@ -1,14 +1,8 @@
defexception Kernel.CompilationError, message: "compilation failed"
defmodule Kernel.ParallelCompiler do
alias :orddict, as: OrdDict
@moduledoc """
A module responsible for compiling files in parallel.
"""
defmacrop default_callback, do: quote(do: fn x -> x end)
@doc """
Compiles the given files.
@@ -17,45 +11,58 @@ defmodule Kernel.ParallelCompiler do
the current file stops being compiled until the dependency is
resolved.
If there is any error during compilation or if warnings_as_errors
is set to true and there is a warning, this function will fail
If there is an error during compilation or if `warnings_as_errors`
is set to `true` and there is a warning, this function will fail
with an exception.
A callback that is invoked every time a file is compiled
with its name can be optionally given as argument.
This function receives a set of callbacks as options:
* `: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
The compiler doesn't care about the return values of the callbacks.
Returns the modules generated by each compiled file.
"""
def files(files, callback // default_callback) do
spawn_compilers(files, nil, callback)
def files(files, callbacks // [])
def files(files, callbacks) when is_list(callbacks) do
spawn_compilers(files, nil, callbacks)
end
@doc """
Compiles the given files to the given path.
Read files/2 for more information.
Read `files/2` for more information.
"""
def files_to_path(files, path, callback // default_callback) when is_binary(path) do
spawn_compilers(files, path, callback)
def files_to_path(files, path, callbacks // [])
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
spawn_compilers(files, path, callbacks)
end
defp spawn_compilers(files, path, callback) do
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
:elixir_code_server.cast(:reset_warnings)
schedulers = max(:erlang.system_info(:schedulers_online), 2)
result = spawn_compilers(files, path, callback, [], [], schedulers, [])
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 CompileError
case :elixir_code_server.call(:compilation_status) do
:ok -> result
:error -> raise Kernel.CompilationError, [], []
:error -> raise CompileError, [], []
end
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(files, output, callback, waiting, queued, schedulers, result) when
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do
wait_for_messages(files, output, callback, waiting, queued, schedulers, result)
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
end
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], output, callback, waiting, queued, schedulers, result) do
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
parent = self()
child = spawn_link fn ->
@@ -76,40 +83,50 @@ defmodule Kernel.ParallelCompiler do
end
end
spawn_compilers(t, output, callback, waiting, [{child, h}|queued], schedulers, result)
spawn_compilers(t, original, output, callbacks, waiting, [{child, h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _output, _callback, [], [], _schedulers, result), do: result
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], output, callback, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
wait_for_messages([], output, callback, waiting, queued, schedulers, result)
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
# No more files, but queue and waiting are not full or do not match
defp spawn_compilers([], output, callback, waiting, queued, schedulers, result) do
wait_for_messages([], output, callback, waiting, queued, schedulers, result)
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
# Wait for messages from child processes
defp wait_for_messages(files, output, callback, waiting, queued, schedulers, result) do
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
receive do
{ :compiled, child, file } ->
callback.(file)
if callback = Keyword.get(callbacks, :each_file) do
callback.(file)
end
new_queued = List.keydelete(queued, child, 0)
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, child, 0)
spawn_compilers(files, output, callback, new_waiting, new_queued, schedulers, result)
{ :module_available, _child, module, binary } ->
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :module_available, child, ref, file, module, binary } ->
if callback = Keyword.get(callbacks, :each_module) do
callback.(file, module, binary)
end
# Release the module loader which is waiting for an ack
child <- { ref, :ack }
new_waiting = release_waiting_processes(module, waiting)
new_result = [{module, binary}|result]
wait_for_messages(files, output, callback, new_waiting, queued, schedulers, new_result)
new_result = [module|result]
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
{ :waiting, child, on } ->
new_waiting = OrdDict.store(child, on, waiting)
spawn_compilers(files, output, callback, new_waiting, queued, schedulers, result)
new_waiting = :orddict.store(child, on, waiting)
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
{ :failure, child, kind, reason, stacktrace } ->
if many_missing?(child, files, waiting, queued) do
IO.puts "== Compilation failed =="
+6 -4
View File
@@ -10,6 +10,8 @@ defmodule Kernel.ParallelRequire do
A callback that is invoked every time a file is required
can be optionally given as argument.
Returns the modules generated by each required file.
"""
def files(files, callback // default_callback) do
schedulers = max(:erlang.system_info(:schedulers_online), 2)
@@ -45,8 +47,8 @@ defmodule Kernel.ParallelRequire do
:erlang.process_flag(:error_handler, handler)
try do
new = Code.require_file(h)
parent <- { :required, self, new, h }
new = Code.require_file(h) || []
parent <- { :required, self, Enum.map(new, &elem(&1, 0)), h }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
@@ -58,9 +60,9 @@ defmodule Kernel.ParallelRequire do
defp wait_for_messages(files, waiting, callback, schedulers, result) do
receive do
{ :required, child, new, file } ->
{ :required, child, mods, file } ->
callback.(file)
spawn_requires(files, List.delete(waiting, child), callback, schedulers, (new || []) ++ result)
spawn_requires(files, List.delete(waiting, child), callback, schedulers, mods ++ result)
{ :failure, _child, kind, reason, stacktrace } ->
:erlang.raise(kind, reason, stacktrace)
end
+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
+392 -149
View File
@@ -9,38 +9,33 @@ 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__`,
`__scope__` and `__aliases__`), which are not intended to be
called directly by the developer but they appear in quoted
contents since they are essential in Elixir's constructions.
contents since they are essential in Elixir's constructs.
"""
@doc """
Defines a new tuple.
Only two item tuples are considered literals in Elixir.
Therefore all other tuples are represented in the AST
as a call to the special form `:{}`.
## Examples
iex> { 1, 2, 3 }
{ 1, 2, 3 }
iex> quote do: { 1, 2, 3 }
{ :{}, [], [1,2,3] }
"""
defmacro :{}.(args)
@doc """
Defines a new list.
## Examples
iex> [ 1, 2, 3 ]
[ 1, 2, 3 ]
"""
defmacro :[].(args)
@doc """
Defines a new bitstring.
@@ -51,41 +46,50 @@ defmodule Kernel.SpecialForms do
## Bitstring types
A bitstring may contain many parts and those may have
specific types. Most of the time, Elixir will figure out
the part's type and won't require any work from you:
A bitstring is made of many segments. Each segment has a
type, which defaults to integer:
iex> <<102, "oo">>
"foo"
iex> <<1, 2, 3>>
<<1, 2, 3>>
Above we have two parts: the first is an integer and the
second is a binary. If we use any other Elixir expression,
Elixir can no longer guess the type:
Elixir also accepts by default the segment to be a literal
string or a literal char list, which are by expanded to integers:
iex> <<0, "foo">>
<<0, 102, 111, 111>>
Any other type needs to be explicitly tagged. For example,
in order to store a float type in the binary, one has to do:
iex> <<3.14 :: float>>
<<64, 9, 30, 184, 81, 235, 133, 31>>
This also means that variables need to be explicitly tagged,
otherwise Elixir defaults to integer:
iex> rest = "oo"
...> <<102, rest>>
iex> <<102, rest>>
** (ArgumentError) argument error
When a variable or expression is given as a binary part,
Elixir defaults the type of that part to an unsigned
little-endian integer. In the example above, since we haven't
specified a type, Elixir expected an integer but we passed a
binary, resulting in `ArgumentError`. We can solve this by
explicitly tagging it as a binary:
We can solve this by explicitly tagging it as a binary:
<<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.
The utf8, utf16, and utf32 types are for UTF code points.
The utf8, utf16, and utf32 types are for UTF code points. They
can also be applied to literal strings and char lists:
iex> <<"foo" :: utf16>>
<<0,102,0,111,0,111>>
The bits type is an alias for bitstring. The bytes type is an
alias for binary.
@@ -111,7 +115,7 @@ defmodule Kernel.SpecialForms do
Signedness is only relevant on integers.
The endianness of a part can be big, little or native (the
The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Passing
many options can be done by giving a list:
@@ -126,9 +130,9 @@ defmodule Kernel.SpecialForms do
Endianness only makes sense for integers and some UTF code
point types (utf16 and utf32).
Finally, we can also specify size and unit for each part. The
Finally, we can also specify size and unit for each segment. The
unit is multiplied by the size to give the effective size of
the part:
the segment:
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
"foo"
@@ -141,7 +145,7 @@ defmodule Kernel.SpecialForms do
In the example above, the first two expressions matches
because the string "foo" takes 24 bits and we are matching
against a part of 24 bits as well, 8 of which are taken by
against a segment of 24 bits as well, 8 of which are taken by
the integer 102 and the remaining 16 bits are specified on
the rest. On the last example, we expect a rest with size 32,
which won't match.
@@ -170,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
@@ -180,12 +297,12 @@ defmodule Kernel.SpecialForms do
alias MyKeyword, as: Keyword
end
In the example above, we have set up `MyKeyword` to be alias
In the example above, we have set up `MyKeyword` to be aliased
as `Keyword`. So now, any reference to `Keyword` will be
automatically replaced by `MyKeyword`.
In case one wants to access the original `Keyword`, it can be done
by accessing Elixir:
by accessing `Elixir`:
Keyword.values #=> uses MyKeyword.values
Elixir.Keyword.values #=> uses Keyword.values
@@ -204,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
@@ -237,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.
@@ -258,23 +388,27 @@ defmodule Kernel.SpecialForms do
import List
A developer can change this behavior to include all macros and
functions, regardless if it starts with underscore, by passing
`:all` as first argument:
A developer can filter to import only macros or functions via
the only option:
import :all, List
import List, only: :functions
import List, only: :macros
It can also be customized to import only all functions or
all macros:
import :functions, List
import :macros, List
Alternatively, Elixir allows a developer to specify `:only`
or `:except` as a fine grained control on what to import (or
not):
Alternatively, Elixir allows a developer to pass pairs of
name/arities to `:only` or `:except` as a fine grained control
on what to import (or not):
import List, only: [flatten: 1]
import String, except: [split: 2]
Notice that calling `except` for a previously declared `import`
simply filters the previously imported elements. For example:
import List, only: [flatten: 1, keyfind: 3]
import List, except: [flatten: 1]
After the two import calls above, only `List.keyfind/3` will be
imported.
## Lexical scope
@@ -296,15 +430,9 @@ defmodule Kernel.SpecialForms do
In the example above, we imported macros from `MyMacros`,
replacing the original `if/2` implementation by our own
during that specific function. All other functions in that
within that specific function. All other functions in that
module will still be able to use the original one.
## Alias/Require shortcut
All imported modules are also required by default. `import`
also accepts `as:` as an option so it automatically sets up
an alias. Please check `alias` for more information.
## Warnings
If you import a module and you don't use any of the imported
@@ -328,22 +456,25 @@ 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
Returns the current module name as an atom or `nil` otherwise.
Although the module can be accessed in the `__ENV__`, this macro
is a convenient shortcut.
"""
defmacro __MODULE__
@doc """
Returns the current file name as a binary.
Although the file can be accessed in the __ENV__, this macro
Although the file can be accessed in the `__ENV__`, this macro
is a convenient shortcut.
"""
defmacro __FILE__
@@ -369,7 +500,7 @@ defmodule Kernel.SpecialForms do
{ :sum, [], [1, 2, 3] }
The tuple above represents a function call to sum passing 1, 2 and
The tuple above represents a function call to `sum` passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
@@ -382,14 +513,14 @@ defmodule Kernel.SpecialForms do
## Options
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control which quote is
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
@@ -405,7 +536,7 @@ defmodule Kernel.SpecialForms do
## Hygiene and context
Elixir macros are hygienic via means of deferred resolution.
Elixir macros are hygienic by 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.
@@ -424,14 +555,17 @@ defmodule Kernel.SpecialForms do
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.
`ContextSample` module and only code generated by this module
will be able to access that particular `world` variable. Not
only variables hygiene, but also imports and aliases hygiene
are delimited by context.
While this means macros from the same module could have
conflicting variables, it also allows different quotes from
the same module to access them.
The context can be disabled or changed by explicitly setting
the context option. All hygiene mechanisms are based on such
the `context` option. All hygiene mechanisms are based on such
context and we are going to explore each of them in the following
subsections.
@@ -469,31 +603,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
#=> no 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:
@@ -550,10 +700,9 @@ defmodule Kernel.SpecialForms do
#=> "world"
end
## Hygiene in imports
Similar to aliases, imports in Elixir hygienic. Consider the
Similar to aliases, imports in Elixir are hygienic. Consider the
following code:
defmodule Hygiene do
@@ -576,7 +725,7 @@ defmodule Kernel.SpecialForms do
Elixir is smart enough to delay the resolution to the latest
moment possible. So, if you call `size("hello")` inside quote,
but no `size/1` function is available, it is then expanded on
but no `size/1` function is available, it is then expanded in
the caller:
defmodule Lazy do
@@ -597,45 +746,45 @@ defmodule Kernel.SpecialForms do
Lazy.return_size #=> 2
As in aliases, imports expansion can be explicitly disabled
As in aliases, import expansion can be explicitly disabled
via the `hygiene: [imports: false]` option.
## Stacktrace information
One of Elixir goals is to provide proper stacktrace whenever there is an
exception. In order to work properly with macros, the default behavior
in quote is to 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
Elixir quote/unquote mechanisms provides a functionality called
unquote fragments. Unquote fragments provide an easy to generate
unquote fragments. Unquote fragments provide an easy way to generate
functions on the fly. Consider this example:
kv = [foo: 1, bar: 2]
@@ -670,7 +819,7 @@ defmodule Kernel.SpecialForms do
code fails.
This is actually a common pitfall when developing macros. In
practive, we want to avoid doing work at compilation time as
practice, we want to avoid doing work at compilation time as
much as we can. That said, let's attempt to improve our macro:
defmacro defkv(kv) do
@@ -702,8 +851,8 @@ 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)
@@ -718,10 +867,6 @@ defmodule Kernel.SpecialForms do
@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)
@@ -813,7 +958,10 @@ 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}]
Note: Differently from Erlang, Elixir comprehensions filters
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
Erlang example:
@@ -830,10 +978,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"
@@ -842,6 +991,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:
@@ -853,34 +1017,113 @@ defmodule Kernel.SpecialForms do
defmacro __block__(args)
@doc """
This is the special form used whenever we have to temporarily
change the scope information of a block. Used when `quote` is
invoked with `location: :keep` to execute a given block as if
it belonged to another file.
Captures an anonymous function.
The most common format to capture a function is via module,
name and arity:
iex> fun = &Kernel.is_atom/1
iex> fun.(:atom)
true
iex> fun.("string")
false
Local functions, including private ones, and imported ones
can also be captured by omitting the module name:
&local_function/1
A capture also allows the captured functions to be partially
applied, for example:
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 = &is_record(&1, &2)
iex> fun.(1..3, Range)
true
Since operators are calls, they are also supported, although
they require explicit parentheses around:
iex> fun = &(&1 + &2)
iex> fun.(1, 2)
3
And even more complex call expressions:
iex> fun = &(&1 + &2 + &3)
iex> fun.(1, 2, 3)
6
Record-like calls are also allowed:
iex> fun = &(&1.first)
iex> fun.(1..3)
1
Remember tuple and lists are represented as calls in the AST and
therefore are also allowed:
iex> fun = &{&1, &2}
iex> fun.(1, 2)
{ 1, 2 }
iex> fun = &[&1|&2]
iex> fun.(1, 2)
[1|2]
Anything that is not a call is not allowed though, examples:
# An atom is not a call
&:foo
# A var is not a call
var = 1
&var
# A block expression is not a call
&(foo(&1, &2); &3 + &4)
"""
name = :&
defmacro unquote(name)(expr)
@doc """
Internal special form for modifying scope information.
Used when `quote` is invoked with `location: :keep` to execute
a given block as if it belonged to another file.
quote location: :keep, do: 1
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
Check `quote/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
is the list is an atom.
in the list is an atom.
On the other hand, aliases holds some properties:
@@ -888,12 +1131,12 @@ defmodule Kernel.SpecialForms do
2) The tail elements of aliases are guaranteed to always be atoms;
3) When the head element of aliases is the atom :Elixir, no expansion happen;
3) When the head element of aliases is the atom `:Elixir`, no expansion happen;
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
+151 -21
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
@@ -362,23 +368,23 @@ defmodule Keyword do
## 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
"""
@spec update(t, key, (value -> value)) :: t | no_return
def update([{key, value}|keywords], key, fun) do
@spec update!(t, key, (value -> value)) :: t | no_return
def update!([{key, value}|keywords], key, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
def update([{_, _} = e|keywords], key, fun) do
[e|update(keywords, key, fun)]
def update!([{_, _} = e|keywords], key, fun) do
[e|update!(keywords, key, fun)]
end
def update([], key, _fun) when is_atom(key) do
def update!([], key, _fun) when is_atom(key) do
raise(KeyError, key: key)
end
@@ -388,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]
"""
@@ -407,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
+107 -53
View File
@@ -5,43 +5,13 @@ defmodule List do
favor using the Enum API instead of List.
A decision was taken to delegate most functions to
Erlang's standard lib but following Elixir's convention
Erlang's standard library but follow Elixir's convention
of receiving the target (in this case, a list) as the
first argument.
"""
@compile :inline_list_funcs
@doc """
Given a list of lists, concatenates the sublists into a single list.
## Examples
iex> List.concat([[1, [2], 3], [4], [5, 6]])
[1,[2],3,4,5,6]
"""
def concat(list) when is_list(list) do
:lists.append(list)
end
@doc """
Concatenates the list on the right with the list on the left.
This function produces the same result the `++` operator. The only difference
is a minor optimization: when the first list contains only one element, we
simply add it as a head to the second list.
## Examples
iex> List.concat([1, 2, 3], [4, 5, 6])
[1,2,3,4,5,6]
"""
def concat(list, elements) when is_list(list) and is_list(elements) do
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
@@ -52,13 +22,16 @@ defmodule List do
iex> List.delete([1, 2, 3], 1)
[2,3]
iex> List.delete([1, 2, 2, 3], 2)
[1, 2, 3]
"""
def delete(list, item) do
:lists.delete(item, list)
end
@doc """
Duplicates the given element n times in a list.
Duplicates the given element `n` times in a list.
## Examples
@@ -73,23 +46,29 @@ defmodule List do
end
@doc """
Flattens the given `list` of nested lists. An optional
tail can be given that will be added at the end of
the flattened list.
Flattens the given `list` of nested lists.
## Examples
iex> List.flatten([1, [[2], 3]])
[1,2,3]
iex> List.flatten([1, [[2], 3]], [4, 5])
[1,2,3,4,5]
"""
def flatten(list) do
:lists.flatten(list)
end
@doc """
Flattens the given `list` of nested lists.
The list `tail` will be added at the end of
the flattened list.
## Examples
iex> List.flatten([1, [[2], 3]], [4, 5])
[1,2,3,4,5]
"""
def flatten(list, tail) do
:lists.flatten(list, tail)
end
@@ -126,7 +105,7 @@ defmodule List do
end
@doc """
Returns the last element in `list` or nil if the `list` is empty.
Returns the last element in `list` or `nil` if `list` is empty.
## Examples
@@ -146,7 +125,7 @@ defmodule List do
@doc """
Receives a list of tuples and returns the first tuple
where the item at position `position` matches with the
where the item at `position` in the tuple matches the
given `item`.
## Examples
@@ -166,9 +145,9 @@ defmodule List do
end
@doc """
Receives a list of tuples and returns true if there is
a tuple where the item at position `position` matches
with the given `item`.
Receives a list of tuples and returns `true` if there is
a tuple where the item at `position` in the tuple matches
the given `item`.
## Examples
@@ -188,7 +167,7 @@ defmodule List do
@doc """
Receives a list of tuples and replaces the item
identified by `key` at position `pos` if it exists.
identified by `key` at `position` if it exists.
## Examples
@@ -200,9 +179,26 @@ defmodule List do
:lists.keyreplace(key, position + 1, list, new_tuple)
end
@doc """
Receives a list of tuples and sorts the items
at `position` of the tuples. The sort is stable.
## Examples
iex> List.keysort([a: 5, b: 1, c: 3], 1)
[b: 1, c: 3, a: 5]
iex> List.keysort([a: 5, c: 1, b: 3], 0)
[a: 5, b: 3, c: 1]
"""
def keysort(list, position) do
:lists.keysort(position + 1, list)
end
@doc """
Receives a list of tuples and replaces the item
identified by `key` at position `pos`. If the item
identified by `key` at `position`. If the item
does not exist, it is added to the end of the list.
## Examples
@@ -210,6 +206,9 @@ defmodule List do
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
[a: 3, b: 2]
iex> List.keystore([a: 1, b: 2], :c, 0, { :c, 3 })
[a: 1, b: 2, c: 3]
"""
def keystore(list, key, position, new_tuple) do
:lists.keystore(key, position + 1, list, new_tuple)
@@ -217,8 +216,8 @@ defmodule List do
@doc """
Receives a list of tuples and deletes the first tuple
where the item at position `position` matches with the
given `item`. Returns the new tuple.
where the item at `position` matches the
given `item`. Returns the new list.
## Examples
@@ -239,13 +238,19 @@ defmodule List do
@doc """
Wraps the argument in a list.
If the argument is already a list, returns the list.
If the argument is nil, returns an empty list.
If the argument is `nil`, returns an empty list.
## Examples
iex> List.wrap("hello")
["hello"]
iex> List.wrap([1, 2, 3])
[1,2,3]
iex> List.wrap(nil)
[]
"""
def wrap(list) when is_list(list) do
list
@@ -290,12 +295,12 @@ 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 """
Returns a list with an inserted value at specified index. Note that the index
is capped at the list length and that negative indicies wraps around at the
Returns a list with `value` inserted at the specified `index`. Note that `index`
is capped at the list length. Negative indices indicate an offset from the
end of the list.
## Examples
@@ -307,19 +312,68 @@ defmodule List do
[1, 2, 3, 0]
iex> List.insert_at([1, 2, 3], -1, 0)
[1, 2, 0, 3]
[1, 2, 3, 0]
iex> List.insert_at([1, 2, 3], -10, 0)
[0, 1, 2, 3]
"""
def insert_at(list, index, value) do
if index < 0 do
do_insert_at(list, length(list) + index, value)
do_insert_at(list, length(list) + index + 1, value)
else
do_insert_at(list, index, value)
end
end
@doc """
Returns a list with a replaced value at the specified `index`. Negative indices
indicate an offset from the end of the list. If `index` is out of bounds, the
original `list` is returned.
## Examples
iex> List.replace_at([1, 2, 3], 0, 0)
[0, 2, 3]
iex> List.replace_at([1, 2, 3], 10, 0)
[1, 2, 3]
iex> List.replace_at([1, 2, 3], -1, 0)
[1, 2, 0]
iex> List.replace_at([1, 2, 3], -10, 0)
[1, 2, 3]
"""
def replace_at(list, index, value) do
if index < 0 do
do_replace_at(list, length(list) + index, value)
else
do_replace_at(list, index, value)
end
end
## Helpers
# replace_at
defp do_replace_at([], _index, _value) do
[]
end
defp do_replace_at(list, index, _value) when index < 0 do
list
end
defp do_replace_at([_old|rest], 0, value) do
[ value | rest ]
end
defp do_replace_at([h|t], index, value) do
[ h | do_replace_at(t, index - 1, value) ]
end
# insert_at
defp do_insert_at([], _index, value) do
+21 -9
View File
@@ -1,5 +1,5 @@
defprotocol List.Chars do
@moduledoc %B"""
@moduledoc %S"""
The List.Chars protocol is responsible for
converting a structure to a list (only if applicable).
The only function required to be implemented is
@@ -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
@@ -19,20 +17,34 @@ defimpl List.Chars, for: Atom do
end
defimpl List.Chars, for: BitString do
def to_char_list(bitstring), do: bitstring_to_list(bitstring)
@doc """
Returns the given binary converted to a char list.
"""
def to_char_list(thing) when is_binary(thing) do
String.to_char_list!(thing)
end
def to_char_list(thing) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: thing,
description: "cannot convert a bitstring to a char list"
end
end
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)
+38 -35
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,33 +73,35 @@ 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() }
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
acc = { [], [] }
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
{ [{k, v}|take], drop }
else
{ take, [{k, v}|drop] }
end
end
{Enum.reverse(take), Enum.reverse(drop)}
end
def take(dict, keys) do
@@ -109,15 +112,15 @@ defmodule ListDict do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
def update([{key, value}|dict], key, fun) do
def update!([{key, value}|dict], key, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
def update([{_, _} = e|dict], key, fun) do
[e|update(dict, key, fun)]
def update!([{_, _} = e|dict], key, fun) do
[e|update!(dict, key, fun)]
end
def update([], key, _fun) do
def update!([], key, _fun) do
raise(KeyError, key: key)
end
@@ -136,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
+236 -169
View File
@@ -5,40 +5,89 @@ 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 """
Returns a list of binary operators. This is available
as a macro so it can be used in guard clauses.
"""
@doc false
defmacro binary_ops do
[
:===, :!==,
[ :===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^
]
:<<<, :>>>, :|||, :&&&, :^^^, :~~~ ]
end
@doc """
Returns a list of unary operators. This is available
as a macro so it can be used in guard clauses.
"""
@doc false
defmacro unary_ops do
[:!, :@, :^, :not, :+, :-, :~~~, :&]
end
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
::: -> {:right, 30}
:when -> {:right, 40}
o when o in [:inlist, :inbits] -> {:left, 50}
:// -> {:right, 60}
:| -> {:left, 70}
:= -> {:right, 80}
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
:in -> {:left, 170}
:.. -> {:left, 200}
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
o when o in [:<>] -> {:right, 230}
:^^^ -> {: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.
This is useful for macros that want to provide the same
arguments syntax available in def/defp/defmacro and friends.
argument syntax available in def/defp/defmacro and friends.
## Examples
@@ -48,6 +97,7 @@ defmodule Macro do
extract_args(quote do: 1.(1, 2, 3)) == :error
"""
@spec extract_args(Macro.t) :: { atom, [Macro.t] } | :error
def extract_args(expr) do
:elixir_clauses.extract_args(expr)
end
@@ -56,7 +106,7 @@ defmodule Macro do
Recursively escapes a value so it can be inserted
into a syntax tree.
One may pass `unquote: true` to `Macro.escape/2`
One may pass `unquote: true` to `escape/2`
which leaves unquote statements unescaped, effectively
unquoting the contents on escape.
@@ -72,14 +122,16 @@ defmodule Macro do
1
"""
@spec escape(term) :: Macro.t
@spec escape(term, Keyword.t) :: Macro.t
def escape(expr, opts // []) do
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc %B"""
@doc %S"""
Unescape the given chars. This is the unescaping behavior
used by default in Elixir single- and double-quoted strings.
Check `unescape_binary/2` for information on how to customize
Check `unescape_string/2` for information on how to customize
the escaping map.
In this setup, Elixir will escape the following: `\a`, `\b`,
@@ -87,23 +139,25 @@ defmodule Macro do
also escaped according to the latin1 set they represent.
This function is commonly used on sigil implementations
(like `%r`, `%b` and others).
(like `%r`, `%b` and others) which receive a raw, unescaped
string.
## Examples
iex> Macro.unescape_binary("example\\n")
iex> Macro.unescape_string("example\\n")
"example\n"
In the example above, we pass a string with `\n` escaped
and we return a version with it unescaped.
"""
def unescape_binary(chars) do
@spec unescape_string(String.t) :: String.t
def unescape_string(chars) do
:elixir_interpolation.unescape_chars(chars)
end
@doc %B"""
@doc %S"""
Unescape the given chars according to the map given.
Check `unescape_binary/1` if you want to use the same map
Check `unescape_string/1` if you want to use the same map
as Elixir single- and double-quoted strings.
## Map
@@ -124,7 +178,7 @@ defmodule Macro do
def unescape_map(?v), do: ?\v
def unescape_map(e), do: e
If the `unescape_map` function returns false. The char is
If the `unescape_map` function returns `false`. The char is
not escaped and `\` is kept in the char list.
## Octals
@@ -134,23 +188,24 @@ defmodule Macro do
## Hex
Octals will by default be escaped unless the map function
Hexadecimals will by default be escaped unless the map function
returns false for ?x.
## Examples
Using the unescape_map defined above is easy:
Macro.unescape_binary "example\\n", unescape_map(&1)
Macro.unescape_string "example\\n", &unescape_map(&1)
"""
def unescape_binary(chars, map) do
@spec unescape_string(String.t, (non_neg_integer -> non_neg_integer | false)) :: String.t
def unescape_string(chars, map) do
:elixir_interpolation.unescape_chars(chars, map)
end
@doc """
Unescape the given tokens according to the default map.
Check `unescape_binary/1` and `unescape_binary/2` for more
Check `unescape_string/1` and `unescape_string/2` for more
information about unescaping.
Only tokens that are binaries are unescaped, all others are
@@ -158,24 +213,20 @@ defmodule Macro do
sigils. Check the implementation of `Kernel.sigil_b`
for examples.
"""
@spec unescape_tokens([Macro.t]) :: [Macro.t]
def unescape_tokens(tokens) do
:elixir_interpolation.unescape_tokens(tokens)
end
@doc """
Unescape the given tokens according to the given map.
Check `unescape_tokens/1` and `unescape_binary/2` for more information.
Check `unescape_tokens/1` and `unescape_string/2` for more information.
"""
@spec unescape_tokens([Macro.t], (non_neg_integer -> non_neg_integer | false)) :: [Macro.t]
def unescape_tokens(tokens, map) do
:elixir_interpolation.unescape_tokens(tokens, map)
end
@doc false
def to_binary(tree) do
IO.write "[WARNING] Macro.to_binary is deprecated, please use Macro.to_string instead\n#{Exception.format_stacktrace}"
to_string(tree)
end
@doc """
Converts the given expression to a binary.
@@ -185,176 +236,213 @@ defmodule Macro do
"foo.bar(1, 2, 3)"
"""
def to_string(tree)
@spec to_string(Macro.t) :: String.t
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
def to_string(tree, fun // fn(_ast, string) -> string end)
# Variables
def to_string({ var, _, atom }) when is_atom(atom) do
atom_to_binary(var, :utf8)
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
fun.(ast, atom_to_binary(var))
end
# Aliases
def to_string({ :__aliases__, _, refs }) do
Enum.map_join(refs, ".", call_to_string(&1))
def to_string({ :__aliases__, _, refs } = ast, fun) do
fun.(ast, Enum.map_join(refs, ".", &call_to_string(&1, fun)))
end
# Blocks
def to_string({ :__block__, _, [expr] }) do
to_string(expr)
def to_string({ :__block__, _, [expr] } = ast, fun) do
fun.(ast, to_string(expr, fun))
end
def to_string({ :__block__, _, _ } = expr) do
block = adjust_new_lines block_to_string(expr), "\n "
"(\n " <> block <> "\n)"
def to_string({ :__block__, _, _ } = ast, fun) do
block = adjust_new_lines block_to_string(ast, fun), "\n "
fun.(ast, "(\n " <> block <> "\n)")
end
# Bits containers
def to_string({ :<<>>, _, args }) do
"<<" <> Enum.map_join(args, ", ", to_string(&1)) <> ">>"
def to_string({ :<<>>, _, args } = ast, fun) do
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
"<" <> rest -> "<< <" <> rest <> " >>"
rest -> "<<" <> rest <> ">>"
end)
end
# Tuple containers
def to_string({ :{}, _, args }) do
"{" <> Enum.map_join(args, ", ", to_string(&1)) <> "}"
end
# List containers
def to_string({ :[], _, args }) do
"[" <> Enum.map_join(args, ", ", to_string(&1)) <> "]"
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]] })
def to_string({ :fn, _, [{ :->, _, [{_, _, tuple}] } = arrow] } = ast, fun)
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
"fn " <> arrow_to_string(arrow) <> " end"
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
end
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
"fn " <> block_to_string(block) <> "\nend"
def to_string({ :fn, _, [{ :->, _, [_] } = block] } = ast, fun) do
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
end
def to_string({ :fn, _, [[do: block]] }) do
block = adjust_new_lines block_to_string(block), "\n "
"fn\n " <> block <> "\nend"
end
# Partial call
def to_string({ :&, _, [num] }) do
"&#{num}"
def to_string({ :fn, _, [block] } = ast, fun) do
block = adjust_new_lines block_to_string(block, fun), "\n "
fun.(ast, "fn\n " <> block <> "\nend")
end
# left -> right
def to_string({ :->, _, _ } = arrow) do
"(" <> arrow_to_string(arrow, true) <> ")"
def to_string({ :->, _, _ } = ast, fun) do
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
end
# Binary ops
def to_string({ op, _, [left, right] }) when op in binary_ops do
op_to_string(left) <> " #{op} " <> op_to_string(right)
def to_string({ op, _, [left, right] } = ast, fun) when op in binary_ops do
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
end
# Splat when
def to_string({ :when, _, args }) do
{ left, right } = :elixir_tree_helpers.split_last(args)
"(" <> Enum.map_join(left, ", ", to_string(&1)) <> ") when " <> to_string(right)
def to_string({ :when, _, args } = ast, fun) do
{ left, right } = :elixir_utils.split_last(args)
fun.(ast, "(" <> Enum.map_join(left, ", ", &to_string(&1, fun)) <> ") when " <> to_string(right, fun))
end
# Unary ops
def to_string({ :not, _, [arg] }) do
"not " <> to_string(arg)
def to_string({ :not, _, [arg] } = ast, fun) do
fun.(ast, "not " <> to_string(arg, fun))
end
def to_string({ op, _, [arg] }) when op in unary_ops do
atom_to_binary(op, :utf8) <> to_string(arg)
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
end
# Access
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
fun.(ast, to_string(left, fun) <> to_string(right, fun))
end
# All other calls
def to_string({ target, _, args }) when is_list(args) do
{ list, last } = :elixir_tree_helpers.split_last(args)
case is_kw_blocks?(last) do
true -> call_to_string_with_args(target, list) <> kw_blocks_to_string(last)
false -> call_to_string_with_args(target, args)
end
def to_string({ target, _, args } = ast, fun) when is_list(args) do
{ list, last } = :elixir_utils.split_last(args)
fun.(ast, case is_kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
end
# Two-item tuples
def to_string({ left, right }) do
to_string({ :{}, [], [left, right] })
def to_string({ left, right }, fun) do
to_string({ :{}, [], [left, right] }, fun)
end
# Lists
def to_string(list) when is_list(list) do
to_string({ :[], [], list })
def to_string(list, fun) when is_list(list) do
if Keyword.keyword?(list) do
fun.(list, "[" <> kw_list_to_string(list, fun) <> "]")
else
fun.(list, "[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]")
end
end
# All other structures
def to_string(other), do: inspect(other, raw: true)
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
# Block keywords
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
defp is_kw_blocks?([_|_] = kw) do
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
Enum.all?(kw, &match?({x, _} when x in kw_keywords, &1))
end
defp is_kw_blocks?(_), do: false
defp module_to_string(atom) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_string(other), do: call_to_string(other)
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) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_string({ :., _, [arg] }), do: module_to_string(arg) <> "."
defp call_to_string({ :., _, [left, right] }), do: module_to_string(left) <> "." <> call_to_string(right)
defp call_to_string(other), do: to_string(other)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
defp call_to_string_with_args(target, args) do
args = Enum.map_join(args, ", ", to_string(&1))
call_to_string(target) <> "(" <> args <> ")"
defp call_to_string_with_args(target, args, fun) do
{ list, last } = :elixir_utils.split_last(args)
target = call_to_string(target, fun)
case last != [] and Keyword.keyword?(last) do
true ->
args = Enum.map_join(list, ", ", &to_string(&1, fun))
if list != [], do: args = args <> ", "
args = args <> kw_list_to_string(last, fun)
target <> "(" <> args <> ")"
false ->
args = Enum.map_join(args, ", ", &to_string(&1, fun))
target <> "(" <> args <> ")"
end
end
defp kw_blocks_to_string(kw) do
defp kw_blocks_to_string(kw, fun) do
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
case Keyword.has_key?(kw, x) do
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x))
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x), fun)
false -> acc
end
end) <> "end"
end
defp kw_block_to_string(key, value) do
block = adjust_new_lines block_to_string(value), "\n "
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
defp kw_block_to_string(key, value, fun) do
block = adjust_new_lines block_to_string(value, fun), "\n "
atom_to_binary(key) <> "\n " <> block <> "\n"
end
defp block_to_string({ :->, _, exprs }) do
defp block_to_string({ :->, _, exprs }, fun) do
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
left = comma_join_or_empty_paren(left, false)
left <> "->\n " <> adjust_new_lines block_to_string(right), "\n "
left = comma_join_or_empty_paren(left, fun, false)
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
end)
end
defp block_to_string({ :__block__, _, exprs }) do
Enum.map_join(exprs, "\n", to_string(&1))
defp block_to_string({ :__block__, _, exprs }, fun) do
Enum.map_join(exprs, "\n", &to_string(&1, fun))
end
defp block_to_string(other), do: to_string(other)
defp block_to_string(other, fun), do: to_string(other, fun)
defp op_to_string({ op, _, [_, _] } = expr) when op in binary_ops do
"(" <> to_string(expr) <> ")"
defp kw_list_to_string(list, fun) do
Enum.map_join(list, ", ", fn { key, value } ->
atom_to_binary(key) <> ": " <> to_string(value, fun)
end)
end
defp op_to_string(expr), do: to_string(expr)
defp parenthise(expr, fun) do
"(" <> to_string(expr, fun) <> ")"
end
defp arrow_to_string({ :->, _, pairs }, paren // false) do
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in binary_ops do
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
{ _, prec } = binary_op_props(op)
cond do
parent_prec < prec -> to_string(expr, fun)
parent_prec > prec -> parenthise(expr, fun)
true ->
# parent_prec == prec, so look at associativity.
if parent_assoc == side do
to_string(expr, fun)
else
parenthise(expr, fun)
end
end
end
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
defp arrow_to_string({ :->, _, pairs }, fun, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
left = comma_join_or_empty_paren(left, paren)
left <> "-> " <> to_string(right)
left = comma_join_or_empty_paren(left, fun, paren)
left <> "-> " <> to_string(right, fun)
end)
end
defp comma_join_or_empty_paren([], true), do: "() "
defp comma_join_or_empty_paren([], false), do: ""
defp comma_join_or_empty_paren([], _fun, true), do: "() "
defp comma_join_or_empty_paren([], _fun, false), do: ""
defp comma_join_or_empty_paren(left, _) do
Enum.map_join(left, ", ", to_string(&1)) <> " "
defp comma_join_or_empty_paren(left, fun, _) do
Enum.map_join(left, ", ", &to_string(&1, fun)) <> " "
end
defp adjust_new_lines(block, replacement) do
@@ -371,13 +459,13 @@ defmodule Macro do
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (__FILE__, __MODULE__, etc);
* Module attributes reader (@foo);
* All pseudo-variables (`__FILE__`, `__MODULE__`, etc);
* Module attributes reader (`@foo`);
In case the expression cannot be expanded, it returns the expression
itself. Notice that `Macro.expand_once/2` performs the expansion just
once and it is not recursive. Check `Macro.expand/2` for expansion
until the node no longer represents a macro and `Macro.expand_all/2`
itself. Notice that `expand_once/2` performs the expansion just
once and it is not recursive. Check `expand/2` for expansion
until the node no longer represents a macro and `expand_all/2`
for recursive expansion.
## Examples
@@ -441,18 +529,23 @@ defmodule Macro do
end
"""
def expand_once(aliases, env) do
expand_once(aliases, env, nil) |> elem(0)
def expand_once(ast, env) do
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
atom when is_atom(atom) -> { atom, true, cache }
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
receiver when is_atom(receiver) ->
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true, cache }
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
true -> { :elixir_aliases.concat(aliases), true, cache }
case :lists.all(&is_atom/1, aliases) do
true ->
receiver = :elixir_aliases.concat(aliases)
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true, cache }
false -> { original, false, cache }
end
end
@@ -530,16 +623,16 @@ 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 """
Receives a AST node and expands it until it no longer represents
a macro. Check `Macro.expand_once/2` for more information on how
expansion works and `Macro.expand_all/2` for recursive expansion.
a macro. Check `expand_once/2` for more information on how
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
@@ -555,36 +648,10 @@ defmodule Macro do
{ tree, cache }
end
@doc %B"""
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 `Macro.expand_once/2` goes into more detail
on how expansion works. Keep in mind that `Macro.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.
"""
@doc false
def expand_all(tree, env) do
expand_all(tree, env, nil) |> elem(0)
IO.write "Macro.expand_all/2 is deprecated, please avoid recursive code expansion\n#{Exception.format_stacktrace}"
elem(expand_all(tree, env, nil), 0)
end
@doc false # Used internally by Elixir
@@ -605,7 +672,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
@@ -613,8 +680,8 @@ defmodule Macro do
end
@doc """
Recurs the quoted expression checking if all sub terms are
safe (i.e. they represented data structured and don't actually
Recurs the quoted expression checking if all sub-terms are
safe (i.e. they represent data structures and don't actually
evaluate code) and returns `:ok` unless a given term is unsafe,
which is returned as `{ :unsafe, term }`.
"""
@@ -622,7 +689,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
@@ -631,7 +698,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
+4 -3
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__)
+45 -28
View File
@@ -5,7 +5,7 @@ defmodule Module do
end
end
@moduledoc %B'''
@moduledoc %S'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
@@ -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__
@@ -51,7 +51,7 @@ defmodule Module do
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
**Example**
### Example
defmodule M do
@before_compile __MODULE__
@@ -67,7 +67,7 @@ defmodule Module do
Specify an OTP or user-defined behaviour.
**Example**
### Example
defmodule M do
@behaviour gen_event
@@ -85,13 +85,13 @@ 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 }
def myfun(arg) do
to_binary(arg)
to_string(arg)
end
end
@@ -106,7 +106,7 @@ defmodule Module do
Can be invoked more than once.
**Example**
### Example
defmodule M do
@doc "Hello world"
@@ -129,7 +129,7 @@ defmodule Module do
Accepts a string. Can be used more than once.
**Example**
### Example
defmodule M do
@doc "Hello world"
@@ -147,7 +147,7 @@ defmodule Module do
`@moduledoc false` will make the module invisible to the
documentation extraction tools like ExDoc.
**Example**
### Example
defmodule M do
@moduledoc """
@@ -159,8 +159,7 @@ defmodule Module do
* `@on_definition`
A hook that will be invoked after each function or macro in the current
module is defined. This makes it easy to annotate and customize
functions.
module is defined. Useful when annotating functions.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
must take 6 arguments:
@@ -178,10 +177,13 @@ defmodule Module do
When just a module is provided, the function is assumed to be
`__on_definition__/6`.
Note that you can't provide the current module to `@on_definition`
because the hook function will not be defined in time.
Note that you can\'t provide the current module to `@on_definition`
because the hook function will not be defined in time. Finally, since
the `on_definition` hook is executed inside the context of the defined
function (i.e. `env.function` returns the current function), the hook
can only be a function, not a macro.
**Example**
### Example
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
@@ -198,7 +200,7 @@ defmodule Module do
@on_definition { H, :on_def }
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_binary(arg)
"Hello" <> to_string(arg)
end
def hello(_) do
@@ -214,7 +216,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
@@ -236,7 +238,7 @@ defmodule Module do
Specify the module version. Accepts any valid Elixir value.
**Example**
### Example
defmodule M do
@vsn "1.0"
@@ -266,7 +268,6 @@ defmodule Module do
It is possible to query a module at runtime to find out which functions and
macros it defines, extract its docstrings, etc. See `__info__/1`.
'''
@doc """
@@ -463,10 +464,24 @@ defmodule Module do
end
@doc """
Attaches documentation to a given function. It expects
the module the function belongs to, the line (a non negative
Gets an anonymous function from the given module, function
and arity. The module and function are not verified to exist.
iex> fun = Module.function(Kernel, :is_atom, 1)
iex> fun.(:hello)
true
"""
def function(mod, fun, arity) do
:erlang.make_fun(mod, fun, arity)
end
@doc """
Attaches documentation to a given function or type. It expects
the module the function/type belongs to, the line (a non negative
integer), the kind (def or defmacro), a tuple representing
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
@@ -477,12 +492,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)
@@ -825,7 +842,7 @@ defmodule Module do
"""
def split(module) do
tl(String.split(Binary.Chars.to_binary(module), "."))
tl(String.split(String.Chars.to_string(module), "."))
end
@doc false
@@ -875,9 +892,9 @@ defmodule Module do
atom
end
defp normalize_attribute(:file, env) when is_env(env), do: { binary_to_list(env.file), env.line }
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
defp normalize_attribute(:file, file) when is_binary(file) do
file
end
defp normalize_attribute(key, atom) when is_atom(atom) and
key in [:before_compile, :after_compile, :on_definition] do
@@ -885,7 +902,7 @@ defmodule Module do
end
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
raise ArgumentError, message: "Attributes type, typep, export_type, opaque and callback " <>
raise ArgumentError, message: "attributes type, typep, export_type, opaque and callback " <>
"must be set via Kernel.Typespec"
end
+16 -88
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,17 +86,6 @@ defmodule Module.DispatchTracker do
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
end
@doc """
Returns all the modules which were imported.
All external dependencies to a module is the sum
of imports and remotes.
"""
@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.
@@ -113,17 +96,6 @@ defmodule Module.DispatchTracker do
:digraph.out_neighbours(d, { :import, name, arity })
end
@doc """
Returns all the modules which were remotely dispatched
to. All external dependencies to a module is the sum
of imports and remotes.
"""
@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.
@@ -152,11 +124,15 @@ 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
defp to_pid(mod) when is_atom(mod), do: Module.get_attribute(mod, :__dispatch_tracker)
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__dispatch_tracker)
val
end
defp only_tuples(list) do
lc x inlist list, is_tuple(x), do: x
@@ -200,41 +176,13 @@ defmodule Module.DispatchTracker do
# Adds a remote dispatch to the given target.
@doc false
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_remote, function, module, target })
:gen_server.cast(pid, { :add_external, :remote, function, module, target })
end
# Adds a import dispatch to the given target.
@doc false
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_import, function, module, target })
end
# Associates a module with a warn. This adds the given
# module and associates it with the `:import` vertex
# permanently, even if warn is false.
@doc false
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
:gen_server.cast(pid, { :add_warnable, module, warn, line })
end
# Collect all unused imports where warn has been set to true.
def collect_unused_imports(pid) do
d = :gen_server.call(pid, :digraph, @timeout)
warnable = :digraph.out_neighbours(d, :warn)
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
{ mod, line }
end
end
defp get_warn_line(d, mod) do
[edge] = :digraph.out_edges(d, mod)
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
line
end
defp has_imports?(d, mod) do
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
:gen_server.cast(pid, { :add_external, :import, function, module, target })
end
# Yanks a local node. Returns its in and out vertices in a tuple.
@@ -243,6 +191,8 @@ defmodule Module.DispatchTracker do
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
end
# Reattach a previously yanked node
@doc false
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
@@ -267,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
@@ -307,9 +257,6 @@ defmodule Module.DispatchTracker do
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
:digraph.add_vertex(d, :import)
:digraph.add_vertex(d, :remote)
:digraph.add_vertex(d, :warn)
{ :ok, d }
end
@@ -337,26 +284,8 @@ defmodule Module.DispatchTracker do
{ :noreply, d }
end
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
handle_import_or_remote(d, :remote, function, module, name, arity)
{ :noreply, d }
end
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
handle_import_or_remote(d, :import, 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
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
@@ -397,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)
+17 -17
View File
@@ -6,7 +6,7 @@ defmodule Node do
@type t :: atom
@doc """
Returns the current node. It returns the same as the built-in node().
Returns the current node. It returns the same as the built-in `node()`.
"""
@spec self :: t
def self do
@@ -14,8 +14,8 @@ defmodule Node do
end
@doc """
Returns true if the local node is alive; that is, if the node can be
part of a distributed system. Otherwise, it returns false.
Returns `true` if the local node is alive; that is, if the node can be
part of a distributed system. Otherwise, it returns `false`.
"""
@spec alive? :: boolean
def alive? do
@@ -24,7 +24,7 @@ defmodule Node do
@doc """
Returns a list of all visible nodes in the system, excluding
the local node. Same as list(visible).
the local node. Same as `list(:visible)`.
"""
@spec list :: [t]
def list do
@@ -45,8 +45,8 @@ defmodule Node do
end
@doc """
Monitors the status of the node. If flag is true, monitoring is
turned on. If flag is false, monitoring is turned off.
Monitors the status of the node. If `flag` is `true`, monitoring is
turned on. If `flag` is `false`, monitoring is turned off.
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
"""
@@ -56,8 +56,8 @@ defmodule Node do
end
@doc """
Behaves as monitor_node/2 except that it allows an extra
option to be given, namely :allow_passive_connect.
Behaves as `monitor/2` except that it allows an extra
option to be given, namely `:allow_passive_connect`.
See http://www.erlang.org/doc/man/erlang.html#monitor_node-3 for more info.
"""
@@ -69,8 +69,8 @@ defmodule Node do
@doc """
Forces the disconnection of a node. This will appear to the `node` as if
the local node has crashed. This BIF is mainly used in the Erlang network
authentication protocols. Returns true if disconnection succeeds, otherwise
false. If the local node is not alive, the function returns ignored.
authentication protocols. Returns `true` if disconnection succeeds, otherwise
`false`. If the local node is not alive, the function returns `:ignored`.
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
"""
@@ -80,8 +80,8 @@ defmodule Node do
end
@doc """
Establishes a connection to Node. Returns true if successful,
false if not, and the atom `:ignored` if the local node is not
Establishes a connection to `node`. Returns `true` if successful,
`false` if not, and the atom `:ignored` if the local node is not
alive.
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
@@ -145,7 +145,7 @@ defmodule Node do
Returns the pid of a new process started by the application of `fun`
on `node`. A link is created between the calling process and the
new process, atomically. If `node` does not exist, a useless pid is returned
(and due to the link, an exit signal with exit reason :noconnection will be
(and due to the link, an exit signal with exit reason `:noconnection` will be
received).
"""
@spec spawn_link(t, (() -> any)) :: pid
@@ -158,7 +158,7 @@ defmodule Node do
`module.function(args)` on `node`. A link is created between the calling
process and the new process, atomically. If `node` does not exist, a useless
pid is returned (and due to the link, an exit signal with exit reason
:noconnection will be received).
`:noconnection` will be received).
"""
@spec spawn_link(t, module, atom, [any]) :: pid
def spawn_link(node, module, fun, args) do
@@ -167,10 +167,10 @@ defmodule Node do
@doc """
Sets the magic cookie of `node` to the atom `cookie`. The default node
is `Node.self`, the local node. If node is the local node, the function also
is `Node.self`, the local node. If `node` is the local node, the function also
sets the cookie of all other unknown nodes to `cookie`.
This function will raise `FunctionClauseError` if the given node is not alive.
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
:erlang.set_cookie(node, cookie)
@@ -183,4 +183,4 @@ defmodule Node do
def get_cookie() do
:erlang.get_cookie()
end
end
end
+103 -67
View File
@@ -4,26 +4,30 @@ defmodule OptionParser do
"""
@doc """
Parses `argv` and returns a tuple with parsed options
and arguments.
Parses `argv` and returns a tuple with the parsed options, its
arguments, and a list options that couldn't be parsed.
## Examples
iex> OptionParser.parse(["--debug"])
{ [debug: true], [] }
{ [debug: true], [], [] }
iex> OptionParser.parse(["--source", "lib"])
{ [source: "lib"], [] }
{ [source: "lib"], [], [] }
iex> OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib", verbose: true], ["test/enum_test.exs"] }
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
{ [source_path: "lib", verbose: true], ["test/enum_test.exs"], [] }
Notice how Elixir automatically translates the "--source-path"
switch to the underscored atom `:source_path`, which better follows
Elixir conventions.
## Aliases
A set of aliases can be given as the second argument:
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
{ [debug: true], [] }
{ [debug: true], [], [] }
## Switches
@@ -39,6 +43,9 @@ defmodule OptionParser do
* `:integer` - Parses the switch as an integer;
* `:float` - Parses the switch as a float;
If a switch can't be parsed, the option is returned in the invalid
options list (third element of the returned tuple).
The following extra options are supported:
* `:keep` - Keeps duplicated items in the list instead of overriding;
@@ -46,10 +53,17 @@ defmodule OptionParser do
Examples:
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
{ [unlock: true], ["path/to/file"] }
{ [unlock: true], ["path/to/file"], [] }
iex> OptionParser.parse(["--unlock", "false", "path/to/file"], switches: [unlock: :boolean])
{ [unlock: false], ["path/to/file"] }
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
...> switches: [unlock: :boolean, limit: :integer])
{ [unlock: true, limit: 0], ["path/to/file"], [] }
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
{ [limit: 3], [], [] }
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
{ [], [], [limit: "yyz"] }
## Negation switches
@@ -57,7 +71,12 @@ defmodule OptionParser do
booleans and never parse the next value:
iex> OptionParser.parse(["--no-op", "path/to/file"])
{ [no_op: true], ["path/to/file"] }
{ [no_op: true], ["path/to/file"], [] }
In case the negated switch exists as a boolean, it sets the boolean to false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{ [op: false], ["path/to/file"], [] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
@@ -73,10 +92,10 @@ defmodule OptionParser do
## Example
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib"], ["test/enum_test.exs", "--verbose"] }
{ [source: "lib"], ["test/enum_test.exs", "--verbose"], [] }
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"]}
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
@@ -91,90 +110,106 @@ defmodule OptionParser do
end
defp parse(argv, aliases, switches, all) do
parse(argv, aliases, switches, [], [], all)
parse(argv, aliases, switches, [], [], [], all)
end
defp parse(["-" <> option|t], aliases, switches, dict, args, all) do
{ option, value } = normalize_option(option, aliases)
kind = switches[option]
defp parse(["--"|_] = value, _aliases, _switches, dict, _args, invalid, _all) do
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
end
if value == nil do
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
{ option, kinds, value } = normalize_option(option, switches, aliases)
if nil?(value) do
{ value, t } =
if is_switch_a? :boolean, kind do
boolean_from_tail(t)
if :boolean in kinds do
{ true, t }
else
value_from_tail(t)
end
end
dict = store_option dict, option, value, kind
parse(t, aliases, switches, dict, args, all)
{ dict, invalid } = store_option(dict, invalid, option, value, kinds)
parse(t, aliases, switches, dict, args, invalid, all)
end
defp parse([h|t], aliases, switches, dict, args, true) do
parse(t, aliases, switches, dict, [h|args], true)
defp parse([h|t], aliases, switches, dict, args, invalid, true) do
parse(t, aliases, switches, dict, [h|args], invalid, true)
end
defp parse([], _, switches, dict, args, true) do
{ reverse_dict(dict, switches), Enum.reverse(args) }
defp parse([], _, _switches, dict, args, invalid, true) do
{ Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid) }
end
defp parse(value, _, switches, dict, _args, false) do
{ reverse_dict(dict, switches), value }
defp parse(value, _, _switches, dict, _args, invalid, false) do
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
end
defp boolean_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
defp boolean_from_tail(t) , do: { true, t }
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
defp value_from_tail([h|t]), do: { h, t }
defp value_from_tail([]), do: { true, [] }
defp store_option(dict, option, value, switches) when value in ["true", "false"] do
store_option dict, option, binary_to_atom(value), switches
defp store_option(dict, invalid, option, value, kinds) do
{ invalid_option, value } =
cond do
:boolean in kinds ->
{ nil, value in [true, "true"] }
:integer in kinds ->
case Integer.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
:float in kinds ->
case Float.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
true ->
{ nil, value }
end
if invalid_option do
{ dict, [{ option, value }|invalid] }
else
{ do_store_option(dict, option, value, kinds), invalid }
end
end
defp store_option(dict, option, value, kind) do
case kind do
:keep ->
defp do_store_option(dict, option, value, kinds) do
cond do
:keep in kinds ->
[{ option, value }|dict]
:integer ->
case String.to_integer(value) do
{ value, "" } -> [{ option, value }|Keyword.delete(dict, option)]
_ -> dict
end
:float ->
case String.to_float(value) do
{ value, "" } -> [{ option, value }|Keyword.delete(dict, option)]
_ -> dict
end
_ ->
true ->
[{ option, value }|Keyword.delete(dict, option)]
end
end
defp reverse_dict(dict, switches) do
switches = lc { k, v } inlist switches,
is_switch_a?(:boolean, v),
not Keyword.has_key?(dict, k), do: { k, false }
Enum.reverse switches ++ dict
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
normalize_option(option, switches, aliases)
end
defp normalize_option(<<?-, option :: binary>>, aliases) do
normalize_option(option, aliases)
end
defp normalize_option(option, aliases) do
defp normalize_option(option, switches, aliases) do
{ option, value } = split_option(option)
if is_no?(option), do: value = true
atom = option |> to_underscore |> binary_to_atom
{ aliases[atom] || atom, value }
if non_neg = get_non_negated(option, aliases) do
kinds = List.wrap(switches[non_neg])
if :boolean in kinds do
{ non_neg, kinds, false }
else
{ get_aliased(option, aliases), [:boolean], true }
end
else
atom = get_aliased(option, aliases)
{ atom, List.wrap(switches[atom]), value }
end
end
defp split_option(option) do
case :binary.split(option, "=") do
[h] -> { h, nil }
[h|t] -> { h, Enum.join(t, "=") }
[h] -> { h, nil }
[h, t] -> { h, t }
end
end
@@ -182,10 +217,11 @@ defmodule OptionParser do
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
end
defp is_no?("no-" <> _), do: true
defp is_no?(_), do: false
defp get_aliased(option, aliases) do
atom = option |> to_underscore |> binary_to_atom
aliases[atom] || atom
end
defp is_switch_a?(kind, list) when is_list(list), do: kind in list
defp is_switch_a?(kind, kind), do: true
defp is_switch_a?(_, _), do: false
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
defp get_non_negated(_, _), do: nil
end
+27 -10
View File
@@ -9,7 +9,7 @@ defmodule Path do
The majority of the functions in this module do not
interact with the file system, except for a few functions
that require it (like `Path.wildcard` and `Path.expand`).
that require it (like `wildcard/1` and `expand/1`).
"""
alias :filename, as: FN
@@ -19,7 +19,7 @@ defmodule Path do
@doc """
Converts the given path to an absolute one. Unlike
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
`expand/1`, no attempt is made to resolve `..`, `.` or `~`.
## Unix examples
@@ -43,9 +43,9 @@ defmodule Path do
@doc """
Builds a path from `relative_to` to `path`. If `path` is already
an absolute path, `relative_to` is ignored. See also `Path.relative/2`.
an absolute path, `relative_to` is ignored. See also `relative_to/2`.
Unlike `Path.expand/2`, no attempt is made to
Unlike `expand/2`, no attempt is made to
resolve `..`, `.` or `~`.
## Examples
@@ -80,7 +80,7 @@ defmodule Path do
expanding any `.` and `..` characters. If the path is already an
absolute path, `relative_to` is ignored.
Note, that this function treats `path` with leading `~` as
Note, that this function treats `path` with a leading `~` as
an absolute one.
The second argument is first expanded to an absolute path.
@@ -236,6 +236,18 @@ defmodule Path do
original
end
@doc """
Convenience to get the path relative to the current working
directory. If, for some reason, the current working directory
cannot be retrieved, returns the full path.
"""
def relative_to_cwd(path) do
case :file.get_cwd do
{ :ok, base } -> relative_to(path, base)
_ -> path
end
end
@doc """
Returns the last component of the path or the path
itself if it does not contain any directory separators.
@@ -397,7 +409,7 @@ defmodule Path do
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
list_to_binary(maybe_remove_dirsep(result, os_type))
iolist_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
@@ -461,7 +473,7 @@ defmodule Path do
Imagine you have a directory called `projects` with three Elixir projects
inside of it: `elixir`, `ex_doc` and `dynamo`. You can find all `.beam` files
inside the ebin directory of each project as follows:
inside the `ebin` directory of each project as follows:
Path.wildcard("projects/*/ebin/**/*.beam")
@@ -471,8 +483,9 @@ defmodule Path do
"""
def wildcard(glob) when is_binary(glob) do
paths = :filelib.wildcard :unicode.characters_to_list(glob)
Enum.map paths, :unicode.characters_to_binary(&1)
paths = :filelib.wildcard binary_to_filename_string(glob)
encoding = :file.native_name_encoding()
Enum.map paths, &flatten_filename_to_binary(&1, encoding)
end
def wildcard(glob) when is_list(glob) do
@@ -494,7 +507,11 @@ defmodule Path do
end
defp filename_string_to_binary(list) do
case :unicode.characters_to_binary(:filename.flatten(list), :unicode, :file.native_name_encoding()) do
flatten_filename_to_binary(:filename.flatten(list), :file.native_name_encoding())
end
defp flatten_filename_to_binary(list, encoding) do
case :unicode.characters_to_binary(list, :unicode, encoding) do
{ :error, _, _ } ->
:erlang.error(:badarg)
bin when is_binary(bin) ->
+9 -8
View File
@@ -286,7 +286,7 @@ defmodule Process do
@doc """
Returns the pid or port identifier with the registered name.
Returns undefined if the name is not registered.
Returns nil if the name is not registered.
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
@@ -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
@@ -347,25 +347,26 @@ defmodule Process do
end
@doc """
Returns information about the process identified by pid.
Returns information about the process identified by pid or nil if the process
is not alive.
Use this only for debugging information.
See http://www.erlang.org/doc/man/erlang.html#process_info-1 for more info.
"""
@spec info(pid) :: Keyword.t
def info(pid) do
:erlang.process_info(pid)
nillify :erlang.process_info(pid)
end
@doc """
Returns information about the process identified by pid
or undefined if the process is not alive.
or nil if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
@spec info(pid, atom) :: {atom, term}
def info(pid, spec) do
:erlang.process_info(pid, spec)
nillify :erlang.process_info(pid, spec)
end
@compile { :inline, nillify: 1 }
+182 -306
View File
@@ -1,58 +1,106 @@
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 :macros, Protocol.DSL
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
msg = "is deprecated and should be removed. " <>
"Note if you want to fallback to Any, you have to set @fallback_to_any true"
if @only do
IO.puts "warning: @only in protocol #{inspect __MODULE__} " <> msg
@fallback_to_any @fallback_to_any || Any in @only
end
if @except do
IO.puts "warning: @except in protocol #{inspect __MODULE__} " <> msg
@fallback_to_any @fallback_to_any || not(Any in @except)
end
{ 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
@@ -71,8 +119,18 @@ defmodule Protocol do
defmodule name do
@behaviour unquote(protocol)
@protocol unquote(protocol)
@for unquote(for)
unquote(block)
def __impl__, do: unquote(protocol)
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
end
end
end
@@ -94,203 +152,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__, structure: 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, Any, arg)
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, Any, arg)
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, Any, arg)
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, Any, arg)
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, Any, arg)
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, Any, arg)
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, Any, arg)
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, Any, arg)
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, Any, arg)
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, Any, 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, fallback, arg) do
quote do
defp __raw_impl__(_) do
__MODULE__.Any
end
{ unquote(guard)(unquote(arg)),
unquote(with_fallback(Module.concat(current, mod), current, fallback, arg)) }
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, current, fallback, arg) 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], _ }|_]|_] ->
unquote(impl_for(current, fallback, arg) |> elem(1))
end
end
end
@@ -299,36 +242,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
@@ -343,80 +295,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__, structure: 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]
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))
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
Kernel.def unquote(name), args, [], do: 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
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
+9 -8
View File
@@ -17,20 +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
@@ -55,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
+134 -107
View File
@@ -1,19 +1,19 @@
defmodule Record do
@moduledoc %B"""
Convenience functions for working with Records.
@moduledoc %S"""
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,16 +135,25 @@ 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`.
You can learn more about records in the `Kernel.defrecord/2` docs. Now
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.
## Protocols
@@ -161,29 +167,39 @@ defmodule Record do
defrecord WeekDate, year: nil, week: nil, week_day: nil
Now we want this date to be represented as a string and this
can be done by implementing the `Binary.Chars` protocol for
can be done by implementing the `String.Chars` protocol for
our record:
defimpl Binary.Chars, for: WeekDate do
def to_binary(WeekDate[year: year, week: week, week_day: day]) do
defimpl String.Chars, for: WeekDate do
def to_string(WeekDate[year: year, week: week, week_day: day]) do
"#{year}-W#{week}-#{day}"
end
end
Now we can explicitly convert our WeekDate:
Now we can explicitly convert our `WeekDate`:
to_binary WeekDate[year: 2013, week: 26, week_day: 4]
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")
"""
@@ -192,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.
@@ -229,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
@@ -278,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
@@ -297,7 +315,6 @@ defmodule Record do
contents = [
reflection(escaped),
initializer(escaped),
indexes(escaped),
conversions(values),
record_optimizable(),
updater(values),
@@ -307,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
@@ -316,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 || []
@@ -338,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
@@ -413,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?
@@ -626,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
@@ -636,47 +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(tuple) when is_tuple(tuple), do: :erlang.setelement(1, tuple, __MODULE__)
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
# Define method to get index of a given key.
#
# Useful if you need to know position of the key for such applications as:
# - ets
# - mnesia
#
# For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define following method:
#
# def __index__(:atime), do: 2
# def __index__(:mtime), do: 3
# def __index__(_), do: nil
#
defp indexes(values) do
quoted = lc { k, _ } inlist values do
index = find_index(values, k, 0)
quote do
@doc false
def __index__(unquote(k)) do
IO.write "[WARNING] #{__MODULE__}.__index__/1 is deprecated, " <>
"please use #{__MODULE__}.__record__(:index, key) instead\n#{Exception.format_stacktrace}"
unquote(index + 1)
end
end
end
defp initialize_lookup(k, v) do
quote do
unquote(quoted)
@doc false
def __index__(_), do: nil
@doc false
def __index__(key, _), do: __index__(key)
case :lists.keyfind(unquote(k), 1, opts) do
false -> unquote(v)
{_, v} -> v
end
end
end
@@ -767,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
@@ -783,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
@@ -807,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
@@ -814,11 +816,11 @@ 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
@spec new(options | tuple) :: t
@spec new(options) :: t
@spec to_keywords(t) :: options
@spec update(options, t) :: t
@spec __record__(:name) :: atom
@@ -832,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)
@@ -853,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
@@ -861,17 +868,36 @@ defmodule Record do
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
defp convert_value({ atom, other }) when is_atom(atom) and is_function(other), do:
raise(ArgumentError, message: "record field default value #{inspect atom} cannot be a function")
defp convert_value({ atom, other }) when is_atom(atom) and (is_reference(other) or is_pid(other) or is_port(other)), do:
raise(ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port")
defp convert_value({ atom, _ } = tuple) when is_atom(atom), do: tuple
defp convert_value({ atom, other }) when is_atom(atom), do:
{ atom, check_value(atom, other) }
defp convert_value({ field, _ }), do:
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
defp check_value(atom, other) when is_list(other) do
lc(i inlist other, do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_tuple(other) do
lc(i inlist tuple_to_list(other), do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_function(other) do
unless :erlang.fun_info(other, :env) == { :env, [] } and
:erlang.fun_info(other, :type) == { :type, :external } do
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
"functions that point to an existing &Mod.fun/arity"
end
end
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
end
defp check_value(_atom, other), do: other
defp find_index([{ k, _ }|_], k, i), do: i
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
@@ -890,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
+10 -10
View File
@@ -3,12 +3,12 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def retrieve(name, from: string) do
file = to_char_list(string)
def retrieve(name, from: file) when is_binary(file) do
file = String.to_char_list!(file)
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
realfile -> :ok
end
retrieve_record(name, realfile)
@@ -16,12 +16,12 @@ defmodule Record.Extractor do
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def retrieve(name, from_lib: file) do
[app|path] = :filename.split(to_char_list(file))
def retrieve(name, from_lib: file) when is_binary(file) do
[app|path] = :filename.split(String.to_char_list!(file))
case :code.lib_dir(to_char_list(app)) do
case :code.lib_dir(list_to_atom(app)) do
{ :error, _ } ->
raise ArgumentError, "lib file #{to_binary(file)} could not be found"
raise ArgumentError, message: "lib file #{file} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
end
@@ -34,7 +34,7 @@ defmodule Record.Extractor do
if record = List.keyfind(records, name, 0) do
parse_record(record, form)
else
raise ArgumentError, "no record #{name} found at #{to_binary(file)}"
raise ArgumentError, message: "no record #{name} found at #{file}"
end
end
@@ -51,7 +51,7 @@ defmodule Record.Extractor do
{ :ok, form } ->
form
other ->
raise "error parsing file #{to_binary(file)}, got: #{inspect(other)}"
raise "error parsing file #{file}, got: #{inspect(other)}"
end
end
@@ -88,4 +88,4 @@ defmodule Record.Extractor do
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
record
end
end
end
+106 -63
View File
@@ -1,22 +1,21 @@
defmodule Regex do
@moduledoc %B"""
Regular expressions for Elixir built on top of the re module
@moduledoc %S"""
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,25 +43,54 @@ defmodule Regex do
"""
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, term, term, term }
@type t :: { Regex, term, binary, binary, [atom] | nil }
defexception CompileError, message: "regex could not be compiled"
@doc """
Compiles the regular expression according to the given options.
Compiles the regular expression.
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](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}}
"""
def compile(source, options // "") when is_binary(source) do
options = to_binary(options)
opts = translate_options(options)
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
def compile(source, options // "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
{ :error, rest } ->
{ :error, { :invalid_option, rest } }
translated_options ->
# Always use the unicode option, we don't have a latin1 legacy like
# Erlang.
compile(source, [:unicode|translated_options], options)
end
end
def compile(source, options) when is_list(options) do
compile(source, options, "")
end
defp compile(source, opts, doc_opts) when is_binary(source) do
re_opts = opts -- [:groups]
groups = if opts != re_opts, do: parse_groups(source)
case :re.compile(source, re_opts) do
{ :ok, re_pattern } ->
{ :ok, regex(re_pattern: re_pattern, source: source, options: options, groups: groups) }
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: groups) }
error ->
error
end
@@ -95,9 +123,11 @@ defmodule Regex do
end
@doc """
Runs the regular expression against the given string.
It returns a list with all matches, `nil` if no match occurred, or `[]`
if it matched, `/g` was specified, but nothing was captured.
Runs the regular expression against the given string until the first match.
It returns a list with all captures or `nil` if no match occurred.
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
## Examples
@@ -128,29 +158,23 @@ defmodule Regex do
end
@doc """
Returns the given captures as a keyword list or `nil` if no captures are found.
Requires the regex to be compiled with the groups option.
Returns the given captures as a keyword list or `nil` if no captures
are found. Requires the regex to be compiled with the groups option.
## 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")
[bar: "d", foo: "b"]
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
[foo: "b", bar: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
nil
"""
def captures(regex(groups: groups) = regex, string, options // []) do
unless captures = Keyword.get(options, :capture) do
captures = if groups do
Enum.sort(groups)
else
raise ArgumentError, message: "regex was not compiled with g"
end
options = Keyword.put(options, :capture, captures)
end
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(captures, results)
if results, do: Enum.zip(groups, results)
end
@doc """
@@ -200,34 +224,48 @@ defmodule Regex do
end
@doc """
Same as run, but scans the target several times collecting all matches of
the regular expression. A list is returned with each match. If the item in
the list is a binary, it means there were no captures. If the item is another
list, each element in this secondary list is a capture.
Same as `run/3`, but scans the target several times collecting all
matches of the regular expression. A list of lists is returned,
where each entry in the primary list represents a match and each
entry in the secondary list represents the captured contents.
The captured contents defaults to `:all`, which includes the whole
regex match and each capture.
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
## Examples
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
[["d"], ["e"]]
[["cd", "d"], ["ce", "e"]]
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
["cd", "ce"]
[["cd"], ["ce"]]
iex> Regex.scan(%r/e/, "abcd")
[]
"""
def scan(regex, string, options // [])
def scan(regex(re_pattern: compiled), string, options) do
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
options = [{ :capture, :all, return }, :global]
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
others -> others
end
options = [{ :capture, captures, return }, :global]
case :re.run(string, compiled, options) do
:match -> []
:nomatch -> []
{ :match, results } -> flatten_result(results)
{ :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
@@ -235,8 +273,12 @@ defmodule Regex do
["a","b","c"]
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
["a","b-c"]
iex> Regex.split(%r/-/, "abc")
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 // [])
@@ -251,16 +293,22 @@ defmodule Regex do
return = Keyword.get(options, :return, return_for(string))
opts = [return: return, parts: parts]
:re.split(string, compiled, opts)
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
else
splits
end
end
@doc %B"""
@doc %S"""
Receives a regex, a binary and a replacement, returns a new
binary where the all matches are replaced by replacement.
Inside the replacement, you can either give `&` to access the
whole regular expression or `\N`, where `N` is in integer to access
a specific matching parens. You can also set global to false
a specific matching parens. You can also set `:global` to `false`
if you want to replace just the first occurrence.
## Examples
@@ -284,10 +332,10 @@ defmodule Regex do
:re.replace(string, compiled, replacement, opts)
end
{ :ok, pattern } = :re.compile(%B"[.^$*+?()[{\\\|\s#]", [:unicode])
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc %B"""
@doc %S"""
Escapes a string to be literally matched in a regex.
## Examples
@@ -306,7 +354,7 @@ defmodule Regex do
# Helpers
@doc false
# Unescape map function used by Macro.unescape_binary.
# Unescape map function used by Macro.unescape_string.
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
@@ -320,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)]
@@ -329,17 +380,9 @@ defmodule Regex do
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
defp translate_options(<<>>), do: []
defp translate_options(rest), do: { :error, rest }
defp flatten_result(results) do
lc result inlist results do
case result do
[t] -> t
[_|t] -> t
end
end
end
{ :ok, pattern } = :re.compile(%B"\(\?<(?<G>[^>]*)>")
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
@groups_pattern pattern
defp parse_groups(source) do
+16 -1
View File
@@ -1,5 +1,5 @@
defmodule Set do
@moduledoc %B"""
@moduledoc %S"""
This module specifies the Set API expected to be
implemented by different representations.
@@ -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
+156 -49
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
@@ -27,16 +27,16 @@ defmodule Stream do
We say the functions in `Stream` are *lazy* and the functions in `Enum`
are *eager*.
Due to their laziness, streams are useful when working with large
Due to their laziness, streams are useful when working with large
(or even infinite) collections. When chaining many operations with `Enum`,
intermediate lists are created, while `Stream` creates a recipe of
computations that are executed at a later moment. Let's see another
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
@@ -97,32 +97,35 @@ defmodule Stream do
defimpl Enumerable, for: Lazy do
def reduce(Lazy[] = lazy, acc, fun) do
do_reduce(lazy, acc, fun)
do_reduce(lazy, acc, fun, 0)
end
def count(Lazy[] = lazy) do
do_reduce(lazy, 0, fn _, acc -> acc + 1 end)
do_reduce(lazy, 0, fn _, acc -> acc + 1 end, 0)
end
def member?(Lazy[] = lazy, value) do
do_reduce(lazy, false, fn(entry, _) ->
if entry === value, do: throw({ :stream_lazy, true }), else: false
end)
if entry === value, do: throw({ :stream_lazy, 0, true }), else: false
end, 0)
end
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: nil], acc, fun) do
do_reduce(enumerable, acc, f1.(fun))
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: nil], acc, fun, nesting) do
do_reduce(enumerable, acc, f1.(fun), nesting)
end
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: side], acc, fun) do
do_reduce(enumerable, { acc, side }, f1.(fun)) |> elem(0)
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: side], acc, fun, nesting) do
do_reduce(enumerable, { acc, side }, f1.(fun, nesting), nesting + 1)
end
defp do_reduce(enumerable, acc, fun) do
Enumerable.reduce(enumerable, acc, fun)
defp do_reduce(enumerable, acc, fun, nesting) do
Enumerable.reduce(enumerable, acc, fun) |> remove_nesting(nesting)
catch
{ :stream_lazy, acc } -> acc
{ :stream_lazy, nesting, res } -> remove_nesting(res, nesting)
end
defp remove_nesting(acc, 0), do: acc
defp remove_nesting(acc, nesting), do: remove_nesting(elem(acc, 0), nesting - 1)
end
@type t :: Lazy.t | (acc, (element, acc -> acc) -> acc)
@@ -131,6 +134,46 @@ defmodule Stream do
@type index :: non_neg_integer
@type default :: any
@doc """
Creates a stream that enumerates each enumerable in an enumerable.
## Examples
iex> stream = Stream.concat([1..3, 4..6, 7..9])
iex> Enum.to_list(stream)
[1,2,3,4,5,6,7,8,9]
"""
@spec concat(Enumerable.t) :: t
def concat(enumerables) do
&do_concat(enumerables, &1, &2)
end
@doc """
Creates a stream that enumerates the first argument, followed by the second.
## Examples
iex> stream = Stream.concat(1..3, 4..6)
iex> Enum.to_list(stream)
[1,2,3,4,5,6]
iex> stream1 = Stream.cycle([1, 2, 3])
iex> stream2 = Stream.cycle([4, 5, 6])
iex> stream = Stream.concat(stream1, stream2)
iex> Enum.take(stream, 6)
[1,2,3,1,2,3]
"""
@spec concat(Enumerable.t, Enumerable.t) :: t
def concat(first, second) do
&do_concat([first, second], &1, &2)
end
defp do_concat(enumerables, acc, fun) do
Enumerable.reduce(enumerables, acc, &Enumerable.reduce(&1, &2, fun))
end
@doc """
Creates a stream that cycles through the given enumerable,
infinitely.
@@ -144,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
@@ -165,7 +208,7 @@ defmodule Stream do
@spec drop(Enumerable.t, non_neg_integer) :: t
def drop(enumerable, n) when n >= 0 do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fun: fn(f1, _) ->
fn
_entry, { acc, n } when n > 0 ->
{ acc, n - 1 }
@@ -182,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]
@@ -190,7 +233,7 @@ defmodule Stream do
@spec drop_while(Enumerable.t, (element -> as_boolean(term))) :: t
def drop_while(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fun: fn(f1, _) ->
fn
entry, { acc, true } ->
if f.(entry), do: { acc, true }, else: { f1.(entry, acc), false }
@@ -228,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]
"""
@@ -266,6 +309,46 @@ defmodule Stream do
end]
end
@doc """
Creates a stream that will apply the given function on enumeration and
flatten the result.
## Examples
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
iex> Enum.to_list(stream)
[1, 2, 2, 4, 3, 6]
"""
@spec flat_map(Enumerable.t, (element -> any)) :: t
def flat_map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
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.
@@ -287,27 +370,25 @@ defmodule Stream do
end]
end
@doc """
Returns a stream generated by calling `generator_fun` repeatedly.
## Examples
iex> Stream.repeatedly(function(:random.uniform/0)) |> Enum.take(3)
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
"""
@spec repeatedly((() -> element)) :: t
def repeatedly(generator_fun)
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
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
end
@doc """
Lazily takes the next `n` items from the enumerable and stops
enumeration.
@@ -324,14 +405,14 @@ defmodule Stream do
"""
@spec take(Enumerable.t, non_neg_integer) :: t
def take(enumerable, n) when n >= 0 do
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: &(&1)]
def take(enumerable, n) when n > 0 do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn
(entry, { acc, n }) when n > 0 ->
{ f1.(entry, acc), n-1 }
(_entry, acc) ->
throw { :stream_lazy, acc }
fun: fn(f1, nesting) ->
fn(entry, { acc, n }) ->
res = f1.(entry, acc)
if n > 1, do: { res, n-1 }, else: throw { :stream_lazy, nesting, res }
end
end,
acc: n]
@@ -343,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]
@@ -351,17 +432,43 @@ defmodule Stream do
@spec take_while(Enumerable.t, (element -> as_boolean(term))) :: t
def take_while(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn
entry, { acc, true } ->
if f.(entry), do: { f1.(entry, acc), true }, else: { acc, false }
_entry, acc ->
acc
fun: fn(f1, nesting) ->
fn(entry, { acc, true }) ->
if f.(entry) do
{ f1.(entry, acc), true }
else
throw { :stream_lazy, nesting, acc }
end
end
end,
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.
@@ -376,10 +483,10 @@ defmodule Stream do
@spec with_index(Enumerable.t) :: t
def with_index(enumerable) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fun: fn(f1, _) ->
fn(entry, { acc, counter }) ->
acc = f1.({ entry, counter }, acc)
{ acc, counter + 1}
{ acc, counter + 1 }
end
end,
acc: 0]
+420 -202
View File
@@ -1,27 +1,29 @@
import Kernel, except: [length: 1]
defmodule String do
@moduledoc %B"""
@moduledoc %S"""
A String in Elixir is a UTF-8 encoded binary.
## String and binary operations
The functions in this module act according to the
Unicode Standard, version 6.2.0. For example,
`titlecase`, `downcase`, `strip` are provided by this
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
module.
Besides this module, Elixir provides more low-level
operations that works directly with binaries. Some
In addition to this module, Elixir provides more low-level
operations that work directly with binaries. Some
of those can be found in the `Kernel` module, as:
* `binary_part/2` and `binary_part/3` - retrieves part of the binary
* `bit_size/1` and `byte_size/1` - size related functions
* `is_bitstring/1` and `is_binary/1` - type checking function
* Plus a bunch of conversion functions, like `binary_to_atom/2`,
`binary_to_integer/2`, `binary_to_term/1` and their opposite
like `integer_to_binary/2`
* `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/1`,
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
like `Kernel.integer_to_binary/2`
Finally, [the `:binary` module](http://erlang.org/doc/man/binary.html)
provides a couple other functions that works on the byte level.
Finally, the [`:binary` module](http://erlang.org/doc/man/binary.html)
provides a few other functions that work on the byte level.
## Codepoints and graphemes
@@ -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
@@ -93,7 +106,7 @@ defmodule String do
codepoint needs to be rejected.
This module relies on this behaviour to ignore such invalid
characters. For example, `String.length` is going to return
characters. For example, `length/1` is going to return
a correct result even if an invalid codepoint is fed into it.
In other words, this module expects invalid data to be detected
@@ -108,7 +121,7 @@ defmodule String do
@doc """
Checks if a string is printable considering it is encoded
as UTF-8. Returns true if so, false otherwise.
as UTF-8. Returns `true` if so, `false` otherwise.
## Examples
@@ -138,69 +151,8 @@ defmodule String do
def printable?(<<>>), do: true
def printable?(_), do: false
@doc %B"""
Escape the given string. It expects one extra paremeter
representing the string surrounds which should also be escaped.
## Examples
iex> String.escape("abc", ?")
"abc"
iex> String.escape("a\nb", ?")
"a\\nb"
"""
def escape(other, char) do
escape(other, char, <<>>)
end
@compile { :inline, escape: 3 }
defp escape(<<>>, _char, binary), do: binary
defp escape(<< char, t :: binary >>, char, binary) do
escape(t, char, << binary :: binary, ?\\, char >>)
end
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
end
defp escape(<<?\a, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?a >>)
end
defp escape(<<?\b, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?b >>)
end
defp escape(<<?\d, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?d >>)
end
defp escape(<<?\e, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?e >>)
end
defp escape(<<?\f, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?f >>)
end
defp escape(<<?\n, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?n >>)
end
defp escape(<<?\r, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?r >>)
end
defp escape(<<?\\, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
end
defp escape(<<?\t, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?t >>)
end
defp escape(<<?\v, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, h >>)
end
@doc """
Splits a string on sub strings at each Unicode whitespace
Divides a string into substrings at each Unicode whitespace
occurrence with leading and trailing whitespace ignored.
## Examples
@@ -216,30 +168,52 @@ defmodule String do
@spec split(t) :: [t]
defdelegate split(binary), to: String.Unicode
@doc """
Divides a string into sub strings based on a pattern,
returning a list of these sub string. The pattern can
@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.
The string is split into as many parts as possible by
default, unless the `global` option is set to false.
default, unless the `global` option is set to `false`.
Empty strings are only removed from the result if the
`trim` option is set to `true`.
## Examples
Splitting with a string pattern:
iex> String.split("a,b,c", ",")
["a", "b", "c"]
iex> String.split("a,b,c", ",", global: false)
["a", "b,c"]
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]
@@ -248,17 +222,25 @@ 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, global: options[:global])
Regex.split(pattern, binary, options)
end
def split(binary, pattern, options) do
opts = if options[:global] != false, do: [:global], else: []
:binary.split(binary, pattern, opts)
splits = :binary.split(binary, pattern, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
else
splits
end
end
@doc """
Convert all characters on the given string to upcase.
Convert all characters on the given string to uppercase.
## Examples
@@ -274,7 +256,7 @@ defmodule String do
defdelegate upcase(binary), to: String.Unicode
@doc """
Convert all characters on the given string to downcase.
Convert all characters on the given string to lowercase.
## Examples
@@ -291,11 +273,11 @@ defmodule String do
@doc """
Converts the first character in the given string to
titlecase and the remaining to downcase.
uppercase and the remaining to lowercase.
This relies on the titlecase information provided
by the Unicode Standard. Note this function makes
no attempt in capitalizing all words in the string
no attempt to capitalize all words in the string
(usually known as titlecase).
## Examples
@@ -343,7 +325,7 @@ defmodule String do
# Do a quick check before we traverse the whole
# binary. :binary.last is a fast operation (it
# does not traverse the whole binary).
def rstrip(string, char) do
def rstrip(string, char) when char in 0..127 do
if :binary.last(string) == char do
do_rstrip(string, "", char)
else
@@ -351,12 +333,16 @@ defmodule String do
end
end
defp do_rstrip(<<char, string :: binary>>, buffer, char) do
do_rstrip(string, <<char, buffer :: binary>>, char)
def rstrip(string, char) do
do_rstrip(string, "", char)
end
defp do_rstrip(<<char, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char, do_rstrip(string, "", another_char) :: binary>>
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, char) do
<<do_rstrip(string, <<char :: utf8, buffer :: binary>>, char) :: binary>>
end
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char :: utf8, do_rstrip(string, "", another_char) :: binary>>
end
defp do_rstrip(<<>>, _, _) do
@@ -387,7 +373,7 @@ defmodule String do
@spec lstrip(t, char) :: t
def lstrip(<<char, rest :: binary>>, char) do
def lstrip(<<char :: utf8, rest :: binary>>, char) do
<<lstrip(rest, char) :: binary>>
end
@@ -427,14 +413,81 @@ defmodule String do
rstrip(lstrip(string, char), char)
end
@doc """
Returns a new binary based on `subject` by replacing the parts
matching `pattern` for `replacement`. By default, it replaces
all entries, except if the `global` option is set to false.
@doc %S"""
Returns a new string of length `len` with `subject` right justified and
padded with `padding`. If `padding` is not present, it defaults to
whitespace. When `len` is less than the length of `subject`, `subject` is
returned.
If the replaced part must be used in `replacement`, then the
position or the positions where it is to be inserted must be
specified by using the option `insert_replaced`.
## Examples
iex> String.rjust("abc", 5)
" abc"
iex> String.rjust("abc", 5, ?-)
"--abc"
"""
@spec rjust(t, pos_integer) :: t
@spec rjust(t, pos_integer, char) :: t
def rjust(subject, len) do
rjust(subject, len, ?\s)
end
def rjust(subject, len, padding) when is_integer(padding) do
do_justify(subject, len, padding, :right)
end
@doc %S"""
Returns a new string of length `len` with `subject` left justified and padded
with `padding`. If `padding` is not present, it defaults to whitespace. When
`len` is less than the length of `subject`, `subject` is returned.
## Examples
iex> String.ljust("abc", 5)
"abc "
iex> String.ljust("abc", 5, ?-)
"abc--"
"""
@spec ljust(t, pos_integer) :: t
@spec ljust(t, pos_integer, char) :: t
def ljust(subject, len) do
ljust(subject, len, ?\s)
end
def ljust(subject, len, padding) when is_integer(padding) do
do_justify(subject, len, padding, :left)
end
defp do_justify(subject, 0, _padding, _type) do
subject
end
defp do_justify(subject, len, padding, type) when is_integer(padding) do
subject_len = length(subject)
cond do
subject_len >= len ->
subject
subject_len < len ->
fill = duplicate(<<padding :: utf8>>, len - subject_len)
case type do
:left -> subject <> fill
:right -> fill <> subject
end
end
end
@doc %S"""
Returns a new binary based on `subject` by replacing the parts
matching `pattern` by `replacement`. By default, it replaces
all entries, except if the `global` option is set to `false`.
A `pattern` may be a string or a regex.
## Examples
@@ -442,6 +495,19 @@ defmodule String do
"a-b-c"
iex> String.replace("a,b,c", ",", "-", global: false)
"a-b,c"
The pattern can also be a regex. In those cases, one can give `\N`
in the `replacement` string to access a specific catpure in the regex:
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
"a,bb,cc"
Notice we had to escape the escape character `\`. By giving `&`,
one can inject the whole matched pattern in the replacement string.
When strings are used as a pattern, a developer can also use the
replaced part inside the `replacement` via the `:insert_replaced` option:
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
"a,[b],c"
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
@@ -453,7 +519,13 @@ defmodule String do
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options // []) do
def replace(subject, pattern, replacement, options // [])
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
Regex.replace(pattern, subject, replacement, global: options[:global])
end
def replace(subject, pattern, replacement, options) do
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
end
@@ -468,6 +540,30 @@ defmodule String do
opts
end
@doc """
Reverses the given string. Works on graphemes.
## Examples
iex> String.reverse("abcd")
"dcba"
iex> String.reverse("hello world")
"dlrow olleh"
iex> String.reverse("hello ∂og")
"go∂ olleh"
"""
@spec reverse(t) :: t
def reverse(string) do
do_reverse(next_grapheme(string), [])
end
defp do_reverse({grapheme, rest}, acc) do
do_reverse(next_grapheme(rest), [grapheme|acc])
end
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
@doc """
Returns a binary `subject` duplicated `n` times.
@@ -509,7 +605,7 @@ defmodule String do
remaining of the string or `:no_codepoint` in case
the string reached its end.
As the other functions in the String module, this
As with other functions in the String module, this
function does not check for the validity of the codepoint.
That said, if an invalid codepoint is found, it will
be returned by this function.
@@ -520,10 +616,11 @@ 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
@doc %B"""
@doc %S"""
Checks whether `str` contains only valid characters.
## Examples
@@ -554,7 +651,7 @@ defmodule String do
def valid?(<<>>), do: true
def valid?(_), do: false
@doc %B"""
@doc %S"""
Checks whether `str` is a valid character.
All characters are codepoints, but some codepoints
@@ -578,30 +675,10 @@ defmodule String do
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc %B"""
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
@@ -610,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.
@@ -625,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,
@@ -650,7 +728,7 @@ defmodule String do
@doc """
Returns the last grapheme from an utf8 string,
nil if the string is empty.
`nil` if the string is empty.
## Examples
@@ -718,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
@@ -738,7 +816,7 @@ defmodule String do
@doc """
Returns a substring starting at the offset given by the first, and
a length given by the second.
If the offset is greater than string length, than it returns nil.
If the offset is greater than string length, than it returns `nil`.
## Examples
@@ -762,18 +840,84 @@ defmodule String do
"""
@spec slice(t, integer, integer) :: grapheme | nil
def slice(string, start, len) when start >= 0 do
def slice(string, start, 0) do
case abs(start) <= length(string) do
true -> ""
false -> nil
end
end
def slice(string, start, len) when start >= 0 and len >= 0 do
do_slice(next_grapheme(string), start, start + len - 1, 0, "")
end
def slice(string, start, len) when start < 0 do
real_start_pos = do_length(next_grapheme(string)) - abs(start)
def slice(string, start, len) when start < 0 and len >= 0 do
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
@@ -801,68 +945,21 @@ defmodule String do
end
end
@doc """
Converts a string to an integer. If successful, returns a
tuple of 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
@doc false
def to_integer(string) do
{result, remainder} = :string.to_integer(binary_to_list(string))
case result do
:error -> :error
_ -> {result, list_to_binary(remainder)}
end
IO.write "String.to_integer/1 is deprecated, please use Integer.parse/1 instead\n#{Exception.format_stacktrace}"
Integer.parse(string)
end
@doc """
Converts a string to a float. If successful, returns a
tuple of form {float, remainder of string}. If unsuccessful,
returns :error. If given an integer value, will return
same as to_integer/1.
## 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
@doc false
def to_float(string) do
charlist = binary_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), list_to_binary(int_remainder)}
end
_ -> {result, list_to_binary(remainder)}
end
IO.write "String.to_float/1 is deprecated, please use Float.parse/1 instead\n#{Exception.format_stacktrace}"
Float.parse(string)
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.
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.
## Examples
@@ -877,14 +974,14 @@ 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
do_starts_with(string, prefix)
end
defp do_starts_with(_, "") do
defp do_starts_with(string, "") when is_binary(string) do
true
end
@@ -897,8 +994,8 @@ defmodule String do
end
@doc """
Returns true if `string` ends with any of the suffixes given, otherwise
false. `suffixes` can be either a single suffix or a list of suffixes.
Returns `true` if `string` ends with any of the suffixes given, otherwise
`false`. `suffixes` can be either a single suffix or a list of suffixes.
## Examples
@@ -913,14 +1010,14 @@ 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
do_ends_with(string, suffix)
end
defp do_ends_with(_, "") do
defp do_ends_with(string, "") when is_binary(string) do
true
end
@@ -936,7 +1033,7 @@ defmodule String do
end
@doc """
Returns true if `string` contains match, otherwise false.
Returns `true` if `string` contains match, otherwise `false`.
`matches` can be either a single string or a list of strings.
## Examples
@@ -952,14 +1049,14 @@ 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
do_contains(string, match)
end
defp do_contains(_, "") do
defp do_contains(string, "") when is_binary(string) do
true
end
@@ -970,4 +1067,125 @@ defmodule String do
defp do_contains(_, _) do
raise ArgumentError
end
defexception UnicodeConversionError, encoded: nil, rest: nil, kind: nil do
def message(exception) do
"#{exception.kind} #{detail(exception.rest)}"
end
defp detail(rest) when is_binary(rest) do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) do
"code point #{h}"
end
end
@doc """
Converts a string into a char list converting each codepoint to its
respective integer value.
## Examples
iex> String.to_char_list("æß")
{ :ok, 'æß' }
iex> String.to_char_list("abc")
{ :ok, 'abc' }
"""
@spec to_char_list(String.t) :: { :ok, char_list } | { :error, list, binary } | { :incomplete, list, binary }
def to_char_list(string) when is_binary(string) do
case :unicode.characters_to_list(string) do
result when is_list(result) ->
{ :ok, result }
{ :error, _, _ } = error ->
error
{ :incomplete, _, _ } = incomplete ->
incomplete
end
end
@doc """
Converts a string into a char list converting each codepoint to its
respective integer value.
In case the conversion fails or is incomplete,
it raises a `String.UnicodeConversionError`.
## Examples
iex> String.to_char_list!("æß")
'æß'
iex> String.to_char_list!("abc")
'abc'
"""
@spec to_char_list!(String.t) :: char_list | no_return
def to_char_list!(string) when is_binary(string) do
case :unicode.characters_to_list(string) do
result when is_list(result) ->
result
{ :error, encoded, rest } ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{ :incomplete, encoded, rest } ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
@doc """
Converts a list of integer codepoints to a string.
## Examples
iex> String.from_char_list([0x00E6, 0x00DF])
{ :ok, "æß" }
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
{ :ok, "abc" }
"""
@spec from_char_list(char_list) :: { :ok, String.t } | { :error, binary, binary } | { :incomplete, binary, binary }
def from_char_list(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
{ :ok, result }
{ :error, _, _ } = error ->
error
{ :incomplete, _, _ } = incomplete ->
incomplete
end
end
@doc """
Converts a list of integer codepoints to a string.
In case the conversion fails, it raises a `String.UnicodeConversionError`.
## Examples
iex> String.from_char_list!([0x00E6, 0x00DF])
"æß"
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
"abc"
"""
@spec from_char_list!(char_list) :: String.t | no_return
def from_char_list!(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
{ :error, encoded, rest } ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
{ :incomplete, encoded, rest } ->
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
end
end
end
+84
View File
@@ -0,0 +1,84 @@
import Kernel, except: [to_string: 1]
defprotocol String.Chars do
@moduledoc %S"""
The String.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
`to_string` which does the conversion.
The `to_string` function automatically imported
by Kernel invokes this protocol. String
interpolation also invokes to_string in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_string(bar)`.
"""
def to_string(thing)
end
defimpl String.Chars, for: Atom do
@doc """
Convert the atom literally to a binary, except
`nil` which is converted to an empty string.
"""
def to_string(nil) do
""
end
def to_string(atom) do
atom_to_binary(atom)
end
end
defimpl String.Chars, for: BitString do
@doc """
Returns the given binary or raises an error for bitstrings.
"""
def to_string(thing) when is_binary(thing) do
thing
end
def to_string(thing) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: thing,
description: "cannot convert a bitstring to a string"
end
end
defimpl String.Chars, for: List do
@doc """
Consider the list is an iolist and converts it
to a binary. This allows a list of binaries, or
a charlist, or a mix of both, to be converted
successfully.
## Examples
iex> to_string('foo')
"foo"
iex> to_string(["foo", 'bar'])
"foobar"
"""
def to_string(char_list), do: String.from_char_list!(char_list)
end
defimpl String.Chars, for: Integer do
@doc """
Simply converts the integer to a string.
"""
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
end
+4 -3
View File
@@ -103,11 +103,11 @@ defmodule Supervisor.Behaviour do
Defaults to `:permanent`;
* `:shutdown` - defines how a child process should be terminated.
Defaults to `5000`;
Defaults to `5000` for a worker and `:infinity` for a supervisor;
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a supervisor, gen_server or gen_fsm. If the
child process is a supervisor, `gen_server` or `gen_fsm`. If the
child process is a gen_event, modules should be `:dynamic`.
Defaults to a list with the given module;
@@ -167,6 +167,7 @@ defmodule Supervisor.Behaviour do
#{@child_doc}
"""
def supervisor(module, args, options // []) do
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
child(:supervisor, module, args, options)
end
@@ -180,4 +181,4 @@ defmodule Supervisor.Behaviour do
{ id, { module, function, args },
restart, shutdown, type, modules }
end
end
end
+129 -78
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.
"""
@@ -53,17 +53,21 @@ defmodule System do
# Get the date at compilation time.
defmacrop get_date do
list_to_binary :httpd_util.rfc1123_date
iolist_to_binary :httpd_util.rfc1123_date
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,17 +85,32 @@ defmodule System do
end
@doc """
Returns the current working directory or nil if one
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(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.
"""
def cwd do
case :file.get_cwd do
{ :ok, list } -> :unicode.characters_to_binary(list)
{ :ok, base } -> String.from_char_list!(base)
_ -> nil
end
end
@doc """
Current working directory, exception on error.
Returns the current working directory or raises `System.NoAccessCwdError`.
"""
def cwd! do
@@ -97,16 +118,23 @@ 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
get_unix_home || get_windows_home
case :os.type() do
{ :win32, _ } -> get_windows_home
_ -> get_unix_home
end
end
@doc """
Same as `user_home` but raises `System.NoHomeError`
instead of returning nil if no user home is set.
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.
"""
def user_home! do
user_home || raise NoHomeError
@@ -117,16 +145,20 @@ defmodule System do
end
defp get_windows_home do
get_env("USERPROFILE") || (
hd = get_env("HOMEDRIVE")
hp = get_env("HOMEPATH")
hd && hp && hd <> hp
:filename.absname(
get_env("USERPROFILE") || (
hd = get_env("HOMEDRIVE")
hp = get_env("HOMEPATH")
hd && hp && hd <> hp
)
)
end
@doc %B"""
@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
@@ -134,28 +166,30 @@ defmodule System do
4. `C:\TMP` on Windows or `/tmp` on Unix
5. As a last resort, the current working directory
Returns nil if none of the above are writable.
Returns `nil` if none of the above are writable.
"""
def tmp_dir do
write_env_tmp_dir('TMPDIR') ||
write_env_tmp_dir('TEMP') ||
write_env_tmp_dir('TMP') ||
write_tmp_dir("/tmp") ||
write_env_tmp_dir('TEMP') ||
write_env_tmp_dir('TMP') ||
write_tmp_dir('/tmp') ||
((cwd = cwd()) && write_tmp_dir(cwd))
end
@doc """
Same as `tmp_dir` but raises `System.NoTmpDirError`
instead of returning nil if no temp dir is set.
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
tmp_dir || raise NoTmpDirError
end
defp write_env_tmp_dir(env) do
case get_env(env) do
nil -> nil
tmp -> write_tmp_dir tmp
case :os.getenv(env) do
false -> nil
tmp -> write_tmp_dir(tmp)
end
end
@@ -166,26 +200,31 @@ defmodule System do
access_index = File.Stat.__record__(:index, :access)
case { elem(info, type_index), elem(info, access_index) } do
{ :directory, access } when access in [:read_write, :write] ->
dir
_ -> nil
String.from_char_list!(dir)
_ ->
nil
end
{ :error, _ } -> nil
end
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.
@@ -193,17 +232,22 @@ defmodule System do
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec cmd(binary) :: binary
@spec cmd(char_list) :: char_list
@spec cmd(String.t) :: String.t
def cmd(command) when is_list(command) do
:os.cmd(command)
end
def cmd(command) do
list_to_binary :os.cmd(to_char_list(command))
def cmd(command) when is_binary(command) do
# Notice we don't use unicode for conversion
# because the OS is expecting and returning raw bytes
:binary.list_to_bin :os.cmd(:binary.bin_to_list(command))
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
@@ -213,61 +257,77 @@ defmodule System do
If `program` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(binary) :: binary | nil
@spec find_executable(char_list) :: char_list | nil
@spec find_executable(String.t) :: String.t | nil
def find_executable(program) when is_list(program) do
:os.find_executable(program) || nil
end
def find_executable(program) do
case :os.find_executable(to_char_list(program)) do
def find_executable(program) when is_binary(program) do
# Notice we don't use unicode for conversion
# because the OS is expecting and returning raw bytes
case :os.find_executable(:binary.bin_to_list(program)) do
false -> nil
other -> list_to_binary(other)
other -> :binary.list_to_bin(other)
end
end
@doc """
Returns a list of all environment variables. Each environment variable is
given as a single string of the format "VarName=Value", where VarName is the
name of the variable and Value its value.
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.
"""
@spec get_env() :: [{String.t, String.t}]
def get_env do
Enum.map :os.getenv, :unicode.characters_to_binary &1
Enum.map(:os.getenv, fn var ->
var = String.from_char_list! var
[k, v] = String.split var, "=", global: false
{k, v}
end)
end
@doc """
Environment variable value.
Returns the value of the environment variable
`varname` as a binary, or nil if the environment
`varname` as a binary, or `nil` if the environment
variable is undefined.
"""
@spec get_env(String.t) :: String.t | nil
def get_env(varname) do
case :os.getenv(to_char_list(varname)) do
@spec get_env(binary) :: binary | nil
def get_env(varname) when is_binary(varname) do
case :os.getenv(String.to_char_list!(varname)) do
false -> nil
other -> :unicode.characters_to_binary(other)
other -> String.from_char_list!(other)
end
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.
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
"""
@spec get_pid() :: String.t
def get_pid, do: list_to_binary(:os.getpid)
@spec get_pid() :: binary
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(String.t, String.t | char_list) :: :ok
def put_env(varname, value) when is_binary(value) or is_list(value) do
:os.putenv to_char_list(varname), :unicode.characters_to_list(value)
@spec put_env(binary, binary) :: :ok
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
:os.putenv :binary.bin_to_list(varname), String.to_char_list!(value)
:ok
end
@doc """
Set multiple environment variables.
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
@@ -277,23 +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
non-negative integer, the atom `:abort` or any type that can be converted
to a char list.
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;
@@ -304,34 +365,24 @@ defmodule System do
Note that on many platforms, only the status codes 0-255 are supported
by the operating system.
For integer status, Erlang runtime system closes all ports and allows async
threads to finish their operations before exiting. To exit without such
flushing, pass options [flush: false] instead.
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-2
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-1
## Examples
System.halt(0)
System.halt(1, flush: false)
System.halt(1)
System.halt(:abort)
"""
@spec halt() :: no_return
@spec halt(non_neg_integer | List.Chars.t | :abort) :: no_return
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]) :: no_return
def halt(status // 0, options // [])
@spec halt(non_neg_integer | binary | :abort) :: no_return
def halt(status // 0)
def halt(status, options) when is_integer(status) or status == :abort do
:erlang.halt(status, options)
def halt(status) when is_integer(status) or status == :abort do
:erlang.halt(status)
end
def halt(status, options) do
:erlang.halt(to_char_list(status), options)
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
+59 -2
View File
@@ -1,3 +1,60 @@
defmodule Tuple do
@moduledoc false
end
@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
+21 -18
View File
@@ -31,8 +31,8 @@ defmodule URI do
@doc """
Returns the default port for a given scheme.
If the scheme is unknown to URI, returns nil.
Any scheme may be registered via `URI.default_port/2`.
If the scheme is unknown to URI, returns `nil`.
Any scheme may be registered via `default_port/2`.
"""
def default_port(scheme) when is_binary(scheme) do
{ :ok, dict } = :application.get_env(:elixir, :uri)
@@ -40,7 +40,7 @@ defmodule URI do
end
@doc """
Registers a scheme with a default port into Elixir.
Registers a scheme with a default port.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{ :ok, dict } = :application.get_env(:elixir, :uri)
@@ -49,19 +49,19 @@ defmodule URI do
@doc """
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of k=v&k2=v2... where keys and values are
URL encoded as per encode. Keys and values can be any term
that implements the Binary.Chars protocol (i.e. can be converted
to binary).
and returns a string of the form "k=v&k2=v2..." where keys and values are
URL encoded as per `encode/1`. Keys and values can be any term
that implements the `String.Chars` protocol (i.e. can be converted
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
orddict with one entry for each key-value pair. Each key and value will be a
binary. It also does percent-unescaping of both keys and values.
Use decoder/1 if you want to customize or iterate each value manually.
Use `query_decoder/1` if you want to iterate over each value manually.
"""
def decode_query(q, dict // HashDict.new) when is_binary(q) do
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
@@ -98,7 +98,7 @@ defmodule URI do
end
defp pair({k, v}) do
encode(to_binary(k)) <> "=" <> encode(to_binary(v))
encode(to_string(k)) <> "=" <> encode(to_string(v))
end
@doc """
@@ -150,14 +150,14 @@ defmodule URI do
have different default ports. Sometimes the parsing
of portions themselves are different. This parser
is extensible via behavior modules. If you have a
module named URI.MYSCHEME with a function called
'parse' that takes a single argument, the generically
module named `URI.MYSCHEME` with a function called
`parse` that takes a single argument, the generically
parsed URI, that function will be called when this
parse function is passed a URI of that scheme. This
allows you to build on top of what the URI library
currently offers. You also need to define default_port
which takes 0 arguments and returns the default port
for that particular scheme. Take a look at URI.HTTPS for an
currently offers. You also need to define `default_port`
which takes no arguments and returns the default port
for that particular scheme. Take a look at `URI.HTTPS` for an
example of one of these extension modules.
"""
def parse(s) when is_binary(s) do
@@ -192,9 +192,12 @@ defmodule URI do
# Split an authority into its userinfo, host and port parts.
defp split_authority(s) do
s = s || ""
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
{ userinfo, host, port }
end
@@ -207,8 +210,8 @@ defmodule URI do
end
end
defimpl Binary.Chars, for: URI.Info do
def to_binary(URI.Info[] = uri) do
defimpl String.Chars, for: URI.Info do
def to_string(URI.Info[] = uri) do
scheme = uri.scheme
if scheme && (port = URI.default_port(scheme)) do
@@ -1,15 +1,13 @@
defmodule Mix.Version do
@moduledoc %B"""
This module provides functions for parsing and matching
versions with requirements.
defmodule Version do
@moduledoc %S"""
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,13 +52,13 @@ 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,
patch :: non_neg_integer | nil,
pre :: { String.t, non_neg_integer } | nil }
pre :: [String.t] }
import Kernel, except: [match?: 2]
@@ -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, [])
@@ -269,7 +304,7 @@ defmodule Mix.Version do
{ :error, errors } ->
{ :error, Enum.map(errors, fn { :error, reason } ->
to_binary(reason)
to_string(reason)
end) }
end
else
@@ -277,14 +312,17 @@ defmodule Mix.Version do
end
end
@spec parse_version(String.t) :: { :ok, Mix.Version.matchable } | { :error, :invalid_version }
defp nillify(""), do: nil
defp nillify(o), do: o
@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)
major = binary_to_integer(major)
minor = binary_to_integer(minor || "0")
patch = binary_to_integer(patch || "0")
minor = binary_to_integer(minor |> nillify || "0")
patch = binary_to_integer(patch |> nillify || "0")
pre = pre && parse_pre(pre) || []
{ :ok, { major, minor, patch, pre } }
@@ -296,7 +334,7 @@ defmodule Mix.Version do
@doc false
def parse_pre(pre) do
String.split(pre, ".") |> Enum.map fn piece ->
if piece =~ %r/^[1-9][0-9]*$/ do
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
binary_to_integer(piece)
else
piece
@@ -361,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, [] }
@@ -384,39 +422,71 @@ 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_binary(from)]),
to_condition([:'<', to_binary(to)]) }
{ :andalso, to_condition([:'>=', to_string(from)]),
to_condition([:'<', to_string(to)]) }
end
defp to_condition([:'>', version | _]) do
matchspec(version, :'>', :<)
{ major, minor, patch, pre } = Version.to_matchable(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :orelse, { :andalso, { :'==', { :length, :'$4' }, 0 },
{ :'/=', length(pre), 0 } },
{ :andalso, { :'/=', length(pre), 0 },
{ :orelse, { :'>', { :length, :'$4' }, length(pre) },
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
{ :'>', :'$4', { :const, pre } } } } } } } } }
end
defp to_condition([:'>=', version | _]) do
matchspec(version, :'>=', :<)
matchable = Version.to_matchable(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
to_condition([:'>', version]) }
end
defp to_condition([:'<', version | _]) do
matchspec(version, :'<', :>)
{ major, minor, patch, pre } = Version.to_matchable(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :orelse, { :andalso, { :'/=', { :length, :'$4' }, 0 },
{ :'==', length(pre), 0 } },
{ :andalso, { :'/=', { :length, :'$4' }, 0 },
{ :orelse, { :'<', { :length, :'$4' }, length(pre) },
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
{ :'<', :'$4', { :const, pre } } } } } } } } }
end
defp to_condition([:'<=', version | _]) do
matchspec(version, :'=<', :>)
matchable = Version.to_matchable(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
to_condition([:'<', version]) }
end
defp to_condition(current, []) do
@@ -430,41 +500,29 @@ defmodule Mix.Version do
defp to_condition(current, [:'||', operator, version | rest]) do
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
end
defp matchspec(version, comp_op, length_op) do
{ major, minor, patch, pre } = Mix.Version.to_matchable(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { comp_op, {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :orelse, { length_op, { :length, :'$4' }, { :const, length(pre) } },
{ comp_op, :'$4', { :const, pre } } } } } }
end
end
end
defimpl Binary.Chars, for: Mix.Version.Schema do
def to_binary(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_binary(self) <> ">"
"#Version.Schema<" <> to_string(self) <> ">"
end
end
defimpl Binary.Chars, for: Mix.Version.Requirement do
def to_binary({ _, source, _ }) 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
File diff suppressed because it is too large Load Diff
-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
@@ -100,4 +100,4 @@ FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
1FF4; 1FF4; 038F 0345; 038F 0399; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
1FB7; 1FB7; 0391 0342 0345; 0391 0342 0399; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
1FC7; 1FC7; 0397 0342 0345; 0397 0342 0399; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+13 -8
View File
@@ -1509,6 +1509,7 @@
0619;ARABIC SMALL DAMMA;Mn;31;NSM;;;;;N;;;;;
061A;ARABIC SMALL KASRA;Mn;32;NSM;;;;;N;;;;;
061B;ARABIC SEMICOLON;Po;0;AL;;;;;N;;;;;
061C;ARABIC LETTER MARK;Cf;0;AL;;;;;N;;;;;
061E;ARABIC TRIPLE DOT PUNCTUATION MARK;Po;0;AL;;;;;N;;;;;
061F;ARABIC QUESTION MARK;Po;0;AL;;;;;N;;;;;
0620;ARABIC LETTER KASHMIRI YEH;Lo;0;AL;;;;;N;;;;;
@@ -5296,7 +5297,7 @@
180B;MONGOLIAN FREE VARIATION SELECTOR ONE;Mn;0;NSM;;;;;N;;;;;
180C;MONGOLIAN FREE VARIATION SELECTOR TWO;Mn;0;NSM;;;;;N;;;;;
180D;MONGOLIAN FREE VARIATION SELECTOR THREE;Mn;0;NSM;;;;;N;;;;;
180E;MONGOLIAN VOWEL SEPARATOR;Zs;0;WS;;;;;N;;;;;
180E;MONGOLIAN VOWEL SEPARATOR;Cf;0;BN;;;;;N;;;;;
1810;MONGOLIAN DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
1811;MONGOLIAN DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
1812;MONGOLIAN DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
@@ -5751,7 +5752,7 @@
1A18;BUGINESE VOWEL SIGN U;Mn;220;NSM;;;;;N;;;;;
1A19;BUGINESE VOWEL SIGN E;Mc;0;L;;;;;N;;;;;
1A1A;BUGINESE VOWEL SIGN O;Mc;0;L;;;;;N;;;;;
1A1B;BUGINESE VOWEL SIGN AE;Mc;0;L;;;;;N;;;;;
1A1B;BUGINESE VOWEL SIGN AE;Mn;0;NSM;;;;;N;;;;;
1A1E;BUGINESE PALLAWA;Po;0;L;;;;;N;;;;;
1A1F;BUGINESE END OF SECTION;Po;0;L;;;;;N;;;;;
1A20;TAI THAM LETTER HIGH KA;Lo;0;L;;;;;N;;;;;
@@ -7116,6 +7117,10 @@
2062;INVISIBLE TIMES;Cf;0;BN;;;;;N;;;;;
2063;INVISIBLE SEPARATOR;Cf;0;BN;;;;;N;;;;;
2064;INVISIBLE PLUS;Cf;0;BN;;;;;N;;;;;
2066;LEFT-TO-RIGHT ISOLATE;Cf;0;LRI;;;;;N;;;;;
2067;RIGHT-TO-LEFT ISOLATE;Cf;0;RLI;;;;;N;;;;;
2068;FIRST STRONG ISOLATE;Cf;0;FSI;;;;;N;;;;;
2069;POP DIRECTIONAL ISOLATE;Cf;0;PDI;;;;;N;;;;;
206A;INHIBIT SYMMETRIC SWAPPING;Cf;0;BN;;;;;N;;;;;
206B;ACTIVATE SYMMETRIC SWAPPING;Cf;0;BN;;;;;N;;;;;
206C;INHIBIT ARABIC FORM SHAPING;Cf;0;BN;;;;;N;;;;;
@@ -7738,10 +7743,10 @@
2305;PROJECTIVE;So;0;ON;;;;;N;;;;;
2306;PERSPECTIVE;So;0;ON;;;;;N;;;;;
2307;WAVY LINE;So;0;ON;;;;;N;;;;;
2308;LEFT CEILING;Sm;0;ON;;;;;Y;;;;;
2309;RIGHT CEILING;Sm;0;ON;;;;;Y;;;;;
230A;LEFT FLOOR;Sm;0;ON;;;;;Y;;;;;
230B;RIGHT FLOOR;Sm;0;ON;;;;;Y;;;;;
2308;LEFT CEILING;Ps;0;ON;;;;;Y;;;;;
2309;RIGHT CEILING;Pe;0;ON;;;;;Y;;;;;
230A;LEFT FLOOR;Ps;0;ON;;;;;Y;;;;;
230B;RIGHT FLOOR;Pe;0;ON;;;;;Y;;;;;
230C;BOTTOM RIGHT CROP;So;0;ON;;;;;N;;;;;
230D;BOTTOM LEFT CROP;So;0;ON;;;;;N;;;;;
230E;TOP RIGHT CROP;So;0;ON;;;;;N;;;;;
@@ -18740,8 +18745,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
12453;CUNEIFORM NUMERIC SIGN FOUR BAN2 VARIANT FORM;Nl;0;L;;;;4;N;;;;;
12454;CUNEIFORM NUMERIC SIGN FIVE BAN2;Nl;0;L;;;;5;N;;;;;
12455;CUNEIFORM NUMERIC SIGN FIVE BAN2 VARIANT FORM;Nl;0;L;;;;5;N;;;;;
12456;CUNEIFORM NUMERIC SIGN NIGIDAMIN;Nl;0;L;;;;-1;N;;;;;
12457;CUNEIFORM NUMERIC SIGN NIGIDAESH;Nl;0;L;;;;-1;N;;;;;
12456;CUNEIFORM NUMERIC SIGN NIGIDAMIN;Nl;0;L;;;;2;N;;;;;
12457;CUNEIFORM NUMERIC SIGN NIGIDAESH;Nl;0;L;;;;3;N;;;;;
12458;CUNEIFORM NUMERIC SIGN ONE ESHE3;Nl;0;L;;;;1;N;;;;;
12459;CUNEIFORM NUMERIC SIGN TWO ESHE3;Nl;0;L;;;;2;N;;;;;
1245A;CUNEIFORM NUMERIC SIGN ONE THIRD DISH;Nl;0;L;;;;1/3;N;;;;;
+191 -60
View File
@@ -1,10 +1,9 @@
# This file has its own compilation step because
# it needs to parse String.Unicode data and
# compile a digested module.
# This file has its own compilation step because it needs to parse
# String.Unicode and String.Graphemes data and compile digested modules.
defmodule String.Unicode do
@moduledoc false
def version, do: {6,2,0}
def version, do: {6,3,0}
to_binary = fn
"" ->
@@ -45,23 +44,13 @@ defmodule String.Unicode do
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
end
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
seqs = Enum.map File.stream!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
codepoints = :binary.split(codepoints, " ", [:global])
codepoints = Enum.map codepoints, Regex.replace(%r/\s+/, &1, "")
codepoints = Enum.filter codepoints, fn(x) -> size(x) > 0 end
Enum.map codepoints, to_binary.(&1)
end
# Downcase
def downcase(string), do: do_downcase(string) |> list_to_binary
def downcase(string), do: do_downcase(string) |> iolist_to_binary
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
defp do_downcase(unquote(codepoint) <> rest) do
unquote(binary_to_list(lower)) ++ downcase(rest)
unquote(:binary.bin_to_list(lower)) ++ downcase(rest)
end
end
@@ -73,11 +62,11 @@ defmodule String.Unicode do
# Upcase
def upcase(string), do: do_upcase(string) |> list_to_binary
def upcase(string), do: do_upcase(string) |> iolist_to_binary
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
defp do_upcase(unquote(codepoint) <> rest) do
unquote(binary_to_list(upper)) ++ do_upcase(rest)
unquote(:binary.bin_to_list(upper)) ++ do_upcase(rest)
end
end
@@ -118,7 +107,7 @@ defmodule String.Unicode do
end
lc codepoint inlist whitespace do
c = binary_to_list(codepoint) |> :lists.reverse
c = :binary.bin_to_list(codepoint) |> :lists.reverse
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
@@ -133,7 +122,7 @@ defmodule String.Unicode do
do_rstrip(rest, nil, [char|acc1])
end
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> list_to_binary
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> iolist_to_binary
# Split
@@ -145,11 +134,7 @@ defmodule String.Unicode do
lc codepoint inlist whitespace do
defp do_split(unquote(codepoint) <> rest, buffer, acc) do
if buffer != "" do
do_split(rest, "", [buffer | acc])
else
do_split(rest, buffer, acc)
end
do_split(rest, "", add_buffer_to_acc(buffer, acc))
end
end
@@ -158,43 +143,13 @@ defmodule String.Unicode do
end
defp do_split(<<>>, buffer, acc) do
if buffer != "" do
[buffer | acc]
else
acc
end
add_buffer_to_acc(buffer, acc)
end
# Graphemes
@compile { :inline, add_buffer_to_acc: 2 }
lc codepoints inlist seqs do
def next_grapheme(<< unquote_splicing(codepoints), t :: binary >>) do
{ << unquote_splicing(codepoints) >>, t }
end
end
def next_grapheme(<<>>) do
:no_grapheme
end
def next_grapheme(binary) when is_binary(binary) do
case next_codepoint(binary) do
:no_codepoint -> :no_grapheme
other -> other
end
end
def graphemes(binary) when is_binary(binary) do
do_graphemes(next_grapheme(binary))
end
defp do_graphemes({ c, rest }) do
[c|do_graphemes(next_grapheme(rest))]
end
defp do_graphemes(:no_grapheme) do
[]
end
defp add_buffer_to_acc("", acc), do: acc
defp add_buffer_to_acc(buffer, acc), do: [buffer|acc]
# Codepoints
@@ -222,3 +177,179 @@ defmodule String.Unicode do
[]
end
end
defmodule String.Graphemes do
@moduledoc false
cluster_path = Path.join(__DIR__, "GraphemeBreakProperty.txt")
regex = %r/(?:^([0-9A-F]+)(?:\.\.([0-9A-F]+))?)\s+;\s(\w+)/m
to_range = fn
first, "" ->
[<< binary_to_integer(first, 16) :: utf8 >>]
first, last ->
range = binary_to_integer(first, 16)..binary_to_integer(last, 16)
Enum.map(range, fn(int) -> << int :: utf8 >> end)
end
cluster = Enum.reduce File.stream!(cluster_path), HashDict.new, fn(line, dict) ->
[ _full, first, last, class ] = Regex.run(regex, line)
# Skip surrogates
if first == "D800" and last == "DFFF" do
dict
else
list = to_range.(first, last)
Dict.update(dict, class, list, &(&1 ++ list))
end
end
# There is no codepoint marked as Prepend by Unicode 6.3.0
if cluster["Prepend"] do
raise "It seems this new unicode version has added Prepend items. " <>
"Please remove this error and uncomment the code below."
end
# Don't break CRLF
def next_grapheme(<< ?\n, ?\r, rest :: binary >>) do
{ "\n\r", rest }
end
# Break on control
lc codepoint inlist cluster["CR"] ++ cluster["LF"] ++ cluster["Control"] do
def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
{ << unquote(codepoint) >>, rest }
end
end
# Break on Prepend*
# lc codepoint inlist cluster["Prepend"] do
# def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
# next_prepend(<< unquote(codepoint) >>, rest)
# end
# end
# Handle Hangul L
lc codepoint inlist cluster["L"] do
def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
next_hangul_l(<< unquote(codepoint) >>, rest)
end
end
# Handle Hangul T
lc codepoint inlist cluster["T"] do
def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
next_hangul_t(<< unquote(codepoint) >>, rest)
end
end
# Handle Regional
lc codepoint inlist cluster["Regional_Indicator"] do
def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
next_regional(<< unquote(codepoint) >>, rest)
end
end
# Handle extended entries
def next_grapheme(<< cp :: utf8, rest :: binary >>) do
next_extend(<< cp :: utf8 >>, rest)
end
def next_grapheme(<< cp, rest :: binary >>) do
{ <<cp>>, rest }
end
def next_grapheme(<<>>) do
:no_grapheme
end
# Handle Hangul L
lc codepoint inlist cluster["L"] do
defp next_hangul_l(head, << unquote(codepoint), rest :: binary >>) do
next_hangul_l(head <> unquote(codepoint), rest)
end
end
lc codepoint inlist cluster["LV"] do
defp next_hangul_l(head, << unquote(codepoint), rest :: binary >>) do
next_hangul_v(head <> unquote(codepoint), rest)
end
end
lc codepoint inlist cluster["LVT"] do
defp next_hangul_l(head, << unquote(codepoint), rest :: binary >>) do
next_hangul_t(head <> unquote(codepoint), rest)
end
end
defp next_hangul_l(head, tail) do
next_hangul_v(head, tail)
end
# Handle Hangul V
lc codepoint inlist cluster["V"] do
defp next_hangul_v(head, << unquote(codepoint), rest :: binary >>) do
next_hangul_v(head <> unquote(codepoint), rest)
end
end
defp next_hangul_v(head, tail) do
next_hangul_t(head, tail)
end
# Handle Hangul T
lc codepoint inlist cluster["T"] do
defp next_hangul_t(head, << unquote(codepoint), rest :: binary >>) do
next_hangul_t(head <> unquote(codepoint), rest)
end
end
defp next_hangul_t(head, tail) do
next_extend(head, tail)
end
# Handle regional
lc codepoint inlist cluster["Regional_Indicator"] do
defp next_regional(head, << unquote(codepoint), rest :: binary >>) do
next_regional(head <> unquote(codepoint), rest)
end
end
defp next_regional(head, tail) do
next_extend(head, tail)
end
# Handle Extend+SpacingMark
lc codepoint inlist cluster["Extend"] ++ cluster["SpacingMark"] do
defp next_extend(head, << unquote(codepoint), rest :: binary >>) do
next_extend(head <> unquote(codepoint), rest)
end
end
defp next_extend(head, tail) do
{ head, tail }
end
# Handle Prepend
# lc codepoint inlist cluster["Prepend"] do
# defp next_prepend(<< unquote(codepoint), rest :: binary >>) do
# next_prepend(head <> unquote(codepoint), rest)
# end
# end
#
# defp next_prepend(head, tail) do
# { head, tail }
# end
def graphemes(binary) when is_binary(binary) do
do_graphemes(next_grapheme(binary))
end
defp do_graphemes({ c, rest }) do
[c|do_graphemes(next_grapheme(rest))]
end
defp do_graphemes(:no_grapheme) do
[]
end
end
+12 -28
View File
@@ -2,11 +2,9 @@
-behaviour(application).
-export([main/1, start_cli/0,
scope_for_eval/1, scope_for_eval/2,
eval/2, eval/3, eval/4, file_type/1,
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").
-include_lib("kernel/include/file.hrl").
%% Top level types
-export_type([char_list/0, as_boolean/1]).
@@ -44,14 +42,6 @@ start_cli() ->
application:start(?MODULE),
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% Helpers
file_type(File) ->
case file:read_file_info(File) of
{ ok, #file_info{type=Type} } -> { ok, Type };
{ error, _ } = Error -> Error
end.
%% EVAL HOOKS
scope_for_eval(Opts) ->
@@ -66,7 +56,7 @@ scope_for_eval(Opts) ->
scope_for_eval(Scope, Opts) ->
File = case lists:keyfind(file, 1, Opts) of
{ file, RawFile } -> to_binary(RawFile);
{ file, RawFile } when is_binary(RawFile) -> RawFile;
false -> Scope#elixir_scope.file
end,
@@ -133,22 +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.
%% INTERNAL HELPERS
to_binary(Bin) when is_binary(Bin) -> Bin;
to_binary(List) when is_list(List) -> list_to_binary(List).
+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.
+4 -1
View File
@@ -83,7 +83,7 @@ extract_args(_) -> error.
% Extract guards when multiple left side args are allowed.
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
{ Left, Right } = elixir_tree_helpers:split_last(Args),
{ Left, Right } = elixir_utils:split_last(Args),
{ Left, extract_or_clauses(Right, []) };
extract_splat_guards(Else) ->
{ Else, [] }.
@@ -199,6 +199,9 @@ has_match_tuple({'case', _, _, _}) ->
has_match_tuple({match, _, _, _}) ->
true;
has_match_tuple({'fun', _, { clauses, _ }}) ->
false;
has_match_tuple(H) when is_tuple(H) ->
has_match_tuple(tuple_to_list(H));
+42 -13
View File
@@ -4,14 +4,13 @@
handle_info/2, terminate/2, code_change/3]).
-behavior(gen_server).
-record(elixir_code_server, {
argv=[],
compilation_status=ok,
argv=[],
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) ->
@@ -64,11 +65,19 @@ handle_call(compilation_status, _From, Config) ->
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) ->
@@ -112,8 +121,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 +136,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.
+99 -118
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(unicode:characters_to_list(Bin), File).
%% Compiles a file to the given path (directory).
string(elixir_utils:characters_to_list(Bin), File).
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,22 +79,53 @@ eval_forms(Forms, Line, Vars, S) ->
Res
end), FS }.
%% Internal API
eval_fun(nil) -> '__FILE__';
eval_fun(_) -> '__MODULE__'.
eval_mod(Fun, Exprs, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
Args = [{ var, Line, '_@MODULE'}, Cons],
Relative = elixir_utils:relative_to_cwd(File),
[
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ Fun, 2 }, { '__RELATIVE__', 0 }] },
{ function, Line, Fun, length(Args), [
{ clause, Line, Args, [], Exprs }
] },
{ function, Line, '__RELATIVE__', 0, [
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
] }
].
retrieve_module_name() ->
elixir_code_server:call(retrieve_module_name).
return_module_name(I) ->
elixir_code_server:cast({ return_module_name, I }).
%% INTERNAL API
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, File, Callback) ->
Options = case get_opt(debug_info) of
true -> [debug_info];
_ -> []
end,
module(Forms, File, Options, false, Callback).
module(Forms, File, Opts, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info];
false -> []
end,
module(Forms, File, Final, false, Callback).
module(Forms, File, RawOptions, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
{ Options, SkipNative } = compile_opts(Forms, RawOptions),
Listname = binary_to_list(File),
{ 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} ->
@@ -102,52 +133,22 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
false -> RawWarnings
end,
format_warnings(Bootstrap, File, Warnings),
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, File, Warnings),
format_errors(File, Errors)
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(),
compiler_options(Forms, Options) ->
EnvOptions = elixir_code_server:call(erl_compiler_options),
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
case SkipNative or lists:member([{native,false}], Options) of
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
false -> { Options ++ EnvOptions, SkipNative }
end.
env_default_opts() ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok,Tokens,_} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok,List} when is_list(List) -> List;
{ok,Term} -> [Term];
{error,_Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_,_,_Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end.
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
contains_on_load([_|T]) -> contains_on_load(T);
contains_on_load([]) -> false.
@@ -156,46 +157,17 @@ no_auto_import() ->
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
module_form_fun(nil) -> '__FILE__';
module_form_fun(_) -> '__MODULE__'.
module_form(Fun, Exprs, Line, File, Module, Vars) when
is_binary(File), is_integer(Line), is_list(Exprs), is_atom(Module) ->
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
%% CORE HANDLING
Args = [{ var, Line, '_@MODULE'}, Cons],
[
{ attribute, Line, file, { binary_to_list(File), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ Fun, 2 }] },
{ function, Line, Fun, length(Args), [
{ clause, Line, Args, [], Exprs }
] }
].
%% Generate module names from code server.
retrieve_module_name() ->
elixir_code_server:call(retrieve_module_name).
return_module_name(I) ->
elixir_code_server:cast({ return_module_name, I }).
%% Receives a module Binary and outputs it in the given path.
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
ok = file:write_file(Path, Binary),
Path.
%% CORE FILES COMPILATION
core() ->
application:start(elixir),
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()].
core_file(File) ->
try
Lists = file(list_to_binary(File)),
Lists = file(File),
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~ts~n", [File])
catch
@@ -206,46 +178,55 @@ core_file(File) ->
core_main() ->
[
"lib/elixir/lib/kernel.ex",
"lib/elixir/lib/keyword.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/list.ex",
"lib/elixir/lib/kernel/typespec.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/exception.ex",
"lib/elixir/lib/code.ex",
"lib/elixir/lib/protocol.ex",
"lib/elixir/lib/enum.ex",
"lib/elixir/lib/inspect/algebra.ex",
"lib/elixir/lib/inspect.ex",
"lib/elixir/lib/binary/chars.ex",
"lib/elixir/lib/io.ex",
"lib/elixir/lib/path.ex",
"lib/elixir/lib/system.ex",
"lib/elixir/lib/kernel/cli.ex",
"lib/elixir/lib/kernel/error_handler.ex",
"lib/elixir/lib/kernel/parallel_compiler.ex",
"lib/elixir/lib/kernel/record_rewriter.ex",
"lib/elixir/lib/module/dispatch_tracker.ex"
<<"lib/elixir/lib/kernel.ex">>,
<<"lib/elixir/lib/keyword.ex">>,
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/record.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
<<"lib/elixir/lib/enum.ex">>,
<<"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">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/system.ex">>,
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>,
<<"lib/elixir/lib/module/dispatch_tracker.ex">>
].
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
ok = file:write_file(Path, Binary),
Path.
%% ERROR HANDLING
format_error({ skip_native, Module }) ->
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
[elixir_errors:inspect(Module)]).
format_errors(_File, []) ->
format_errors([]) ->
exit({ nocompile, "compilation failed but no error was raised" });
format_errors(File, Errors) ->
lists:foreach(fun ({_, Each}) ->
format_errors(Errors) ->
lists:foreach(fun ({File, Each}) ->
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
end, Errors).
format_warnings(Bootstrap, File, Warnings) ->
lists:foreach(fun ({_, Each}) ->
format_warnings(Bootstrap, Warnings) ->
lists:foreach(fun ({File, Each}) ->
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
end, Warnings).
+35 -41
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
@@ -152,21 +139,21 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO)
ok;
_ ->
Env = elixir_scope:to_ex_env({ Line, S }),
elixir_module:eval_callbacks(Line, Module, on_definition,
[Env, Kind, Name, Args, Guards, Expr], S)
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
end.
%% Retrieve @file or fallback to default
retrieve_file(Line, Module, S, CO) ->
case elixir_compiler:get_opt(internal, CO) of
true -> { binary_to_list(S#elixir_scope.file), Line };
true -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
_ ->
case 'Elixir.Module':get_attribute(Module, file) of
nil -> { binary_to_list(S#elixir_scope.file), Line };
nil -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
Else ->
'Elixir.Module':delete_attribute(Module, file),
Else
{ Else, 0 }
end
end.
@@ -239,9 +226,9 @@ unwrap_stored_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], []).
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro, Functions, Tail) ->
unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
Tuple = element(1, Fun),
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
@@ -252,15 +239,19 @@ unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro,
end,
{ NewFunctions, NewTail } = case NewFun of
false -> { Functions, Tail };
_ -> function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
false ->
NewAll = All,
{ Functions, Tail };
_ ->
NewAll = [Tuple|All],
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
end,
unwrap_stored_definition(T, CTable, File, NewExports, NewPrivate,
unwrap_stored_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_stored_definition([], _CTable, _File, Exports, Private, Def, Defmacro, Functions, Tail) ->
{ Exports, Private, ordsets:from_list(Def),
unwrap_stored_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
{ All, Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
@@ -305,12 +296,13 @@ default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = { Name, Arity },
case ets:lookup(Table, Tuple) of
[{ Tuple, StoredKind, StoredLine, _, StoredCheck, StoredLocation, StoredDefaults }] ->
[{ Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults }] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults),
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table),
(Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
[] ->
FinalLine = Line,
@@ -328,13 +320,14 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE,
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
check_valid_clause(Line, File, Name, Arity, Kind, Table) ->
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
case ets:lookup_element(Table, last, 2) of
{Name,Arity} -> [];
[] -> [];
_ ->
Relative = elixir_utils:relative_to_cwd(StoredFile),
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
{ override_function, { Kind, Name, Arity } } })
{ ungrouped_clause, { Kind, Name, Arity, StoredLine, Relative } } })
end.
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
@@ -381,8 +374,9 @@ format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({override_function,{Kind,Name,Arity}}) ->
io_lib:format("trying to override previously defined ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({ungrouped_clause,{Kind,Name,Arity,OrigLine,OrigFile}}) ->
io_lib:format("clauses for the same ~ts should be grouped together, ~ts ~ts/~B was previously defined (~ts:~B)",
[Kind, Kind, Name, Arity, OrigFile, OrigLine]);
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
+5 -5
View File
@@ -5,11 +5,11 @@
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
try elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
elixir_tracker:record_local(Tuple, Module, Function),
get_function(Line, Module, Clauses);
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro, Clauses /= [];
All, Kind == defmacrop, Clauses /= [] ->
fun() -> get_function(Line, Module, Clauses) end;
_ ->
false
catch
@@ -19,7 +19,7 @@ macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case elixir_def:lookup_definition(Module, Tuple) of
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
%% There is no need to record such calls
%% since they come from funtions that were
%% already analyzed at compilation time.
+2 -15
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) ->
@@ -85,5 +72,5 @@ format_error({ no_super, Module, { Name, Arity } }) ->
format_fa({ Name, Arity }) ->
A = atom_to_binary(Name, utf8),
B = list_to_binary(integer_to_list(Arity)),
B = integer_to_binary(Arity),
<< A/binary, $/, B/binary >>.
+74 -50
View File
@@ -17,16 +17,18 @@ default_functions() ->
default_macros() ->
[ { ?builtin, elixir_imported_macros() } ].
default_requires() ->
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
find_import(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),
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,15 +41,20 @@ 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 } ->
false;
{ import, Receiver } ->
require_function(Meta, Receiver, Name, Arity, S);
nomatch ->
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
false ->
case elixir_import:special_form(Name, Arity) of
true -> false;
false ->
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
end
end.
require_function(Meta, Receiver, Name, Arity, S) ->
@@ -56,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.
@@ -69,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;
@@ -76,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 ->
@@ -83,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.
@@ -97,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 };
@@ -105,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.
@@ -119,27 +132,45 @@ expand_import(Meta, Tuple, Args, Module, Extra, S) ->
do_expand_import(Meta, Tuple, Args, Module, S, Result).
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
Function = S#elixir_scope.function,
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, S),
case Fun of
false ->
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
_ ->
case Result of
{ Kind, Receiver } when Kind == import; Receiver == Module ->
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
{ _, Receiver } ->
Error = { macro_conflict, { Receiver, Name, Arity } },
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
false ->
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
end
end.
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
case Result of
{ macro, ?builtin } ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
end;
{ macro, Receiver } ->
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
{ import, Receiver } ->
expand_require(Meta, Receiver, Tuple, Args, Module, S);
_ ->
Fun = (S#elixir_scope.function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, S),
case Fun of
false -> { error, noexpansion };
_ ->
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
end
{ error, noexpansion }
end.
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
@@ -148,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 ->
@@ -156,19 +188,14 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
end;
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, false, S),
Function = S#elixir_scope.function,
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true ->
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
false -> { error, noexpansion }
end;
_ ->
{ 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
@@ -190,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) ->
@@ -199,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, [mfa(Line, S)|erlang:get_stacktrace()])
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
end.
mfa(Line, #elixir_scope{module=nil} = S) ->
@@ -246,7 +274,7 @@ find_dispatch(Meta, Tuple, Extra, S) ->
case { FunMatch, MacMatch } of
{ [], [Receiver] } -> { macro, Receiver };
{ [Receiver], [] } -> { function, Receiver };
{ [], [] } -> nomatch;
{ [], [] } -> false;
_ ->
{ Name, Arity } = Tuple,
[First, Second|_] = FunMatch ++ MacMatch,
@@ -281,17 +309,19 @@ not_an_import(Tuple, Context, Dict) ->
true
end.
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
%% We've reached the invoked macro, skip it with the rest
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
munge_stacktrace(Info, [{ elixir_dispatch, expand_macro_fun, _, _ }|_], _) ->
%% We've reached the elixir_dispatch internals, skip it with the rest
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(_, [], _) ->
[].
prune_stacktrace(Info, [], _) ->
[Info].
remote_function(Meta, Receiver, Name, Arity, S) ->
Line = ?line(Meta),
@@ -312,10 +342,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], ", "),
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({ macro_conflict, { Receiver, Name, Arity } }) ->
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B",
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
@@ -363,17 +397,12 @@ in_elixir_macros() ->
in_erlang_functions() ->
[
{ abs, 1 },
{ atom_to_binary, 2 },
{ 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 },
{ binary_to_integer, 2 },
{ binary_to_list, 1 },
{ binary_to_list, 3 },
{ binary_to_term, 1 },
{ binary_to_term, 2 },
{ bit_size, 1 },
@@ -408,13 +437,11 @@ in_erlang_functions() ->
{ is_tuple, 1 },
{ length, 1 },
{ list_to_atom, 1 },
{ list_to_binary, 1 },
{ list_to_bitstring, 1 },
{ list_to_existing_atom, 1 },
{ 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 },
@@ -422,7 +449,6 @@ in_erlang_functions() ->
{ node, 0 },
{ node, 1 },
% { now, 0 },
{ pid_to_list, 1 },
{ round, 1 },
{ self, 0 },
{ size, 1 },
@@ -480,8 +506,6 @@ in_erlang_macros() ->
{defp,1},
{defp,2},
{defp,4},
{function,1},
{function,3},
{in,2},
{'not',1},
{'or',2},
+79 -33
View File
@@ -8,22 +8,22 @@
assert_no_match_scope/3, assert_no_guard_scope/3,
assert_no_match_or_guard_scope/3, warn/1,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4, file_format/3]).
deprecation/3, deprecation/4]).
-include("elixir.hrl").
warn(Warning) ->
elixir_code_server:cast(register_warning),
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));
@@ -33,7 +33,7 @@ syntax_error(Meta, File, Message) when is_binary(Message) ->
syntax_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
raise(Meta, File, 'Elixir.SyntaxError', elixir_utils:characters_to_binary(Message)).
compile_error(Meta, File, Message) when is_list(Message) ->
compile_error(Meta, File, iolist_to_binary(Message));
@@ -43,11 +43,11 @@ compile_error(Meta, File, Message) when is_binary(Message) ->
compile_error(Meta, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Meta, File, 'Elixir.CompileError', unicode:characters_to_binary(Message)).
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
%% 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
@@ -67,7 +67,7 @@ parse_error(Meta, File, Error, Token) ->
BinToken = if
Token == [] -> <<>>;
true -> unicode:characters_to_binary(Token)
true -> elixir_utils:characters_to_binary(Token)
end,
Message = <<BinError / binary, BinToken / binary >>,
@@ -75,7 +75,7 @@ parse_error(Meta, File, Error, Token) ->
%% Raised during compilation
-spec form_error(non_neg_integer(), 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)),
@@ -103,11 +103,30 @@ handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% We handle unused local warnings ourselves
handle_file_warning(_, _File, { _Line, erl_lint, { unused_function, _ } }) -> [];
%% Rewrite
%% Make no_effect clauses pretty
handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A } } }) ->
{ Fmt, Args } = case erl_internal:comp_op(F, A) of
true -> { "use of operator ~ts has no effect", [translate_comp_op(F)] };
false ->
case erl_internal:bif(F, A) of
false -> { "the call to :erlang.~ts/~B has no effect", [F,A] };
true -> { "the call to ~ts/~B has no effect", [F,A] }
end
end,
Message = io_lib:format(Fmt, Args),
warn(file_format(Line, File, Message));
%% Rewrite undefined behaviour to check for protocols
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
{ DefKind, Def, DefArity } =
case atom_to_list(Fun) of
"MACRO-" ++ Rest -> { macro, list_to_atom(Rest), Arity - 1 };
_ -> { function, Fun, Arity }
end,
Kind = protocol_or_behaviour(Module),
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, inspect(Module)]),
warn(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
@@ -118,17 +137,19 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
warn(file_format(Line, File, Message))
end;
%% Ignore unused vars at "weird" lines (<= 0)
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
[];
%% Ignore shadowed vars as we guarantee no conflicts ourselves
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
[];
%% Properly format other unused vars
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
warn(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
warn(file_format(Line, File, Message));
%% Default behavior
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
@@ -137,7 +158,20 @@ 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
'orelse' -> 'or';
'andalso' -> 'and';
_ -> In
end,
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).
@@ -148,18 +182,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.
@@ -168,6 +190,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) ->
@@ -177,15 +211,21 @@ raise(none, File, Kind, Message) ->
raise(0, File, Kind, Message);
raise(Line, File, Kind, Message) when is_integer(Line) ->
%% Populate the stacktrace so we can raise it
try
throw(ok)
catch
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
erlang:raise(error, Exception, Stacktrace).
erlang:raise(error, Exception, tl(Stacktrace)).
file_format(0, File, Message) ->
io_lib:format("~ts: ~ts~n", [File, Message]);
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
file_format(Line, File, Message) ->
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
format_var(Var) ->
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
@@ -211,4 +251,10 @@ is_protocol(Module) ->
end;
{ error, _ } ->
false
end.
end.
translate_comp_op('/=') -> '!=';
translate_comp_op('=<') -> '<=';
translate_comp_op('=:=') -> '===';
translate_comp_op('=/=') -> '!==';
translate_comp_op(Other) -> Other.
+159
View File
@@ -0,0 +1,159 @@
-module(elixir_fn).
-export([fn/3, capture/3]).
-import(elixir_scope, [umergec/2]).
-import(elixir_errors, [syntax_error/3, syntax_error/4, compile_error/4]).
-include("elixir.hrl").
fn(Meta, Clauses, S) ->
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
elixir_clauses:assigns_block(?line(CMeta), fun translate_fn_match/2, Args, [Expr], Guards, umergec(S, Acc))
end,
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
case length(lists:usort(Arities)) of
1 ->
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
_ ->
syntax_error(Meta, S#elixir_scope.file,
"cannot mix clauses with different arities in function definition")
end.
translate_fn_match(Arg, S) ->
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, S) when is_atom(F), is_integer(A) ->
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_require(Meta, { Dot, RequireMeta, Args }, S, true);
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
ImportMeta =
case lists:keyfind(import_fa, 1, Meta) of
{ import_fa, { Receiver, Context } } ->
lists:keystore(context, 1,
lists:keystore(import, 1, Meta, { import, Receiver }),
{ context, Context }
);
false -> Meta
end,
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
capture_import(Meta, { F, ImportMeta, Args }, S, true);
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is_list(Args) ->
capture_require(Meta, Expr, S, is_sequential_and_not_empty(Args));
capture(Meta, { { '.', _, [_] }, _, 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)]);
capture(Meta, { Atom, _, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
capture_import(Meta, Expr, S, is_sequential_and_not_empty(Args));
capture(Meta, { Left, Right }, S) ->
capture(Meta, { '{}', Meta, [Left, Right] }, S);
capture(Meta, List, S) when is_list(List) ->
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]);
capture(Meta, Arg, S) ->
invalid_capture(Meta, Arg, S).
%% Helpers
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, S, Sequential) ->
case Sequential andalso
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
false -> do_capture(Meta, Expr, S, Sequential);
Else -> Else
end.
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, S, Sequential) ->
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
case Sequential andalso is_atom(Mod) andalso
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), SE) of
false -> do_capture(Meta, Expr, S, Sequential);
Else -> Else
end.
do_capture(Meta, Expr, S, Sequential) ->
case do_escape(Expr, S, []) of
{ _, _, [] } when not Sequential ->
invalid_capture(Meta, Expr, S);
{ TExpr, TS, TDict } ->
TVars = validate(Meta, TDict, 1, S),
fn(Meta, [{ TVars, Meta, TExpr }], TS)
end.
invalid_capture(Meta, Arg, S) ->
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]).
validate(Meta, [{ Pos, Var }|T], Pos, S) ->
[Var|validate(Meta, T, Pos + 1, S)];
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
compile_error(Meta, S#elixir_scope.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
validate(_Meta, [], _Pos, _S) ->
[].
do_escape({ '&', Meta, [Pos] }, S, Dict) when is_integer(Pos), Pos > 0 ->
case orddict:find(Pos, Dict) of
{ ok, Var } ->
{ Var, S, Dict };
error ->
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
{ Var, SC, orddict:store(Pos, Var, Dict) }
end;
do_escape({ '&', Meta, [Pos] }, S, _Dict) when is_integer(Pos) ->
compile_error(Meta, S#elixir_scope.file, "capture &~B is not allowed", [Pos]);
do_escape({ '&', Meta, _ } = Arg, S, _Dict) ->
Message = "nested captures via & are not allowed: ~ts",
compile_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]);
do_escape({ Left, Meta, Right }, S0, Dict0) ->
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
{ { TLeft, Meta, TRight }, S2, Dict2 };
do_escape({ Left, Right }, S0, Dict0) ->
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
{ { TLeft, TRight }, S2, Dict2 };
do_escape(List, S, Dict) when is_list(List) ->
do_escape_list(List, S, Dict, []);
do_escape(Other, S, Dict) ->
{ Other, S, Dict }.
do_escape_list([H|T], S, Dict, Acc) ->
{ TH, TS, TDict } = do_escape(H, S, Dict),
do_escape_list(T, TS, TDict, [TH|Acc]);
do_escape_list([], S, Dict, Acc) ->
{ lists:reverse(Acc), S, Dict }.
is_sequential_and_not_empty([]) -> false;
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
is_sequential([{ '&', _, [Int] }|T], Int) ->
is_sequential(T, Int + 1);
is_sequential([], _Int) -> true;
is_sequential(_, _Int) -> false.
+89 -113
View File
@@ -2,76 +2,70 @@
%% in between local functions and imports.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/5, format_error/1,
ensure_no_local_conflict/4]).
-export([import/4, special_form/2, format_error/1]).
-include("elixir.hrl").
%% IMPORT HELPERS
%% IMPORT
%% Update the scope to consider the imports for aliases
%% based on the given options and selector.
import(Meta, Ref, Opts, S) ->
SI =
case keyfind(only, Opts) of
{ only, functions } ->
import_functions(Meta, Ref, Opts, S);
{ only, macros } ->
import_macros(true, Meta, Ref, Opts, S);
{ only, List } when is_list(List) ->
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S));
false ->
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S))
end,
record_warn(Meta, Ref, Opts, SI),
SI.
import(Meta, Ref, Opts, Selector, S) ->
IncludeAll = (Selector == all) or (Selector == default),
import_functions(Meta, Ref, Opts, S) ->
{ Functions, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
S#elixir_scope.macro_functions, fun() -> get_functions(Ref) end, S),
S#elixir_scope{functions=Functions, macro_functions=Temp}.
SF = case IncludeAll or (Selector == functions) of
false -> S;
true ->
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
{ Functions, TempF } = calculate(Meta, Ref, Opts,
S#elixir_scope.functions, S#elixir_scope.macro_functions, FunctionsFun, S),
S#elixir_scope{functions=Functions, macro_functions=TempF}
import_macros(Force, Meta, Ref, Opts, S) ->
Existing = fun() ->
case Force of
true -> get_macros(Meta, Ref, S);
false -> get_optional_macros(Ref)
end
end,
{ Macros, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.macros,
S#elixir_scope.macro_macros, Existing, S),
S#elixir_scope{macros=Macros, macro_macros=Temp}.
SM = case IncludeAll or (Selector == macros) of
false -> SF;
true ->
MacrosFun = fun(K) ->
case IncludeAll of
true -> remove_underscored(K andalso Selector, get_optional_macros(Ref));
false -> get_macros(Meta, Ref, SF)
end
end,
{ Macros, TempM } = calculate(Meta, Ref, Opts,
SF#elixir_scope.macros, SF#elixir_scope.macro_macros, MacrosFun, SF),
SF#elixir_scope{macros=Macros, macro_macros=TempM}
end,
record_warn(Meta, Ref, Opts, S),
SM.
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
calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
File = S#elixir_scope.file,
calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
New = case keyfind(only, Opts) of
{ only, Only } ->
{ only, Only } when is_list(Only) ->
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple);
_ ->
intersection(Only, AvailableFun(false))
intersection(Only, Existing())
end;
false ->
_ ->
case keyfind(except, Opts) of
false -> AvailableFun(true);
{ except, [] } -> AvailableFun(true);
{ except, Except } ->
false -> remove_underscored(Existing());
{ except, [] } -> remove_underscored(Existing());
{ except, Except } when is_list(Except) ->
case keyfind(Key, Old) of
false -> AvailableFun(true) -- Except;
false -> remove_underscored(Existing()) -- Except;
{Key,OldImports} -> OldImports -- Except
end
end
@@ -82,9 +76,11 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
Final = remove_internals(Set),
case Final of
[] -> { keydelete(Key, Old), if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
[] ->
{ keydelete(Key, Old),
if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
_ ->
ensure_no_special_form_conflict(Meta, File, Key, Final, internal_conflict),
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
{ [{ Key, Final }|keydelete(Key, Old)],
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
end.
@@ -139,28 +135,16 @@ get_optional_macros(Module) ->
%% VALIDATION HELPERS
%% Check if any of the locals defined conflicts with an invoked
%% Elixir "implemented in Erlang" macro.
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
ensure_no_special_form_conflict(Meta, File, Module, AllDefined, local_conflict).
%% Ensure the given functions don't clash with any
%% of Elixir non overridable macros.
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
Values = lists:filter(fun({X,Y}) ->
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
end, special_form()),
case Values /= [] of
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T]) ->
case special_form(Name, Arity) of
true ->
Tuple = { Reason, { Key, Name, Arity } },
Tuple = { special_form_conflict, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
false -> ensure_no_special_form_conflict(Meta, File, Key, T, Reason)
false ->
ensure_no_special_form_conflict(Meta, File, Key, T)
end;
ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
%% ERROR HANDLING
@@ -168,10 +152,7 @@ format_error({invalid_import,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({local_conflict,{_, Name, Arity}}) ->
io_lib:format("cannot define local ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
format_error({special_form_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
[elixir_errors:inspect(Receiver), Name, Arity]);
@@ -196,57 +177,52 @@ intersection([], _All) -> [].
%% Internal funs that are never imported etc.
remove_underscored(default, List) ->
remove_underscored(List) ->
lists:filter(fun({ Name, _ }) ->
case atom_to_list(Name) of
"_" ++ _ -> false;
_ -> true
end
end, List);
remove_underscored(_, List) -> List.
end, List).
remove_internals(Set) ->
ordsets:del_element({ module_info, 1 },
ordsets:del_element({ module_info, 0 }, Set)).
%% Macros implemented in Erlang that are not importable.
%% Special forms
special_form() ->
[
{'^',1},
{'=',2},
{'__op__',2},
{'__op__',3},
{'__scope__',2},
{'__block__','*'},
{'->','*'},
{'<<>>','*'},
{'{}','*'},
{'[]','*'},
{'alias',1},
{'alias',2},
{'require',1},
{'require',2},
{'import',1},
{'import',2},
{'import',3},
{'__ENV__',0},
{'__CALLER__',0},
{'__MODULE__',0},
{'__FILE__',0},
{'__DIR__',0},
{'__aliases__','*'},
{'quote',1},
{'quote',2},
{'unquote',1},
{'unquote_splicing',1},
{'fn','*'},
{'super','*'},
{'super?',0},
{'bc','*'},
{'lc','*'},
{'var!',1},
{'var!',2},
{'alias!',1}
].
special_form('&',1) -> true;
special_form('^',1) -> true;
special_form('=',2) -> true;
special_form('__op__',2) -> true;
special_form('__op__',3) -> true;
special_form('__scope__',2) -> true;
special_form('__block__',_) -> true;
special_form('->',_) -> true;
special_form('<<>>',_) -> true;
special_form('{}',_) -> true;
special_form('alias',1) -> true;
special_form('alias',2) -> true;
special_form('require',1) -> true;
special_form('require',2) -> true;
special_form('import',1) -> true;
special_form('import',2) -> true;
special_form('__ENV__',0) -> true;
special_form('__CALLER__',0) -> true;
special_form('__MODULE__',0) -> true;
special_form('__FILE__',0) -> true;
special_form('__DIR__',0) -> true;
special_form('__aliases__',_) -> true;
special_form('quote',1) -> true;
special_form('quote',2) -> true;
special_form('unquote',1) -> true;
special_form('unquote_splicing',1) -> true;
special_form('fn',_) -> true;
special_form('super',_) -> true;
special_form('super?',0) -> true;
special_form('bc',_) -> true;
special_form('lc',_) -> true;
special_form('var!',1) -> true;
special_form('var!',2) -> true;
special_form('alias!',1) -> true;
special_form(_, _) -> false.
+53 -62
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,33 +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) ->
[unicode: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) when is_binary(Form) ->
Form;
wrap_interpol(Line, Form) ->
Meta = [{line,Line}],
{ '::', Meta, [{ { '.', Meta, ['Elixir.Binary.Chars', to_binary] }, 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)]).
+50 -65
View File
@@ -5,71 +5,48 @@
-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_tree_helpers: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(Number, S) when is_integer(Number) ->
{ { integer, 0, Number }, S };
translate([_|_] = Args, S) ->
[RTail|RArgs] = lists:reverse(Args),
translate(Number, S) when is_float(Number) ->
{ { float, 0, Number }, S };
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,
translate(Atom, S) when is_atom(Atom) ->
{ { atom, 0, Atom }, S };
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
{ Tail, ST } = TailFun(SE),
{ elixir_utils:list_to_cons(0, Exprs, Tail), ST };
translate(Bitstring, S) when is_bitstring(Bitstring) ->
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S };
translate(Tuple, S) when is_tuple(Tuple) ->
elixir_errors:compile_error(0, S#elixir_scope.file,
"tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Tuple)]);
translate(Other, S) ->
elixir_errors:compile_error(0, S#elixir_scope.file, "invalid quoted expression: ~ts",
['Elixir.Kernel':inspect(Other)]).
{ elixir_utils:elixir_to_erl(0, Other, S), S }.
%% Helpers
@@ -102,9 +79,16 @@ build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
{ Expr, NS } = Fun(H, S),
case (is_default_or_utf(Types) andalso Expr) of
{ bin, _, BinElements } ->
build_bitstr_each(Fun, T, Meta, NS, rehash_bin_elements(BinElements, Size, Types, []) ++ Acc);
AllowString = types_allow_string(Types),
AllowSplice = types_allow_splice(Types),
AllowAny = (AllowString orelse AllowSplice) andalso (Size == default),
case AllowAny andalso Expr of
{ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] } when AllowString ->
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), { string, 0, String }, Size, Types }|Acc]);
{ bin, _, Elements } when AllowSplice ->
build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
{ cons, _, _, _ } = Cons ->
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
{ nil, _ } ->
@@ -117,16 +101,17 @@ rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
rehash_bin_elements([{ bin_element, Line, Expr, _S, _T }|T], Size, Types, Acc) ->
rehash_bin_elements(T, Size, Types, [{ bin_element, Line, Expr, Size, Types }|Acc]);
types_allow_string([End|T]) when End == little; End == big -> types_allow_string(T);
types_allow_string([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_allow_string(T);
types_allow_string([]) -> true;
types_allow_string(_) -> false.
rehash_bin_elements([], _Size, _Types, Acc) ->
Acc.
is_default_or_utf(default) -> true;
is_default_or_utf([UTF|_]) when UTF == utf8; UTF == utf16; UTF == utf32 -> true;
is_default_or_utf([_|T]) -> is_default_or_utf(T);
is_default_or_utf([]) -> false.
types_allow_splice(default) -> true;
types_allow_splice([bytes]) -> true;
types_allow_splice([binary]) -> true;
types_allow_splice([bits]) -> true;
types_allow_splice([bitstring]) -> true;
types_allow_splice(_) -> false.
%% Extra bitstring specifiers
+31 -67
View File
@@ -4,10 +4,8 @@
-export([translate/2]).
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
-import(elixir_scope, [umergec/2, umergea/2]).
-import(elixir_errors, [compile_error/3, compile_error/4,
syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3,
assert_no_match_or_guard_scope/3]).
-import(elixir_errors, [compile_error/3, syntax_error/3, syntax_error/4,
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
-include("elixir.hrl").
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
@@ -35,58 +33,21 @@ translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
translate({ '!', Meta, [{ '!', _, [Expr] }] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
elixir_tree_helpers:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
elixir_utils:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
translate({ '!', Meta, [Expr] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
elixir_tree_helpers:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
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) ->
elixir_errors:deprecation(Meta, S#elixir_scope.file, "in operator inside matches is deprecated, please move it to a guard"),
{ TVar, TExpr, TS } = translate_in(Meta, Left, Right, S),
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
%% Functions
translate({ function, Meta, [[{do,{ '->',_,Pairs}}]] }, S) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
elixir_translator:translate_fn(Meta, Pairs, S);
translate({ function, _, [{ '/', _, [{{ '.', Meta, [M, F] }, _ , []}, A]}] }, S) when is_atom(F), is_integer(A) ->
translate({ function, Meta, [M, F, A] }, S);
translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
WrappedMeta =
case lists:keyfind(import_fa, 1, MetaFA) of
{ import_fa, { Receiver, Context } } ->
lists:keystore(context, 1,
lists:keystore(import, 1, Meta, { import, Receiver }),
{ context, Context }
);
false -> Meta
end,
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
false -> compile_error(WrappedMeta, S#elixir_scope.file,
"expected ~ts/~B to be a function, but it is a macro", [F, A]);
Else -> Else
end;
translate({ function, Meta, [Arg] }, S) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
syntax_error(Meta, S#elixir_scope.file, "invalid args for function/1: ~ts",
['Elixir.Macro':to_string(Arg)]);
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, 'function', S),
{ [A,B,C], SA } = translate_args(Args, S),
{ { 'fun', ?line(Meta), { function, A, B, C } }, SA };
%% @
translate({'@', Meta, [{ Name, _, [Arg] }]}, S) when Name == typep; Name == type; Name == spec; Name == callback; Name == opaque ->
@@ -127,7 +88,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
}, S);
_ ->
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
{ elixir_tree_helpers:elixir_to_erl(Contents), S }
{ elixir_utils:elixir_to_erl(?line(Meta), Contents, S), S }
end;
_ ->
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
@@ -141,7 +102,7 @@ translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
{ TExpr, NS } = translate_each(Expr, S),
RClauses = case elixir_tree_helpers:returns_boolean(TExpr) of
RClauses = case elixir_utils:returns_boolean(TExpr) of
true -> rewrite_case_clauses(Clauses);
false -> Clauses
end,
@@ -182,7 +143,7 @@ translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
_ ->
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
{ FClauses, TAfter } = elixir_tree_helpers:split_last(TClauses),
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
{ _, _, [FExpr], _, FAfter } = TAfter,
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
end;
@@ -202,7 +163,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,
@@ -229,15 +190,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) ->
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) ->
@@ -274,20 +226,20 @@ translate_in(Meta, Left, Right, S) ->
false -> { TLeft, SR }
end,
{ InGuard, TExpr } = case TRight of
{ TCache, TExpr } = case TRight of
{ nil, _ } ->
Expr = { atom, Line, false },
{ Cache, Expr };
{ cons, _, _, _ } ->
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
[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
@@ -315,7 +267,7 @@ translate_in(Meta, Left, Right, S) ->
end
end,
case InGuard of
case TCache of
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
false -> { Var, TExpr, SV }
end.
@@ -330,8 +282,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.
@@ -346,7 +308,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) ->
@@ -358,6 +321,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.
+126 -48
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)).
@@ -43,7 +44,7 @@ docs_table(Module) ->
translate(Meta, Ref, Block, S) ->
Line = ?line(Meta),
MetaBlock = elixir_tree_helpers:elixir_to_erl(Block),
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, S),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
@@ -59,18 +60,19 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
end,
File = S#elixir_scope.file,
FileList = binary_to_list(File),
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),
{ Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Functions, C),
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, 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;
@@ -78,18 +80,18 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
end,
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
elixir_tracker:warn_unused_local(File, Module, Private),
elixir_tracker:ensure_no_import_conflict(Line, File, Module, All),
elixir_tracker:ensure_all_imports_used(Line, File, Module),
elixir_import:ensure_no_local_conflict(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 +110,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 +120,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 +129,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),
@@ -149,18 +152,20 @@ eval_form(Line, Module, Block, Vars, RawS) ->
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions, C) ->
Functions = case elixir_compiler:get_opt(internal, C) of
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions, C) ->
BaseFunctions = case elixir_compiler:get_opt(internal, C) of
true -> RawFunctions;
false -> record_rewrite_functions(Module, RawFunctions)
end,
{ FinalExport, FinalFunctions } =
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro, C),
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
{ FinalExport ++ PrivateTuple, [
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
All = [{ '__info__', 1 }|BaseAll],
Export = [{ '__info__', 1 }|BaseExport],
Functions = [Info|BaseFunctions],
{ All, [
{ attribute, Line, export, lists:sort(Export) } | Functions
] }.
record_rewrite_functions(Module, Functions) ->
@@ -196,9 +201,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;
@@ -210,16 +227,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).
@@ -244,8 +262,14 @@ spec_for_macro(Else) -> Else.
%% Loads the form into the code server.
load_form(Line, Forms, S) ->
elixir_compiler:module(Forms, S#elixir_scope.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),
@@ -253,11 +277,16 @@ load_form(Line, Forms, S) ->
case get(elixir_compiled) of
Current when is_list(Current) ->
put(elixir_compiled, [{Module,Binary}|Current]),
case get(elixir_compiler_pid) of
undefined -> [];
PID -> PID ! { module_available, self(), Module, Binary }
PID ->
Ref = make_ref(),
PID ! { module_available, self(), Ref, File, Module, Binary },
receive { Ref, ack } -> ok end
end;
_ -> []
_ ->
[]
end,
Binary
@@ -276,31 +305,56 @@ check_module_availability(Line, File, Module, Compiler) ->
[]
end.
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel', _All) -> ok;
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
warn_invalid_clauses(_Line, File, Module, All) ->
ets:foldl(fun
({ _, _, Kind, _, _ }, _) when Kind == type; Kind == opaque ->
ok;
({ Tuple, Line, _, _, _ }, _) ->
case lists:member(Tuple, All) of
false ->
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, Functions, Def, Defmacro, C) ->
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
Pair = { '__info__', 1 },
case lists:member(Pair, Export) of
true -> elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
true ->
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
false ->
Docs = elixir_compiler:get_opt(docs, C),
Contents = { function, 0, '__info__', 1, [
{ function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Module, Def, Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
module_clause(Module),
else_clause()
] },
{ [Pair|Export], [Contents|Functions] }
] }
end.
functions_clause(Def) ->
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:elixir_to_erl(Def)] }.
{ clause, 0, [{ atom, 0, functions }], [], [elixir_utils:elixir_to_erl(Def)] }.
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:elixir_to_erl(All)] }.
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(All)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
@@ -311,15 +365,18 @@ module_clause(Module) ->
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
docs_clause(Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:elixir_to_erl(Docs)] };
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, _) ->
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:elixir_to_erl({ Line, Docs })] };
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_utils:elixir_to_erl({ Line, Docs })] };
moduledoc_clause(_Line, _Module, _) ->
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
@@ -331,8 +388,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}],
@@ -350,14 +406,36 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
erl_eval:exprs([Tree], Binding)
catch
Kind:Reason ->
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
end
end
end, Callbacks).
%% We've reached the elixir_module or erl_eval internals, skip it with the rest
prune_stacktrace(Info, [{ erl_eval, _, _, _ }|_]) ->
[Info];
prune_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
[Info];
prune_stacktrace(Info, [H|T]) ->
[H|prune_stacktrace(Info, T)];
prune_stacktrace(Info, []) ->
[Info].
% ERROR HANDLING
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]);
+66 -46
View File
@@ -19,16 +19,16 @@ Nonterminals
kw_eol kw_expr kw_comma kw
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_punctuated_identifier dot_bracket_identifier
var list bracket_access bit_string tuple
dot_paren_identifier dot_bracket_identifier
var list bit_string tuple
do_block fn_eol do_eol end_eol block_eol block_item block_list
.
Terminals
identifier kw_identifier punctuated_identifier
bracket_identifier paren_identifier do_identifier block_identifier
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
@@ -42,6 +42,7 @@ Rootsymbol grammar.
%% There are two shift/reduce conflicts coming from call_args_parens.
Expect 2.
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
@@ -62,7 +63,7 @@ Left 220 mult_op_eol. %% * (op), / (op)
Right 230 than_op_eol. %% < (op) > (e.g <>)
Right 240 two_op_eol. %% ++, --, **
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
Left 310 dot_call_op.
Left 310 dot_op. %% .
Nonassoc 320 at_op_eol. %% @ (op)
@@ -95,7 +96,6 @@ matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
matched_expr -> bracket_at_expr : '$1'.
matched_expr -> identifier_expr : '$1'.
no_parens_expr -> dot_punctuated_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
no_parens_expr -> dot_op_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
no_parens_expr -> dot_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
@@ -109,7 +109,6 @@ unmatched_expr -> block_expr : '$1'.
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
block_expr -> dot_punctuated_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
block_expr -> dot_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
op_expr -> match_op_eol expr : { '$1', '$2' }.
@@ -166,10 +165,8 @@ matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
identifier_expr -> dot_punctuated_identifier call_args_no_parens_one : build_identifier('$1', '$2').
identifier_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2').
identifier_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2').
identifier_expr -> dot_punctuated_identifier : build_identifier('$1', []).
identifier_expr -> dot_do_identifier : build_identifier('$1', nil).
identifier_expr -> var : build_identifier('$1', nil).
identifier_expr -> max_expr : '$1'.
@@ -182,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').
@@ -203,9 +200,10 @@ 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 -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
base_expr -> sigil : build_sigil('$1').
base_expr -> '&' number : { '&', [{line,?line('$1')}], [?exprs('$2')] }.
%% Blocks
@@ -364,9 +362,6 @@ dot_bracket_identifier -> matched_expr dot_op bracket_identifier : build_dot('$2
dot_paren_identifier -> paren_identifier : '$1'.
dot_paren_identifier -> matched_expr dot_op paren_identifier : build_dot('$2', '$1', '$3').
dot_punctuated_identifier -> punctuated_identifier : '$1'.
dot_punctuated_identifier -> matched_expr dot_op punctuated_identifier : build_dot('$2', '$1', '$3').
parens_call -> dot_paren_identifier : '$1'.
parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }. % Fun/local calls
@@ -440,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
@@ -482,6 +473,12 @@ Erlang code.
%% Operators
build_op({ _Kind, Line, '/' }, { '&', _, [{ Kind, _, Atom } = Left] }, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left] }, Right) when is_number(Right) ->
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
@@ -499,7 +496,6 @@ build_tuple(Marker, Args) ->
%% Blocks
build_block([nil]) -> { '__block__', [], [nil] };
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
build_block([{unquote_splicing,_,Args}]=Exprs) when
length(Args) =< 2 -> { '__block__', [], Exprs };
@@ -540,8 +536,8 @@ build_identifier({ _, Line, Identifier }, Args) ->
{ Identifier, [{line,Line}], Args }.
extract_identifier({ Kind, _, Identifier }) when
Kind == identifier; Kind == punctuated_identifier; Kind == bracket_identifier;
Kind == paren_identifier; Kind == do_identifier; Kind == op_identifier ->
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
Kind == do_identifier; Kind == op_identifier ->
Identifier;
extract_identifier(Other) -> Other.
@@ -549,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) -> unicode: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, [unicode, characters_to_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
@@ -608,7 +628,7 @@ unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
unwrap_splice(Other) -> Other.
unwrap_when(Args) ->
case elixir_tree_helpers:split_last(Args) of
case elixir_utils:split_last(Args) of
{ Start, { 'when', Meta, [_, _] = End } } ->
[{ 'when', Meta, Start ++ End }];
{ _, _ } ->
-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_translator:translate_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) ->
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.
+111 -49
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,
@@ -150,13 +196,9 @@ do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=tr
%% Imports
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
do_quote({ '&', Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa(function, Meta, Args, F, A, Q, S);
do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa(Target, Meta, Args, F, A, Q, S);
do_quote_fa('&', Meta, Args, F, A, Q, S);
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
@@ -199,8 +241,18 @@ do_quote(Tuple, #elixir_quote{escape=true} = Q, S) when is_tuple(Tuple) ->
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
{ { '{}', [], TT }, TQ };
do_quote(List, #elixir_quote{escape=true} = Q, S) when is_list(List) ->
% The improper case is pretty inefficient, but improper lists are hopefully
% rare.
case reverse_improper(List) of
{ L } -> do_splice(L, Q, S);
{ L, R } ->
{ TL, QL } = do_splice(L, Q, S, [], []),
{ TR, QR } = do_quote(R, QL, S),
{ update_last(TL, fun(X) -> { '|', [], [X, TR] } end), QR }
end;
do_quote(List, Q, S) when is_list(List) ->
do_splice(lists:reverse(List), Q, S);
do_splice(lists:reverse(List), Q, S);
do_quote(Other, Q, _) ->
{ Other, Q }.
@@ -208,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 =
@@ -233,6 +285,16 @@ meta(Meta, #elixir_quote{line=nil}) ->
meta(Meta, #elixir_quote{line=Line}) ->
keystore(line, Meta, Line).
reverse_improper(L) -> reverse_improper(L, []).
reverse_improper([], Acc) -> { Acc };
reverse_improper([H|T], Acc) when is_list(T) -> reverse_improper(T, [H|Acc]);
reverse_improper([H|T], Acc) -> { [H|Acc], T }.
update_last([], _) -> [];
update_last([H], F) -> [F(H)];
update_last([H|T], F) -> [H|update_last(T,F)].
keyfind(Key, Meta) ->
lists:keyfind(Key, 1, Meta).
keydelete(Key, Meta) ->
@@ -248,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 }.

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