Compare commits

...
198 Commits
Author SHA1 Message Date
José Valim 8cede65b0f Release 0.7.1 2012-11-18 20:51:37 +01:00
José Valim bb2e1d5d7c Merge pull request #646 from burningTyger/master
enum doc typo
2012-11-17 02:48:25 -08:00
burningTyger e4f5f9d9ed fix typo in enum docs
RuPy 2012 in talk bug hunt
2012-11-17 11:16:27 +01:00
José Valim e801cbd380 Convert integer to a list in order to generate the module name 2012-11-16 09:50:13 +01:00
José Valim 7894ba0fc1 Add IO.binread, IO.binwrite and friends, closes #645 2012-11-16 09:42:35 +01:00
José Valim ad37d5f67a No need for lists:concat 2012-11-15 16:56:29 +01:00
José Valim 40efeb48d7 Reduce atom footprint when compiling code
Previously, we were generating atom names based on
the file or the module name. This commit replaces
it by a pool of atom names. After a module name
is used and purged with success, it is added back
to the pool so it can be reused many times.
2012-11-15 16:44:27 +01:00
José Valim d03d647560 Bring the same __ENV__ semantics to Module.create 2012-11-15 16:31:26 +01:00
José Valim c3b58eef16 file needs to be a binary 2012-11-15 16:30:55 +01:00
José Valim 32764b3370 Copy all __ENV__ information on eval 2012-11-15 10:01:18 +01:00
José Valim 2104b292c1 Ensure rstrip and strip work on emoty strings, closes #643 2012-11-14 10:34:43 +01:00
José Valim e0bbd47d7b Support R15B 2012-11-13 09:50:28 +01:00
José Valim 743213d8c1 halt was moved to System module
`halt` is a function that is less frequently used than the other
functions in Kernel, not allowed in guard clauses and its outcome
is to shutdown the whole runtime, being a great fit to the System
module.
2012-11-13 09:23:21 +01:00
José Valim 2db5b9c76c Make update no-op if no args are given 2012-11-13 09:23:21 +01:00
José Valim ba6a23a1a4 Merge pull request #640 from clofresh/master
default extracted record vals to :undefined instead of :nil
2012-11-11 23:44:20 -08:00
Carlo Cabanilla b9692fc2fe default record vals to :undefined instead of :nil
When translating erlang records into elixir records, use :undefined
as the default record value since that's what erlang uses, and
presumably you're going to be using these records with erlang apis.
2012-11-12 01:05:17 -05:00
José Valim 8a5f3f3a53 Fix documentation for defrecordp 2012-11-10 21:08:24 +01:00
José Valim c0a42c95e0 Do not print warnings for injected private macros 2012-11-10 12:30:24 +01:00
José Valim 9cad7eb1e2 Use new defrecordp in Regex module 2012-11-10 12:17:52 +01:00
José Valim fcb1a610de Add support for defrecordp 2012-11-10 11:51:13 +01:00
José Valim 04d96f7f18 Update CHANGELOG 2012-11-08 21:20:47 +01:00
José Valim d117dbf04c Filter out compilable files from stale ones in Mix 2012-11-08 21:19:40 +01:00
José Valim ce489eae2b Only allow () under special circunstances 2012-11-08 16:00:16 +01:00
José Valim 02e63e4849 Ensure parenthesis can be used in macros and fn shortcut, closes #634 2012-11-08 15:52:52 +01:00
José Valim cae328112c Fix unecessary double file read 2012-11-08 13:42:25 +01:00
José Valim b2931135d0 Use File.read instead of File.exists? + File.read! 2012-11-08 13:34:47 +01:00
José Valim bddaed1284 Merge pull request #636 from yrashk/exunit-user-config
Add support for ExUnit user configs
2012-11-08 04:30:51 -08:00
José Valim f78433c892 Clean up ExUnit.Formatter 2012-11-08 13:29:45 +01:00
Yurii Rashkovskii f1bbd3f37e Improve mix test helpers with respect to user configs 2012-11-08 03:23:35 -08:00
José Valim b159b1c6bf Merge pull request #637 from elixir-lang/ex_unit_formatter
Flexible formatters for ExUnit
2012-11-08 03:15:49 -08:00
José Valim e6c75bbe98 Merge pull request #632 from yrashk/is_keyword
Add keyword?/1 function
2012-11-08 03:08:58 -08:00
Yurii Rashkovskii c6a012602c Add Keyword.keyword?/1 function 2012-11-08 03:04:55 -08:00
José Valim cc89cf942f Flexible formatters for ExUnit 2012-11-08 12:01:07 +01:00
Yurii Rashkovskii 73d7de7bc9 Add support for ExUnit user configs 2012-11-08 02:49:59 -08:00
José Valim 1ba159ddd7 Merge pull request #635 from yrashk/deps_test-fix
Bring one more check back in mix's deps.git_test
2012-11-07 15:05:49 -08:00
Yurii Rashkovskii 8c3d8fc676 Bring one more check back in mix's deps.git_test 2012-11-07 15:00:51 -08:00
José Valim 8ac3844955 Merge pull request #633 from yrashk/test-kernel-warn-suppress
Suppress an unnecessary (optimization-related) warning in one of the ker...
2012-11-07 07:55:41 -08:00
Yurii Rashkovskii 1b15972d07 Suppress an unnecessary (optimization-related) warning in one of the kernel tests 2012-11-07 06:14:33 -08:00
José Valim 4a2d773e34 Merge pull request #631 from yrashk/defrecord-doc-improvement
Improve defrecord documentation
2012-11-07 05:59:56 -08:00
Yurii Rashkovskii 8185ff7333 Improve defrecord documentation 2012-11-07 05:57:39 -08:00
José Valim 2263d44ccf Merge pull request #630 from yrashk/t-helper-doc-improvement
Improve t helper documentation
2012-11-07 05:05:53 -08:00
Yurii Rashkovskii 46f3696799 Improve t helper documentation 2012-11-07 04:52:30 -08:00
José Valim 9b276f52d9 Update lib/iex/lib/iex/helpers.ex
Fix grammar and improve docs
2012-11-07 13:46:00 +01:00
José Valim 175a2dc1eb Merge pull request #628 from yrashk/t3-helper
Resurrect t/3 iex helper
2012-11-07 04:42:43 -08:00
Yurii Rashkovskii aba27a27e3 Resurrect t/3 iex helper
Closes #627
2012-11-07 04:38:11 -08:00
José Valim 1f56b673bd Support -q when running tests to compile only changed files 2012-11-07 13:04:51 +01:00
José Valim 41dbbbf714 Merge pull request #626 from yrashk/typespec-inspect-improvements-record
Add support for printing record typespecs
2012-11-07 04:01:48 -08:00
Yurii Rashkovskii 1331f23a27 Add support for printing record typespecs 2012-11-07 02:40:45 -08:00
José Valim 52a5f5c9c9 Generate a proper clause var name 2012-11-06 10:19:23 +01:00
José Valim 1ae30a56e2 Do not name quoted variables 2012-11-06 09:54:17 +01:00
José Valim 30bfd4736d Remove Calendar from the upcoming release 2012-11-05 15:19:10 +01:00
José Valim e9334fb7dc Tidy up Typespec API, improve docs 2012-11-05 14:51:51 +01:00
José Valim bd8648d490 Only generate needed clauses in Protocol 2012-11-05 11:20:29 +01:00
José Valim ec68b7dd8a Compile specs and callbacks to @spec and @callback so they are easier to access 2012-11-05 10:26:02 +01:00
José Valim a5539beb37 Use $(RELEASE_FLAG) in Makefile 2012-11-05 09:26:02 +01:00
José Valim 47ce7e3fd8 Actually define types for Macro.Env 2012-11-05 09:21:31 +01:00
José Valim 1113d3cfc9 Add support to record_type in defrecord
Closes #613
Closes #452
2012-11-05 09:14:07 +01:00
José Valim f160f3ee6f Merge remote-tracking branch 'yrashk/record-typespec' into record-typespec 2012-11-05 07:53:36 +01:00
José Valim 1bf06bbc1a Speed up protocols by concatenating modules at compilation time 2012-11-03 10:52:22 +01:00
José Valim 8b981efecd Improve shadowed variable warning 2012-11-03 09:15:46 +01:00
José Valim f95e4512b5 Update CHANGELOG 2012-11-02 10:19:18 +01:00
José Valim 12d14edab5 Deprecate Enum.times, closes #619 2012-11-02 10:17:39 +01:00
José Valim caa50368a7 Check if the first aliases argument is indeed an alias atom 2012-11-02 10:15:19 +01:00
José Valim a144d27886 Warn that function definitions should start with lowercase characters or underscore, closes #620 2012-11-02 10:05:17 +01:00
José Valim 413fcfdd65 Reorganize keyword handling clauses 2012-11-02 10:05:17 +01:00
José Valim 78c6c7201f Keywords are allowed after . identifier, closes #621 2012-11-02 10:05:17 +01:00
José Valim 76cf3e5bba Merge pull request #618 from yrashk/typespec-to_binary-ann_type
Support annotated types in Kernel.Typespec.to_binary
2012-11-01 07:07:51 -07:00
Yurii Rashkovskii 5d2336d23e Support annotated types in Kernel.Typespec.to_binary 2012-11-01 07:05:54 -07:00
José Valim 8280de5fa9 Merge pull request #615 from yrashk/specs-retrieval-fixes
Kernel.Typespec.types/1,3 should recognize typeps and opaques
2012-11-01 06:48:16 -07:00
Yurii Rashkovskii 8cadba5ea4 Kernel.Typespec.types/1,3 should recognize typeps and opaques
Also improves code reuse
2012-11-01 06:37:59 -07:00
José Valim 3750bcc8d1 Remove unused variables 2012-11-01 14:11:10 +01:00
José Valim 3fac73ea17 Merge pull request #609 from yrashk/iex-t
Implement a simple version of the t helper for the interactive shell
2012-11-01 05:23:03 -07:00
José Valim 01f9518899 Keep user's given name when rebinding variables
The following code:

    counter = 1
    counter = 2

Is now rewritten internally to:

    counter   = 1
    counter@1 = 2

Which gives us a couple things:

1. Better debugging
2. We can now show warnings for rebound variables not used

Elixir also keeps `_@` prefix to store internal variables
(for example, `_@1`, `_@MODULE`, etc).

Closes #607.
2012-11-01 12:37:08 +01:00
José Valim 11eb2d6d3c Do not print elixir modules as keywords, closes #603 2012-11-01 10:04:05 +01:00
Yurii Rashkovskii c00381780f Implement a simple version of the t helper for the interactive shell (prints out type/spec information)
Also implements corresponding helper functions in Kernel.Typespec
2012-10-31 22:26:59 -07:00
Yurii Rashkovskii b325e29d23 Record field types definition support 2012-10-31 16:07:13 -07:00
José Valim e2e1747acb Merge pull request #610 from yrashk/fix-typespec-to_binary_tuple_any
Fix Kernel.Typespec.to_binary for tuple() type (with no element type spe...
2012-10-31 08:42:58 -07:00
Yurii Rashkovskii 84501090ce Fix Kernel.Typespec.to_binary for tuple() type (with no element type specified) 2012-10-31 07:26:30 -07:00
José Valim c9dead1fc1 Merge pull request #608 from yrashk/typespec-misc
Typespec misc
2012-10-31 06:33:54 -07:00
Yurii Rashkovskii 4906fac3cf Remove dialyzer warnings from elixir_errors 2012-10-31 06:05:57 -07:00
Yurii Rashkovskii 1c827af937 Remove dialyzer warnings from ExUnit.Assertions 2012-10-31 05:56:38 -07:00
José Valim 06d9b26f94 Merge pull request #606 from yrashk/typespec-dict
Add typespecs for Dict
2012-10-31 05:42:47 -07:00
José Valim aecb1b3716 Merge pull request #605 from yrashk/typespec-enum
Add typespecs for Enum
2012-10-31 05:42:24 -07:00
Yurii Rashkovskii a322635971 Add typespecs for Dict 2012-10-31 05:40:55 -07:00
Yurii Rashkovskii 88fdb20eb0 Add typespecs for Enum
Also fixes some documentation mistakes
2012-10-31 05:29:33 -07:00
José Valim 6d5c2e1d22 Merge pull request #602 from yrashk/typespec-string
Add typespecs for String
2012-10-31 04:59:17 -07:00
José Valim 437ef403c4 Merge pull request #601 from yrashk/typespec-keyword
Add typespecs for Keyword
2012-10-31 04:44:28 -07:00
José Valim d5b6340977 Support (parallel) requires on mix run, closes #495 2012-10-31 12:43:24 +01:00
Yurii Rashkovskii 99befc6715 Add typespecs for Keyword 2012-10-31 04:41:59 -07:00
Yurii Rashkovskii e97e2b310e Add typespecs for String 2012-10-31 04:40:52 -07:00
José Valim d305c96762 Keep arguments order on OptionParser 2012-10-31 12:40:03 +01:00
José Valim e99f623b03 Purge loaded modules on tests 2012-10-31 12:37:18 +01:00
José Valim 562e7e391e Changing deps information on mix.exs forces users to fetch new dependencies
Closes #557
2012-10-31 11:22:09 +01:00
José Valim 69bccec7ae Make SCM formatting more flexible 2012-10-31 10:20:13 +01:00
José Valim e65b18b203 Wrap mix escriptize in Elixir's default CLI setup, closes #560 2012-10-31 10:10:38 +01:00
José Valim 3b33478cc0 Make typespecs compilation happen in two steps, closes #598 2012-10-31 09:44:16 +01:00
José Valim 4d427f9f50 Merge pull request #600 from yrashk/typespecs
Use Behaviour in Dict
2012-10-30 12:59:31 -07:00
Yurii Rashkovskii ea3775ecf0 Use Behaviour in Dict 2012-10-30 07:13:43 -07:00
José Valim 7607051224 Merge Typespec and Kernel.Typespec 2012-10-30 11:47:57 +01:00
José Valim 4fdec2c780 Only define types in protocols and records if one was not defined yet 2012-10-30 11:42:07 +01:00
José Valim 335ba59712 Merge pull request #599 from yrashk/typespec-to_binary
Add Typespec.to_binary/1
2012-10-30 03:41:59 -07:00
Yurii Rashkovskii 84e04b6a8b Add Typespec.to_binary/1 2012-10-30 03:37:44 -07:00
José Valim 06d1b47cc2 Support @opaque 2012-10-30 10:56:53 +01:00
José Valim c21c00d42f Add Kernel.Typespec.defines_type? 2012-10-30 10:48:33 +01:00
José Valim 097a427cb7 Ensure defoverridable functions can be referred in many clauses, closes #595 2012-10-30 10:37:10 +01:00
José Valim 8c317e3d43 Do not check clauses just after non quoted ones, closes #591 2012-10-30 10:13:32 +01:00
José Valim f7490bd5af Provide faster, with docs and debug info first compilation 2012-10-30 10:01:09 +01:00
José Valim 6aec85fae5 Avoid possible race conditions on test suite 2012-10-30 09:17:16 +01:00
José Valim cc22787c65 Do actual matching on = assertions 2012-10-30 09:17:16 +01:00
José Valim c547d70c5f Merge pull request #596 from devinus/dict-force-get
Add get! method to dict behaviour
2012-10-30 01:16:49 -07:00
José Valim a35b3f8ac1 Merge pull request #597 from yrashk/typespec-access
Add support for access macro in typespecs
2012-10-30 01:16:18 -07:00
Yurii Rashkovskii 856cd70d86 Add support for access macro in typespecs 2012-10-29 17:42:14 -07:00
Devin Torres beac96cd55 Add get! method to dict behaviour 2012-10-29 17:17:10 -05:00
José Valim edf34edf2a Merge pull request #593 from yrashk/dialyze-release
Run make dialyze only after .release
2012-10-29 02:06:13 -07:00
José Valim f4daf0a39d Merge pull request #592 from yrashk/char_list_string_t-types
Add String.t() and char_list() types, discourage use of string() because...
2012-10-29 02:04:46 -07:00
Yurii Rashkovskii a67df523ce Run make dialyze only after .release 2012-10-29 02:01:38 -07:00
Yurii Rashkovskii 154e73b0d4 Add String.t() and char_list() types, discourage use of string() because it might lead to confusion.
Note, however, that dialyzer will often talk about string() anyway.

Addresses #588
2012-10-29 01:55:30 -07:00
José Valim 18fd04010e Merge pull request #590 from yrashk/typespec-build-fix
Typespec build fix
2012-10-29 00:40:38 -07:00
Yurii Rashkovskii a6d86d9f44 Protocol compilation happens during the internal phase, therefore @callback and @type are not yet available 2012-10-28 20:01:42 -07:00
Yurii Rashkovskii a2b79e4331 Correct dialyzer option use when adding Elixir to PLT 2012-10-28 19:42:19 -07:00
Yurii Rashkovskii 6a9a3b3efe Reorder core modules so that elixir will compile 2012-10-28 19:31:31 -07:00
José Valim 591b95bfb9 Protocols should define a @callback implementation 2012-10-29 01:38:37 +01:00
José Valim 833a153b33 Avoid dispatching to locals from compiled structures 2012-10-29 00:45:32 +01:00
José Valim fdc2657af8 Deprecate Behaviour.defcallback/1 in favor of the typespecs wrapper, closes #520 2012-10-29 00:04:43 +01:00
José Valim f2a284e268 Use new callbacks on Mix.SCM 2012-10-28 23:08:07 +01:00
José Valim d75fb11dc4 Disable internal as soon as core is fully compiled
This allows us to compile callbacks for the following
modules, which would trigger missing behaviour warnings
otherwise.
2012-10-28 22:33:13 +01:00
José Valim 6edab89372 Add Module.to_binary 2012-10-28 21:56:50 +01:00
José Valim 078da93886 Use new callbacks in URI.Parser 2012-10-28 21:48:53 +01:00
José Valim ee380fe79c Add support to new style behaviours 2012-10-28 21:41:53 +01:00
José Valim ce26312803 Define a type t for records and protocols 2012-10-28 21:29:47 +01:00
José Valim 7c78f22a95 Allow to define a callback from a spec 2012-10-28 21:20:11 +01:00
José Valim 925ab81ca2 Define __info__(:module) instead of __info__(:self) 2012-10-28 21:20:11 +01:00
José Valim 4301d14555 Merge pull request #586 from yrashk/dialyzer-makefile
Add make dialyze target
2012-10-28 02:26:08 -07:00
Yurii Rashkovskii 523170228a Add make dialyze target 2012-10-27 17:02:38 -07:00
José Valim 2b9d67dd9d More dialyzer fixes 2012-10-28 01:36:38 +02:00
José Valim 7eae5a5e8e Avoid negation in unless, simply invert the clauses 2012-10-28 01:15:54 +02:00
José Valim 87391a5832 Implement more returns boolean checks 2012-10-28 01:06:00 +02:00
José Valim b9dbd56f3a Do not take :__exception__ for granted as a record 2012-10-28 00:26:21 +02:00
José Valim 215a28ceed Avoid generating extra clauses for protocols 2012-10-27 23:52:40 +02:00
José Valim f95529b7b3 Many dialyzer fixes 2012-10-27 22:20:15 +02:00
José Valim 21d2fec7aa Filter no auto import list 2012-10-27 21:45:18 +02:00
José Valim 6afb2684c6 Merge pull request #584 from yrashk/kernel-typespec-fix
Typespec fix in Kernel
2012-10-27 12:44:26 -07:00
Yurii Rashkovskii 93f426fcff Typespec fix in Kernel 2012-10-27 12:31:49 -07:00
José Valim 94b4831709 Revert "Work around a dialyzer bug"
The dialyzer bug was caused because orelse clauses was generated
from inside out. For example, the following clause:

    var in [:a, :b, :c]

Was generating:

    ((var == :a orelse var == :b) orelse var == :c)

Instead of:

    var == :a orelse var == :b orelse var == :c

While it isn't clear why the first one caused the dialyzer to
hang, the `in` operator was fixed to output the second in the
previous commit.

This reverts commit 3c15bcc36f.
2012-10-27 21:01:50 +02:00
José Valim b497632540 Invert the order arguments are handled on in 2012-10-27 20:55:51 +02:00
José Valim d1cbefdbe0 No need to special case the compiler_clause anymore 2012-10-27 20:54:37 +02:00
José Valim 2704ba1a07 Convert file to char list, closes #583 2012-10-27 19:57:04 +02:00
José Valim 3c15bcc36f Work around a dialyzer bug 2012-10-27 12:34:33 +02:00
José Valim 40cda34a23 Direct to Binary.Inspect 2012-10-27 12:18:44 +02:00
José Valim 95bea1a4c7 Fix delegations in Process.ex 2012-10-27 11:19:07 +02:00
José Valim 85fce7aaef Fix delegations in Node.ex (ht @yrashk) 2012-10-27 11:15:56 +02:00
José Valim fc76de175c Also add debug_info to Erlang source 2012-10-27 11:04:15 +02:00
José Valim bae135c0ef Use inspect to convert atoms 2012-10-27 10:53:03 +02:00
José Valim e653dece8d Merge pull request #579 from yrashk/macro-to-binary-call-atom
Macro.to_binary should correctly process expressions involving regular a...
2012-10-27 01:47:57 -07:00
José Valim 71368b6590 Compile with --debug-info by default, closes #580 2012-10-27 10:43:23 +02:00
Yurii Rashkovskii 50960e0e88 Macro.to_binary should correctly process expressions involving regular atom module names (e.g. :lists.seq(1,100)) 2012-10-26 15:15:06 -07:00
José Valim 03bb0831e5 Support do blocks on __scope__ special form 2012-10-26 20:37:25 +02:00
José Valim 194647ef5a Only unquote() if unquote is permitted 2012-10-26 18:54:51 +02:00
José Valim 1514ffbabc Refactor the Makefile and recompile kernel by default 2012-10-26 18:32:46 +02:00
José Valim c3da51f32c Use String.printable? instead of Enum.all? on tests
When we first run the tests, docs are not compiled
and therefore Enum.all?/1 and Enum.all?/2 are shown
in the autocompletion results.

We chose String.printable? because it also ends
with a question mark but has only one arity.

Closes #578
2012-10-26 18:12:24 +02:00
José Valim 91b43f244c Also autocomplete when / was already typed in 2012-10-25 14:32:37 +02:00
José Valim fa10442606 Show available arities when function name is complete in autocomplete 2012-10-25 14:08:38 +02:00
José Valim 35fd0bf896 Show docs for default generated functions 2012-10-25 13:36:08 +02:00
José Valim 7a8fa34020 Only show documented functions on autocomplete
Autocompletion is used for discovery by other developers
and we should avoid exposing non documented functions.
2012-10-25 13:36:08 +02:00
José Valim 9a949702f1 Merge pull request #575 from yrashk/patch-3
Minor documentation fix for defdelegate
2012-10-24 14:32:58 -07:00
Yurii Rashkovskii e8ea3e834c Minor documentation fix for defdelegate 2012-10-24 14:15:37 -07:00
José Valim 2f3289aaef Try a different and simpler approach to clauses warnings, closes #567 2012-10-24 16:57:23 +02:00
José Valim 2de1102544 Merge pull request #573 from yrashk/string-at-fix
Out-of-bounds String.at should return nil
2012-10-24 03:59:41 -07:00
Yurii Rashkovskii 445398b7bc Out-of-bounds String.at should return nil 2012-10-24 03:53:42 -07:00
José Valim e2e6e5d0d4 Store overridable funtions defined in before_compile callbacks, closes #572 2012-10-24 11:55:12 +02:00
José Valim d33eff2aec Improve docs for __aliases__ 2012-10-24 11:35:45 +02:00
José Valim 9a2ae1bac7 Rename String.Unicode.sequences to graphemes 2012-10-24 11:20:45 +02:00
José Valim 31e1965ea2 Merge pull request #566 from yrashk/unicode-sequences
Add support for named sequences and make String module aware of it so it...
2012-10-24 01:57:22 -07:00
José Valim dc169dc783 Fix an issue where variables inside clauses remained unassigned 2012-10-23 11:48:34 +02:00
José Valim a617628848 Atoms true and false are also boolean returns 2012-10-23 11:48:34 +02:00
José Valim 431c8739cc Merge pull request #569 from yrashk/patch-2
README: List all applications provided
2012-10-22 01:43:54 -07:00
Yurii Rashkovskii 96c84370e3 README: List all applications provided 2012-10-22 01:32:36 -07:00
Yurii Rashkovskii 4717c9a10b Add support for named sequences and make String module aware of it so it does correct job at calculating length, picking characters, etc. 2012-10-22 01:10:49 -07:00
José Valim 78230df154 Update Makefile 2012-10-22 01:41:07 +03:00
José Valim b1e130cace Merge pull request #563 from yrashk/unicode-makefile
Minor improvements to the unicode compilation step
2012-10-21 15:40:16 -07:00
Yurii Rashkovskii 354be2383d Minor improvements to the unicode compilation step 2012-10-21 15:17:47 -07:00
José Valim 3b63ce2ab1 Merge pull request #562 from yrashk/unicode-version
Add String.Unicode.version/0
2012-10-21 14:55:27 -07:00
José Valim d220618f12 Compare all known compilation types 2012-10-21 23:53:28 +02:00
Yurii Rashkovskii 5c4a5db828 Add String.Unicode.version/0 2012-10-21 14:52:15 -07:00
José Valim 146dddf0b5 Use bin/elixirc (fix the build) 2012-10-21 23:37:43 +02:00
José Valim b2c8669c4c Support String.downcase and String.upcase according to Unicode 6.2.0 2012-10-21 23:35:03 +02:00
José Valim 318454c092 Optimize range match when values are known at compile time 2012-10-21 20:56:47 +02:00
José + Yehuda 0aa4c7227e Rename Mix.SCM behaviour functions 2012-10-21 20:56:17 +02:00
José Valim a17140ea50 Remove unused function 2012-10-21 19:44:13 +02:00
José + Yehuda e03bf4d7d8 Initial work on Calendar.format (based on ICU) 2012-10-21 19:43:11 +02:00
José + Yehuda 85a9d70544 Improvements to Calendar module 2012-10-21 19:00:16 +02:00
José Valim 1cfdb00c4d Improvements to SCM behaviour specs 2012-10-21 18:08:18 +02:00
José Valim b94def32d2 Add package.exs, update RELEASE file 2012-10-20 22:25:19 +02:00
José Valim f438630e82 Remove deprecated branches 2012-10-20 22:06:03 +02:00
José Valim 293044c168 Give more context to h() 2012-10-20 20:24:15 +02:00
José Valim e975336d5d Bump to 0.7.1.dev 2012-10-20 20:18:19 +02:00
José Valim 43b8a26b36 Revert "Remove Calendar from the upcoming release"
This reverts commit 411f1ce55d.
2012-10-20 20:10:59 +02:00
José Valim 26d35eead1 Use Keyword and check for atom key 2012-10-20 20:09:42 +02:00
144 changed files with 28485 additions and 1798 deletions
+4 -2
View File
@@ -1,5 +1,5 @@
/.eunit/*
/.full
/.release
/lib/*/ebin/*
/lib/*/tmp
/lib/*/test/tmp
@@ -10,4 +10,6 @@
/deps/*
/ebin
/rel/elixir
erl_crash.dump
erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
+1 -1
View File
@@ -1,5 +1,5 @@
language: erlang
script: "make compile && make .full test"
script: "make compile && make .release test"
notifications:
irc: "irc.freenode.org#elixir-lang"
recipients:
+31
View File
@@ -1,3 +1,34 @@
# v0.7.1 (2012-11-18)
* 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`
* [ExUnit] Add support for custom formatters via a well-defined behaviour
* [Kernel] Add support for `defrecordp`
* [Kernel] Improved dialyzer support
* [Kernel] Improved error messages when creating functions with aliases names
* [Mix] Improve SCM behaviour to allow more robust integration
* [Mix] Changing deps information on `mix.exs` forces users to fetch new dependencies
* [Mix] Support (parallel) requires on mix run
* [Mix] Support `-q` when running tests to compile only changed files
* [String] Support `String.downcase` and `String.upcase` according to Unicode 6.2.0
* [String] Add support for graphemes in `String.length`, `String.at` and others
* [Typespec] Support `@opaque` as attribute
* [Typespec] Define a default type `t` for protocols and records
* [Typespec] Add support for the access protocol in typespecs
* bug fix
* [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
* [Behaviour] `defcallback/1` was deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
* [Enum] `Enum.times` was deprecated in favor of using ranges
* [System] `halt` was moved to System module
# v0.7.0 (2012-10-20)
* enhancements
+51 -27
View File
@@ -1,21 +1,22 @@
REBAR:=$(shell echo `pwd`/rebar)
ELIXIRC:=bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ELIXIRC:=bin/elixirc --debug-info --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC:=erlc -I lib/elixir/include
ERL:=erl -I lib/elixir/include -noshell -env ERL_LIBS $ERL_LIBS:lib
FULLFLAG:=.full
VERSION:=0.7.0
VERSION:=0.7.1
RELEASE_FLAG:=.release
.PHONY: 1
.NOTPARALLEL: compile
#==> Templates
define TASK_TEMPLATE
define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
@ cd lib/$(1) && ../../bin/elixir ../../bin/mix compile.app
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex) $$(FORCE)
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
@@ -25,7 +26,11 @@ test_$(1): $(1)
endef
#==> Compilation tasks
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
UNICODE:=lib/elixir/ebin/Elixir-String-Unicode.beam
default: compile
compile: lib/elixir/src/elixir.app.src erlang elixir
@@ -36,46 +41,52 @@ lib/elixir/src/elixir.app.src: src/elixir.app.src
erlang:
@ cd lib/elixir && $(REBAR) compile
# We need to compile only EEx (without the app)
# file so we can compile Mix
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
# file, then mix and then compile eex fully
elixir: kernel unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
kernel: $(KERNEL)
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
@ if [ -f $(KERNEL) ]; then \
echo "==> kernel (compile)"; \
$(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin; \
else \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
@ if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@ echo "==> kernel (compile)";
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
@ rm -rf lib/elixir/ebin/elixir.app
@ cd lib/elixir && $(REBAR) compile
$(eval $(call TASK_TEMPLATE,ex_unit,ExUnit))
$(eval $(call TASK_TEMPLATE,eex,EEx))
$(eval $(call TASK_TEMPLATE,mix,Mix))
$(eval $(call TASK_TEMPLATE,iex,IEx))
unicode: $(UNICODE)
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
@ $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
$(eval $(call APP_TEMPLATE,mix,Mix))
$(eval $(call APP_TEMPLATE,iex,IEx))
clean:
@ cd lib/elixir && $(REBAR) clean
rm -rf .full
rm -rf $(RELEASE_FLAG)
rm -rf ebin
rm -rf lib/*/ebin
rm -rf lib/*/test/tmp
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/mix/tmp
#==> Release tasks
$(FULLFLAG): $(wildcard lib/*/ebin/*)
make ELIXIRC_OPTS="--debug-info" FORCE=1
touch $(FULLFLAG)
#==> Release tasks (modules compiled with --debug-info and --docs)
zip: $(FULLFLAG)
$(RELEASE_FLAG): compile
touch $(RELEASE_FLAG)
zip: $(RELEASE_FLAG)
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
docs: $(FULLFLAG)
docs: $(RELEASE_FLAG)
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
@@ -87,11 +98,12 @@ release_docs: docs
rm -rf ../elixir-lang.github.com/docs/master
mv output ../elixir-lang.github.com/docs/master
release_erl: $(FULLFLAG)
release_erl: $(RELEASE_FLAG)
@ rm -rf rel/elixir
@ cd rel && ../rebar generate
#==> Tests tasks
test: test_erlang test_elixir
test_erlang: compile
@@ -106,3 +118,15 @@ test_elixir: test_kernel test_mix test_ex_unit test_eex test_iex
test_kernel: compile
@ echo "==> kernel (exunit)"
@ cd lib/elixir && time ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
.dialyzer.base_plt:
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
@ dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler syntax_tools inets crypto ssl
dialyze: $(RELEASE_FLAG) .dialyzer.base_plt
@ rm -f .dialyzer_plt
@ cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
@ dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
@ echo "==> Dialyzing Elixir..."
@ dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
+4
View File
@@ -30,6 +30,10 @@ If you want to contribute, Elixir code is divided in applications inside the `li
* `ex_unit` - Simple test framework that ships with Elixir;
* `iex` — IEx, Elixir's interactive shell
* `mix` — Elixir's build tool
We usually keep a list of features and bugs [in the issue tracker][2].
# Important links
+7 -4
View File
@@ -4,19 +4,21 @@ This document simply outlines the release process:
1) Remove .dev extension from current versions
2) Run `make clean test` to ensure all tests pass from scratch
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
4) Commit changes above and update stable branch
5) Create tag from stable branch
5) Create tag from master branch
6) Release new docs, update elixir-lang.org
7) Push new zip to Elixir's downloads page
8) After release, bump versions and add .dev back
8) Push package to expm
9) After release, bump versions and add .dev back
## Places where version is mentioned
@@ -24,4 +26,5 @@ This document simply outlines the release process:
* lib/elixir/lib/system.ex
* rel/reltool.config
* Makefile
* CHANGELOG
* CHANGELOG
* package.exs
+8 -9
View File
@@ -14,23 +14,22 @@
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
name_args=false, %% when true, it means arguments should be named
macro=[], %% a stack with macros nesting
module=nil, %% the current module
function=nil, %% the current function
recur=nil, %% the current loop function to be recurred
vars=[], %% a dict of defined variables and their alias
temp_vars=[], %% a dict of all variables defined in a particular assign
clause_vars=[], %% a dict of all variables defined in a particular clause
quote_vars=[], %% a dict of all quoted variables
clause_vars=nil, %% a dict of all variables defined in a particular clause
extra_guards=nil, %% extra guards from args expansion
counter=0, %% a counter for the variables defined
file=(<<"nofile">>), %% the current scope filename
counter=[], %% a counter for the variables defined
local=nil, %% the scope to evaluate local functions against
aliases=[], %% an orddict with aliases by new -> old names
requires=elixir_dispatch:default_requires(), %% a set with modules required
macros=elixir_dispatch:default_macros(), %% a list with macros imported by module
functions=elixir_dispatch:default_functions(), %% a list with functions imported by module
scheduled=[]}). %% scheduled modules to be loaded
scheduled=[], %% scheduled modules to be loaded
file, %% the current scope filename
aliases, %% an orddict with aliases by new -> old names
requires, %% a set with modules required
macros, %% a list with macros imported by module
functions}). %% a list with functions imported by module
-record(elixir_quote, {
line=0,
+3 -4
View File
@@ -5,11 +5,10 @@ defprotocol Access do
The Access protocol is the underlying protocol invoked
when the brackets syntax is used. For instance, `foo[bar]`
is translated to `access foo, bar` which, by default,
invokes `Access.access` protocol.
invokes the `Access.access` protocol.
This protocol is limited and is implemented only for the
following built-in types: keywords, records, atoms and
functions.
following built-in types: keywords, records and functions.
"""
@only [List, Function, Record, Atom]
@@ -17,7 +16,7 @@ defprotocol Access do
@doc """
Receives the element being accessed and the access item.
"""
def access(element, qualifier)
def access(container, key)
end
defimpl Access, for: List do
+62 -14
View File
@@ -12,13 +12,13 @@ defmodule Behaviour do
use Behaviour
@doc "Parses the given URL"
defcallback parse(arg)
defcallback parse(uri_info :: URI.Info.t), do: URI.Info.t
@doc "Defines a default port"
defcallback default_port()
defcallback default_port(), do: integer
end
And then a specific protocol may use it as:
And then a specific module may use it as:
defmodule URI.HTTP do
@behaviour URI.Parser
@@ -32,20 +32,56 @@ defmodule Behaviour do
## Implementation
Internally, Erlang call `behaviour_info(:callbacks)`
to obtain all functions that a behaviour should
implemented. Therefore, all this module does is
to define `behaviour_info(:callbacks)` with the
`defcallback` definitions.
Behaviours since Erlang R15 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.
The callbacks and their documentation can be retrieved
via the `__behaviour__` callback function.
"""
@doc """
Annotates the given function is a callback. `defcallback` is
slightly different than simple using `def` because, even if
`defcallback` contains default values, a default function
won't be generated, which would happen with `def`.
Defines a callback according to the given type specification.
"""
defmacro defcallback(fun, do: return) do
{ name, args } = :elixir_clauses.extract_args(fun)
arity = length(args)
Enum.each args, (function do
{ :::, _, [left, right] } ->
ensure_not_default(left)
ensure_not_default(right)
left
other ->
ensure_not_default(other)
other
end)
quote do
@callback unquote(name)(unquote_splicing(args)), do: unquote(return)
Behaviour.store_docs __MODULE__, unquote(__CALLER__.line), unquote(name), unquote(arity)
end
end
defp ensure_not_default({ ://, _, [_, _] }) do
raise ArgumentError, message: "default arguments // not supported in defcallback"
end
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :__behaviour_docs, { { name, arity }, line, doc }
end
@doc false
defmacro defcallback(fun) do
IO.write "[WARNING] Behaviour.defcallback/1 is deprecated, please use defcallback/2 instead\n#{Exception.formatted_stacktrace}"
{ name, args } = :elixir_clauses.extract_args(fun)
length = length(args)
@@ -72,6 +108,7 @@ defmodule Behaviour do
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :__behaviour_callbacks, accumulate: true)
Module.register_attribute(__MODULE__, :__behaviour_docs, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
end
@@ -80,9 +117,20 @@ defmodule Behaviour do
@doc false
defmacro __before_compile__(_) do
quote do
if @__behaviour_callbacks != [] do
@doc false
def behaviour_info(:callbacks) do
@__behaviour_callbacks
end
end
@doc false
def behaviour_info(:callbacks) do
@__behaviour_callbacks
def __behaviour__(:callbacks) do
__MODULE__.behaviour_info(:callbacks)
end
def __behaviour__(:docs) do
@__behaviour_docs
end
end
end
+7
View File
@@ -38,6 +38,13 @@ defmodule Binary.Dict do
end
end
def get!(dict(data), key) do
case :orddict.find(to_binary(key), data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
def put(dict(data), key, value) do
dict(:orddict.store to_binary(key), value, data)
end
+14 -26
View File
@@ -128,8 +128,7 @@ defimpl Binary.Inspect, for: Atom do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
"Elixir-" <> rest = binary
bc <<r>> inbits rest, do: <<to_dot(r)>>
Module.to_binary(atom)
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
@@ -139,9 +138,6 @@ defimpl Binary.Inspect, for: Atom do
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
defp to_dot(?-), do: ?.
defp to_dot(l), do: l
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
@@ -265,20 +261,13 @@ defimpl Binary.Inspect, for: List do
cond do
printable?(thing) ->
escape(list_to_binary(thing), ?')
keywords?(thing) ->
Keyword.keyword?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
container_join(thing, "[", "]", opts)
end
end
## keywords?
defp keywords?([]), do: true
defp keywords?([{ key, _value } | rest]) when is_atom(key) do
keywords?(rest)
end
defp keywords?(_other), do: false
defp join_keywords(thing, opts) do
Enum.join(lc {key, value} inlist thing do
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
@@ -334,9 +323,7 @@ defimpl Binary.Inspect, for: Tuple do
name = elem(tuple, 0)
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
if name in [BitString, List, Tuple, Atom, Number, Any] do
record_inspect(tuple, opts)
else
unless name in [BitString, List, Tuple, Atom, Number, Any] do
try do
target = Module.concat(Binary.Inspect, name)
target.inspect(tuple, opts)
@@ -348,19 +335,16 @@ defimpl Binary.Inspect, for: Tuple do
end
end
defp record_inspect(exception, opts) when is_exception(exception) do
list = tuple_to_list(exception)
[name,_|tail] = list
[_|fields] = name.__record__(:fields)
record_join(name, fields, tail, opts)
end
defp record_inspect(record, opts) do
list = tuple_to_list(record)
[name|tail] = list
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
record_join(name, fields, tail, opts)
if Enum.first(tail) == :__exception__ do
record_join(name, tl(fields), tl(tail), opts)
else
record_join(name, fields, tail, opts)
end
end
end
@@ -422,8 +406,12 @@ defimpl Binary.Inspect, for: Regex do
"""
def inspect(thing, _) do
"%r" <> Binary.Inspect.inspect(Regex.source(thing), []) <> Regex.opts(thing)
def inspect(regex, _opts) when size(regex) == 5 do
"%r" <> Binary.Inspect.inspect(Regex.source(regex), []) <> Regex.opts(regex)
end
def inspect(other, opts) do
Binary.Inspect.inspect other, Keyword.put(opts, :raw, true)
end
end
+28 -28
View File
@@ -52,28 +52,41 @@ defmodule Code do
end
@doc """
Evalutes the contents given by string. The second argument is the binding
(which should be a Keyword) followed by a keyword list of options. The
options can be:
Evalutes 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:
* `:file` - the file to be considered in the evaluation
* `:line` - the line the script starts
* `:aliases` - a list of tuples with the alias and its target
* `: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
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity
* `:delegate_locals_to` - delegate local calls to the given module,
otherwise functions are evaluated inside Erlang's default scope.
the default is to not delegate
## Examples
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
When passing the __ENV__'s file and line, we could simply get
the location which already returns both fields as a keyword list:
For convenience, you can my pass `__ENV__` as argument and
all imports, requires and aliases will be automatically carried
over:
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
Code.eval "a + b", [a: 1, b: 2], __ENV__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval(string, binding // [], opts // []) do
def eval(string, binding // [], opts // [])
def eval(string, binding, Macro.Env[] = env) do
eval(string, binding, env.to_keywords)
end
def eval(string, binding, opts) do
{ value, binding, _scope } =
:elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding }
@@ -82,15 +95,8 @@ defmodule Code do
@doc """
Evalutes the quoted contents.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
This function accepts a list of environment options.
Check `Code.eval` for more information.
## Examples
@@ -99,8 +105,8 @@ defmodule Code do
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
For convenience, you can also pass the current __ENV__ and
the proper information will be extracted:
For convenience, you can my pass `__ENV__` as argument and
all options will be automatically extracted from the environment:
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
#=> { 3, [ {:a, 1}, {:b, 2} ] }
@@ -109,7 +115,7 @@ defmodule Code do
def eval_quoted(quoted, binding // [], opts // [])
def eval_quoted(quoted, binding, Macro.Env[] = env) do
eval_quoted(quoted, binding, env.location)
eval_quoted(quoted, binding, env.to_keywords)
end
def eval_quoted(quoted, binding, opts) do
@@ -319,7 +325,7 @@ defmodule Code do
:undefined -> error
_ ->
try do
module.__info__(:self)
module.__info__(:module)
{ :module, module }
rescue
UndefinedFunctionError -> error
@@ -353,13 +359,7 @@ defmodule Code do
if File.regular?(file) do
file
else
prefix = "#{file}.exs"
if File.regular?(prefix) do
IO.write "[WARNING] Passing a file without .exs extension to Code.load_file or Code.require_file is deprecated, please pass the full name instead\n#{Exception.formatted_stacktrace}"
prefix
else
raise ArgumentError, message: "could not load #{file}"
end
raise ArgumentError, message: "could not load #{file}"
end
end
+47 -6
View File
@@ -26,12 +26,23 @@ defmodule Dict do
"""
@doc false
def behaviour_info(:callbacks) do
[ delete: 2, empty: 1, get: 3, has_key?: 2,
keys: 1, merge: 3, put: 3, size: 1, to_list: 1,
update: 3, update: 4, values: 1 ]
end
use Behaviour
@type key :: any
@type value :: any
@type t :: tuple
defcallback delete(t, key), do: t
defcallback empty(t), do: t
defcallback get(t, key, value), do: value
defcallback has_key?(t, key), do: boolean
defcallback keys(t), do: list(key)
defcallback merge(t, t, fun(key, value, value, do: value)), do: t
defcallback put(t, key, value), do: t
defcallback size(t), do: non_neg_integer()
defcallback to_list(t), do: list()
defcallback update(t, key, fun(value, do: value)), do: t
defcallback values(t), do: list(value)
@doc """
Returns a list containing all dict's keys.
@@ -44,6 +55,7 @@ defmodule Dict do
Dict.keys d #=> [:a,:b]
"""
@spec keys(t), do: [key]
def keys(dict) do
elem(dict, 0).keys(dict)
end
@@ -57,6 +69,7 @@ defmodule Dict do
Dict.values d #=> [1,2]
"""
@spec values(t), do: [value]
def values(dict) do
elem(dict, 0).values(dict)
end
@@ -70,6 +83,7 @@ defmodule Dict do
Dict.size d #=> 2
"""
@spec size(t), do: non_neg_integer
def size(dict) do
elem(dict, 0).size(dict)
end
@@ -84,6 +98,7 @@ defmodule Dict do
Dict.has_key?(d, :b) #=> false
"""
@spec has_key?(t, key), do: boolean
def has_key?(dict, key) do
elem(dict, 0).has_key?(dict, key)
end
@@ -100,10 +115,28 @@ defmodule Dict do
Dict.get d, :b, 3 #=> 3
"""
@spec get(t, key), do: value | nil
@spec get(t, key, value), do: value
def get(dict, key, default // nil) do
elem(dict, 0).get(dict, key, default)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, it raises `KeyError`.
## Examples
d = new [a: 1]
Dict.get d, :a #=> 1
Dict.get d, :b #=> raises KeyError[key: :b]
"""
@spec get!(t, key), do: value | no_return
def get!(dict, key) do
elem(dict, 0).get!(dict, key)
end
@doc """
Stores the given `value` under `key` in `dict`.
If `dict` already has `key`, the stored value is replaced by the new one.
@@ -115,6 +148,7 @@ defmodule Dict do
#=> [a: 3, b: 2]
"""
@spec put(t, key, value), do: t
def put(dict, key, val) do
elem(dict, 0).put(dict, key, val)
end
@@ -132,6 +166,7 @@ defmodule Dict do
Dict.delete d, :a #=> [b: 2]
"""
@spec delete(t, key), do: t
def delete(dict, key) do
elem(dict, 0).delete(dict, key)
end
@@ -150,6 +185,7 @@ defmodule Dict do
#=> [a: 3, b: 2, d: 4]
"""
@spec merge(t, t), do: t
def merge(dict1, dict2) do
merge(dict1, dict2, fn(_k, _v1, v2) -> v2 end)
end
@@ -168,6 +204,7 @@ defmodule Dict do
#=> [a: 4, b: 2, d: 4]
"""
@spec merge(t, t, fun(key, value, value, do: value)), do: t
def merge(dict1, dict2, fun) do
elem(dict1, 0).merge(dict1, dict2, fun)
end
@@ -183,6 +220,7 @@ defmodule Dict do
#=> [a: -1, b: 2]
"""
@spec update(t, key, fun(value, do: value)), do: t
def update(dict, key, fun) do
elem(dict, 0).update(dict, key, fun)
end
@@ -199,6 +237,7 @@ defmodule Dict do
#=> [a: 1, b: 2, c: 3]
"""
@spec update(t, key, value, fun(value, do: value)), do: t
def update(dict, key, initial, fun) do
elem(dict, 0).update(dict, key, initial, fun)
end
@@ -206,6 +245,7 @@ defmodule Dict do
@doc """
Returns an empty dict of the same type as `dict`.
"""
@spec empty(t), do: t
def empty(dict) do
elem(dict, 0).empty(dict)
end
@@ -214,6 +254,7 @@ defmodule Dict do
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
"""
@spec to_list(t), do: list
def to_list(dict) do
elem(dict, 0).to_list(dict)
end
+48 -34
View File
@@ -68,9 +68,12 @@ defmodule Enum do
`{ key, value }` tuple.
"""
@type t :: Enum.Iterator.t
@type element :: any
@doc """
Invokes the given `fun` for each item in the `collection` and returns true if
each invocation returns true as well, otherwise it shirt-circuits and returns
each invocation returns true as well, otherwise it short-circuits and returns
false.
## Examples
@@ -88,6 +91,9 @@ defmodule Enum do
Enum.all? [1,nil,3] #=> false
"""
@spec all?(t), do: boolean
@spec all?(t, fun(element, do: boolean)), do: boolean
def all?(collection, fun // fn(x) -> x end)
def all?(collection, fun) when is_list(collection) do
@@ -122,6 +128,9 @@ defmodule Enum do
Enum.any? [false,true,false] #=> true
"""
@spec any?(t), do: boolean
@spec any?(t, fun(element, do: boolean)), do: boolean
def any?(collection, fun // fn(x) -> x end)
def any?(collection, fun) when is_list(collection) do
@@ -151,6 +160,7 @@ defmodule Enum do
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
"""
@spec at!(t, non_neg_integer), do: element | no_return
def at!(collection, n) when is_list(collection) and n >= 0 do
do_at!(collection, n)
end
@@ -172,6 +182,7 @@ defmodule Enum do
Enum.count [1,2,3] #=> 3
"""
@spec count(t), do: non_neg_integer
def count(collection) do
I.count(collection)
end
@@ -179,6 +190,7 @@ defmodule Enum do
@doc """
Counts for how many items the function returns true.
"""
@spec count(t, fun(element, do: boolean)), do: non_neg_integer
def count(collection, fun) when is_list(collection) do
do_count(collection, fun)
end
@@ -203,6 +215,7 @@ defmodule Enum do
Enum.drop [1,2,3], 0 #=> [1,2,3]
"""
@spec drop(t, non_neg_integer), do: t
def drop(collection, count) do
elem split(collection, count), 1
end
@@ -216,6 +229,7 @@ defmodule Enum do
Enum.drop_while [1,2,3,4,5], fn(x) -> x < 3 end
#=> [3,4,5]
"""
@spec drop_while(t, fun(element, do: boolean)), do: t
def drop_while(collection, fun) when is_list(collection) do
do_drop_while(collection, fun)
end
@@ -238,6 +252,7 @@ defmodule Enum do
Enum.each ['some', 'example'], fn(x) -> IO.puts x end
"""
@spec each(t, fun(element, do: any)), do: :ok
def each(collection, fun) when is_list(collection) do
:lists.foreach(fun, collection)
:ok
@@ -262,6 +277,7 @@ defmodule Enum do
Enum.empty? [1,2,3] #=> false
"""
@spec empty?(t), do: boolean
def empty?(collection) when is_list(collection) do
collection == []
end
@@ -283,6 +299,7 @@ defmodule Enum do
#=> [2]
"""
@spec filter(t, fun(element, do: boolean)), do: t
def filter(collection, fun) when is_list(collection) do
lc item inlist collection, fun.(item), do: item
end
@@ -305,6 +322,7 @@ defmodule Enum do
#=> [4]
"""
@spec filter_map(t, fun(element, do: boolean), fun(element, do: element)), do: t
def filter_map(collection, filter, mapper) when is_list(collection) do
lc item inlist collection, filter.(item), do: mapper.(item)
end
@@ -334,6 +352,9 @@ defmodule Enum do
#=> 3
"""
@spec find(t, fun(element, do: any)), do: element | :nil
@spec find(t, any, fun(element, do: any)), do: element | :nil
def find(collection, ifnone // nil, fun)
def find(collection, ifnone, fun) when is_list(collection) do
@@ -362,6 +383,9 @@ defmodule Enum do
#=> true
"""
@spec find_value(t, fun(element, do: any)), do: any | :nil
@spec find_value(t, any, fun(element, do: any)), do: any | :nil
def find_value(collection, ifnone // nil, fun)
def find_value(collection, ifnone, fun) when is_list(collection) do
@@ -392,6 +416,7 @@ defmodule Enum do
#=> 2
"""
@spec find_index(t, fun(element, do: any)), do: non_neg_integer | :nil
def find_index(collection, fun) when is_list(collection) do
do_find_index(collection, 0, fun)
end
@@ -414,6 +439,7 @@ defmodule Enum do
Enum.first [1,2,3] #=> 1
"""
@spec first(t), do: :nil | element
def first([]), do: nil
def first([h|_]), do: h
@@ -442,6 +468,8 @@ defmodule Enum do
Enum.join([1,2,3], ' = ') #=> '1 = 2 = 3'
"""
@spec join(t), do: String.t
@spec join(t, String.t | char_list), do: String.t | char_list
def join(collection, joiner // "")
def join(collection, joiner) when is_list(joiner) do
@@ -475,6 +503,7 @@ defmodule Enum do
#=> [a: -1, b: -2]
"""
@spec map(t, fun(element, do: any)), do: t
def map(collection, fun) when is_list(collection) do
lc item inlist collection, do: fun.(item)
end
@@ -501,10 +530,12 @@ defmodule Enum do
## Examples
Enum.map_join([1,2,3], &1 * 2) #=> "246"
Enum.map_join([1,2,3], &1 * 2, " = ") #=> "2 = 4 = 6"
Enum.map_join([1,2,3], &1 * 2, ' = ') #=> '2 = 4 = 6'
Enum.map_join([1,2,3], " = ", &1 * 2) #=> "2 = 4 = 6"
Enum.map_join([1,2,3], ' = ', &1 * 2) #=> '2 = 4 = 6'
"""
@spec map_join(t, fun(element, do: any)), do: String.t
@spec map_join(t, String.t | char_list, fun(element, do: any)), do: String.t | char_list
def map_join(collection, joiner // "", mapper)
def map_join(collection, joiner, mapper) when is_list(joiner) do
@@ -539,6 +570,7 @@ defmodule Enum do
#=> { [2, 4, 6], 6 }
"""
@spec map_reduce(t, any, fun(element, any, do: any)), do: any
def map_reduce(collection, acc, f) when is_list(collection) do
:lists.mapfoldl(f, acc, collection)
end
@@ -563,6 +595,7 @@ defmodule Enum do
#=> { [2], [1,3] }
"""
@spec partition(t, fun(element, do: any)), do: {t, t}
def partition(collection, fun) when is_list(collection) do
do_partition(collection, fun, [], [])
end
@@ -587,6 +620,7 @@ defmodule Enum do
#=> 6
"""
@spec reduce(t, any, fun(element, any, do: any)), do: any
def reduce(collection, acc, fun) when is_list(collection) do
:lists.foldl(fun, acc, collection)
end
@@ -603,14 +637,13 @@ defmodule Enum do
@doc """
Reverses the collection.
Expects an ordered collection.
## Examples
Enum.reverse [1, 2, 3]
#=> [3, 2, 1]
"""
@spec reverse(t), do: t
def reverse(collection) when is_list(collection) do
:lists.reverse(collection)
end
@@ -630,6 +663,7 @@ defmodule Enum do
Enum.qsort [3,2,1] #=> [1,2,3]
"""
@spec qsort(t), do: t
def qsort(collection) when is_list(collection) do
do_list_qsort(collection, [])
end
@@ -659,6 +693,7 @@ defmodule Enum do
Enum.split [1,2,3], -5 #=> { [], [1,2,3] }
"""
@spec split(t, integer), do: {t, t}
def split(collection, count) when is_list(collection) and count >= 0 do
do_split(collection, count, [])
end
@@ -679,12 +714,6 @@ defmodule Enum do
split(list, real_count)
end
@doc false
def split_with(collection, fun) do
IO.write "[WARNING] Enum.split_with is deprecated, please use split_while instead\n#{Exception.formatted_stacktrace}"
split_while(collection, fun)
end
@doc """
Splits `collection` at the first element, for which `fun` returns true.
Expects an ordered collection.
@@ -694,6 +723,7 @@ defmodule Enum do
Enum.split_while [1,2,3,4], fn x -> x == 2 end
#=> { [1], [2, 3, 4] }
"""
@spec split_while(t, fun(element, do: boolean)), do: {t, t}
def split_while(collection, fun) when is_list(collection) do
do_split_while(collection, fun, [])
end
@@ -718,6 +748,7 @@ defmodule Enum do
Enum.take [1,2,3], 0 #=> []
"""
@spec take(t, non_neg_integer), do: t
def take(collection, count) do
elem split(collection, count), 0
end
@@ -732,6 +763,7 @@ defmodule Enum do
#=> [1, 2]
"""
@spec take_while(t, fun(element, do: boolean)), do: t
def take_while(collection, fun) when is_list(collection) do
do_take_while(collection, fun)
end
@@ -745,19 +777,9 @@ defmodule Enum do
end
end
@doc """
Iterates the given function n times, passing values
from 0 to n - 1.
## Examples
Enum.times 3, fn(x) -> IO.inspect x end
0
1
2
"""
@doc false
def times(times, function) when times >= 0 do
IO.write "[WARNING] Enum.times/2 is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
case is_function(function, 0) do
true ->
do_times_0(times, 1, function)
@@ -767,18 +789,9 @@ defmodule Enum do
times
end
@doc """
Iterates the given function n times, passing values from 1
to n. Also has an accumulator similar to reduce to store the
value between computations.
## Examples
Enum.times 5, 0, fn(x, acc) -> acc + x end
#=> 15
"""
@doc false
def times(times, acc, function) when times >= 0 do
IO.write "[WARNING] Enum.times/3 is deprecated, please use ranges instead\n#{Exception.formatted_stacktrace}"
do_times_2(times, 1, function, acc)
end
@@ -788,6 +801,7 @@ defmodule Enum do
dictated by the first enum. In case the second list is shorter,
values are filled with nil.
"""
@spec zip(t, t), do: [{any, any}]
def zip(coll1, coll2) when is_list(coll1) do
do_zip(coll1, iterator(coll2))
end
+2 -2
View File
@@ -56,7 +56,7 @@ defmodule Exception do
ErlangError.new original: other
end
# Check the given module is a valid record.
# Check the given module is a valid exception record.
@doc false
def check!(module) do
unless :erlang.function_exported(module, :message, 1) do
@@ -217,7 +217,7 @@ defexception ErlangError, [original: nil] do
end
end
defexception Keyword.KeyError, key: nil do
defexception KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
+26 -16
View File
@@ -59,8 +59,16 @@ defmodule File do
that interact with the filesystem have their naming
based on its UNIX variants. For example, deleting a
file is done with `File.rm`. Getting its stats with
`File.stat`. If you want to read or write to a file
in chunks, check the IO module.
`File.stat`.
In order to write and read files, one must use the
functions in the IO module. By default, a file is
opened on binary mode which requires the functions
`IO.binread`, `IO.binwrite` and `IO.binreadline` 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.
Most of the functions in this module return `:ok`
or `{ :ok, result }` in case of success, `{ :error, reason }`
@@ -974,6 +982,12 @@ defmodule File do
Note that the file size obtained with `stat/1` will most probably not
match the number of bytes that can be read from a compressed file.
* `:utf8` - This option denotes how data is actually stored in the disk file and
makes the file perform automatic translation of characters to and from utf-8.
If data is sent to a file in a format that cannot be converted to the utf-8
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`.
Check `http://www.erlang.org/doc/man/file.html#open-2` for more information about
@@ -1000,7 +1014,7 @@ defmodule File do
def open(path, modes // [])
def open(path, modes) when is_list(modes) do
F.open(path, open_defaults(modes, true, true))
F.open(path, open_defaults(modes, true))
end
def open(path, function) when is_function(function) do
@@ -1166,7 +1180,7 @@ defmodule File do
def iterator(device) do
fn ->
function = fn(_) ->
case :io.get_line(device, "") do
case :io.get_line(device, '') do
:eof ->
close(device)
:stop
@@ -1229,22 +1243,18 @@ defmodule File do
join Enum.reverse(acc)
end
defp open_defaults([:charlist|t], add_encoding, _add_binary) do
open_defaults(t, add_encoding, false)
defp open_defaults([:charlist|t], _add_binary) do
open_defaults(t, false)
end
defp open_defaults([{:encoding, _} = h|t], _add_encoding, add_binary) do
[h|open_defaults(t, false, add_binary)]
defp open_defaults([:utf8|t], add_binary) do
open_defaults([{ :encoding, :utf8 }|t], add_binary)
end
defp open_defaults([h|t], add_encoding, add_binary) do
[h|open_defaults(t, add_encoding, add_binary)]
defp open_defaults([h|t], add_binary) do
[h|open_defaults(t, add_binary)]
end
defp open_defaults([], add_encoding, add_binary) do
options = []
if add_encoding, do: options = [{:encoding, :unicode}|options]
if add_binary, do: options = [:binary|options]
options
end
defp open_defaults([], true), do: [:binary]
defp open_defaults([], false), do: []
end
-11
View File
@@ -1,14 +1,3 @@
defmodule GenServer.Behavior do
@moduledoc false
defmacro __using__(_) do
IO.puts "Using GenServer.Behavior is deprecated, use GenServer.Behaviour instead"
quote do
use GenServer.Behaviour
end
end
end
defmodule GenServer.Behaviour do
@moduledoc """
By using this module, you get default GenServer callbacks
+8
View File
@@ -46,6 +46,14 @@ defmodule HashDict do
end
end
@doc false
def get!(dict(data), key) do
case :dict.find(key, data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
@doc false
def put(dict(data), key, value) do
dict(:dict.store key, value, data)
+38 -2
View File
@@ -3,7 +3,7 @@ defmodule IO do
Module responsible for doing IO. Many functions in this
module expects an IO device and an io data encoded in UTF-8.
An IO device must be a pid is an atom representing a process.
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`.
@@ -39,7 +39,20 @@ defmodule IO do
end
@doc """
Read a line from the IO device. It returns:
Reads `count` bytes from the IO device as binary,
no unicode conversion happens.
Check `read/2` for more information.
"""
def binread(device // :stdio, count) do
case :file.read(map_dev(device), count) do
{ :ok, data } -> data
other -> other
end
end
@doc """
Reads a line from the IO device. It returns:
* `data` - The input characters.
@@ -56,6 +69,19 @@ defmodule IO do
:io.get_line(map_dev(device), "")
end
@doc """
Reads a line from the IO device as binary,
no unicode conversion happens.
Check `readline/1` for more information.
"""
def binreadline(device // :stdio) do
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.
@@ -77,6 +103,16 @@ defmodule IO do
:io.put_chars map_dev(device), to_iodata(item)
end
@doc """
Writes the given argument to the given device
as a binary, no unicode conversion happens.
Check `write/2` for more information.
"""
def binwrite(device // :stdio, item) do
:file.write map_dev(device), to_iodata(item)
end
@doc """
Writes the argument to the device, similarly to write
but adds a new line at the end. The argument is expected
+117 -84
View File
@@ -487,7 +487,7 @@ defmodule Kernel do
number of bits in the binary is not divisible by 8, the last element of the list will
be a bitstring containing the remaining bits (1 up to 7 bits).
"""
@spec bitstring_to_list(bitstring), do: non_neg_integer
@spec bitstring_to_list(bitstring), do: list
def bitstring_to_list(bitstring) do
:erlang.bitstring_to_list(bitstring)
end
@@ -543,59 +543,11 @@ defmodule Kernel do
#=> '7.00000000000000000000e+00'
"""
@spec float_to_list(number), do: string
@spec float_to_list(number), do: char_list
def float_to_list(number) do
:erlang.float_to_list(number)
end
@doc """
The same as halt(0, []).
"""
@spec halt(), do: no_return
def halt() do
:erlang.halt()
end
@doc """
The same as halt(status, []).
"""
@spec halt(non_neg_integer | string | :abort), do: no_return
def halt(status) do
:erlang.halt(status)
end
@doc """
Halts the Erlang runtime system where the first argument status must be a
non-negative integer, a char list, or the atom `:abort`.
* If an integer, the runtime system exits with the integer value which
is returned to the Operating System;
* If a char list, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1;
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system.
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.
## Examples
halt(0)
halt(1, flush: false)
halt(:abort)
"""
@spec halt(non_neg_integer | string | :abort, [] | [flush: false]), do: no_return
def halt(status, options) do
:erlang.halt(status, options)
end
@doc """
Returns the head of a list, raises badarg if the list is empty.
"""
@@ -613,7 +565,7 @@ defmodule Kernel do
#=> '7'
"""
@spec integer_to_list(integer), do: string
@spec integer_to_list(integer), do: char_list
def integer_to_list(number) do
:erlang.integer_to_list(number)
end
@@ -628,7 +580,7 @@ defmodule Kernel do
#=> "3FF"
"""
@spec integer_to_list(integer, pos_integer), do: string
@spec integer_to_list(integer, pos_integer), do: char_list
def integer_to_list(number, base) do
:erlang.integer_to_list(number, base)
end
@@ -840,7 +792,7 @@ defmodule Kernel do
list_to_atom('elixir') #=> :elixir
"""
@spec list_to_atom(string), do: atom
@spec list_to_atom(char_list), do: atom
def list_to_atom(char_list) do
:erlang.list_to_atom(char_list)
end
@@ -880,7 +832,7 @@ defmodule Kernel do
Returns the atom whose text representation is `char_list`, but only if there already
exists such atom.
"""
@spec list_to_existing_atom(string), do: atom
@spec list_to_existing_atom(char_list), do: atom
def list_to_existing_atom(char_list) do
:erlang.list_to_existing_atom(char_list)
end
@@ -892,7 +844,7 @@ defmodule Kernel do
list_to_float('2.2017764e+0') #=> 2.2017764
"""
@spec list_to_float(string), do: float
@spec list_to_float(char_list), do: float
def list_to_float(char_list) do
:erlang.list_to_float(char_list)
end
@@ -904,7 +856,7 @@ defmodule Kernel do
list_to_integer('123') #=> 123
"""
@spec list_to_integer(string), do: integer
@spec list_to_integer(char_list), do: integer
def list_to_integer(char_list) do
:erlang.list_to_integer(char_list)
end
@@ -916,7 +868,7 @@ defmodule Kernel do
list_to_integer('3FF', 16) #=> 1023
"""
@spec list_to_integer(string, non_neg_integer), do: integer
@spec list_to_integer(char_list, non_neg_integer), do: integer
def list_to_integer(char_list, base) do
:erlang.list_to_integer(char_list, base)
end
@@ -934,7 +886,7 @@ defmodule Kernel do
## Examples
list_to_pid('<0.41>') #=> <0.4.1>
"""
@spec list_to_pid(string), do: pid
@spec list_to_pid(char_list), do: pid
def list_to_pid(char_list) do
:erlang.list_to_pid(char_list)
end
@@ -1393,7 +1345,12 @@ defmodule Kernel do
defmacro defp(name, args, guards, do: contents)
@doc %B"""
Define a record given by name and values.
Defines a record.
A record is a tagged tuple which contains one or more elements
and the first element is a module. This macro defines a module
that generates accessors to manipulate the record at both
compilation and runtime.
## Examples
@@ -1402,25 +1359,25 @@ defmodule Kernel do
The line above will define a module named `FileInfo` which
contains a function named `new` that returns a new record
and other functions to read and set the values in the
record. Therefore, we can do:
record:
file_info = FileInfo.new(atime: now())
file_info.atime #=> Returns the value of atime
file_info.atime(now()) #=> Updates the value of atime
Internally, a record is simply a tuple where the first element is
the record module name. This can be noticed if we print the record:
A record is simply a tuple where the first element is the record
module name. We can get the record raw representation as follow:
IO.inspect FileInfo.new
{ FileInfo, nil, nil }
inspect FileInfo.new, raw: true
#=> { FileInfo, nil, nil }
## Extensions
Besides defining readers and writers for each attribute. Elixir will
define extensions functions for each attribute. By default, it will
define an `update_#{attribute}` function to update the value. Such
functions expect a function as argument that receives the current value
and must return the new one:
functions expect a function as argument that receives the current
value and must return the new one:
file_info.update_atime(fn(_old) -> now() end) #=> Updates the value of atime
@@ -1443,20 +1400,79 @@ defmodule Kernel do
Besides, if the default is a list, Elixir will define two helpers:
* `merge_field` - Receives keywords and merge it into the current value;
* `prepend_field` - Receives another list and prepend its values
You can define your own extensions or disable them using the except
option:
defrecord Config, [counter: 0, failures: []], except: [:extensions]
* `prepend_field` - Receives another list and prepend its values;
## Documentation
By default records are not documented and have @moduledoc set to false.
By default records are not documented and have `@moduledoc` set to false.
## Types
Every record defines a type named `t` that can be accessed in typespecs.
For example, assuming the `Config` record defined above, it could be used
in typespecs as follow:
@spec handle_config(Config.t), do: boolean()
Inside the record definition, a developer can define his own types too:
defrecord Config, counter: 0, failures: [] do
@type kind :: term
record_type counter: integer, failures: [kind]
end
When defining a type, all the fields not mentioned in the type are
assumed to have type `term`.
"""
defmacro defrecord(name, fields, opts // [], do_block // []) do
Record.defrecord(name, fields, Keyword.merge(opts, do_block))
end
@doc """
Defines a record with a set of private macros to manipulate it.
A record is a tagged tuple which contains one or more elements
and the first element is a module. This macro defines a set of
macros private to the current module to manipulate the record
exclusively at compilation time.
`defrecordp` must be used instead of `defrecord` when there is
no interest in exposing the record as a whole. In many ways,
it is similar to Erlang records, since it is only available at
compilation time.
## Examples
defmodule User do
defrecordp :user, [name: "José", age: "25"]
end
In the example above, a set of macros named `user` but with different
arities will be defined to manipulate the underlying record:
# To create records
user() #=> { User, "José", 25 }
user(age: 26) #=> { User, "José", 26 }
# To get a field from the record
user(record, :name) #=> "José"
# To get many fields from the record
user(record, [:name, :age]) #=> ["José", 25]
# To update the record
user(record, age: 26) #=> { User, "José", 26 }
# To convert the record to keywords
user(record) #=> [name: "José", age: 25]
# To match against the record
user(name: name) = record
name #=> "José"
"""
defmacro defrecord(name, values, opts // [], do_block // []) do
Record.defrecord(name, values, Keyword.merge(opts, do_block))
defmacro defrecordp(name, fields) when is_atom(name) do
Record.defrecordp(name, fields)
end
@doc """
@@ -1467,21 +1483,21 @@ defmodule Kernel do
differences:
1) Differently from records, exceptions are documented by default;
2) Exceptions **must** implement `message/1` as API and return a
2) Exceptions **must** implement `message/1` as API that return a
binary as result;
"""
defmacro defexception(name, values, opts // [], do_block // []) do
defmacro defexception(name, fields, opts // [], do_block // []) do
opts = Keyword.merge(opts, do_block)
opts = Keyword.put(opts, :do, quote do
@moduledoc nil
unquote(Keyword.get opts, :do)
def exception(args), do: new(args)
def exception(args, self), do: self
def exception(args, self), do: update(args, self)
end)
values = [{ :__exception__, :__exception__ }|values]
record = Record.defrecord(name, values, opts)
fields = [{ :__exception__, :__exception__ }|fields]
record = Record.defrecord(name, fields, opts)
check = quote do
Exception.check! Module.concat(__MODULE__, unquote(name))
@@ -1621,7 +1637,7 @@ defmodule Kernel do
Reference are never going to be blank, it would be easier if we
could simply provide a default implementation.
This can be achieved with Elixir as follows:
This can be achieved in Elixir as follows:
defprotocol Blank do
@only [Atom, Tuple, List, BitString, Any]
@@ -1659,6 +1675,19 @@ defmodule Kernel do
Finally, since records are simply tuples, one can add a default protocol
implementation to any record by defining a default implementation for tuples.
## Types
As in records, defining a protocol automatically defines a type named `t`,
which can be used as:
@spec present?(Blank.t), do: boolean
def present?(blank) do
not Blank.blank?(blank)
end
The `@spec` above expresses that all types allowed to implement the
given protocol are valid argument types for the given function.
"""
defmacro defprotocol(name, [do: block]) do
Protocol.defprotocol(name, [do: block])
@@ -2299,7 +2328,11 @@ defmodule Kernel do
and documentation.
"""
defmacro unless(clause, options) do
quote do: if(!unquote(clause), unquote(options))
do_clause = Keyword.get(options, :do, nil)
else_clause = Keyword.get(options, :else, nil)
quote do
if(unquote(clause), do: unquote(else_clause), else: unquote(do_clause))
end
end
@doc """
@@ -2828,7 +2861,7 @@ defmodule Kernel do
end
end
Record.access(caller, atom, fields, args)
Record.access(atom, fields, args, caller)
false ->
case caller.in_match? do
true -> raise "invalid usage of access protocol in signature"
@@ -2872,7 +2905,7 @@ defmodule Kernel do
defdelegate other_reverse(list), to: :lists, as: :reverse
end
My:lists.reverse([1,2,3])
MyList.reverse([1,2,3])
#=> [3,2,1]
MyList.other_reverse([1,2,3])
+21 -11
View File
@@ -4,8 +4,10 @@ defrecord Kernel.CLI.Config, commands: [], output: ".",
defmodule Kernel.CLI do
@moduledoc false
# Invoked directly from erlang boot process. It parses all argv
# options and execute them in the order they are specified.
@doc """
Invoked directly from erlang boot process. It parses all argv
options and execute them in the order they are specified.
"""
def process_argv(options) do
{ config, argv } = process_options(options, Kernel.CLI.Config.new)
@@ -14,11 +16,19 @@ defmodule Kernel.CLI do
all_commands = Enum.reverse(config.commands)
run fn -> Enum.map all_commands, process_command(&1, config) end, config.halt
end
@doc """
Runs the given function and wraps any exception or
error in messages useful for the command line.
"""
def run(fun, halt // true) do
try do
Enum.map all_commands, process_command(&1, config)
if config.halt do
fun.()
if halt do
at_exit(0)
halt(0)
System.halt(0)
end
rescue
exception ->
@@ -26,20 +36,20 @@ defmodule Kernel.CLI do
trace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts Exception.formatted_stacktrace(trace)
halt(1)
System.halt(1)
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
halt(reason)
System.halt(reason)
:exit, :normal ->
at_exit(0)
halt(0)
System.halt(0)
kind, reason ->
at_exit(1)
trace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
IO.puts Exception.formatted_stacktrace(trace)
halt(1)
System.halt(1)
end
end
@@ -66,7 +76,7 @@ defmodule Kernel.CLI do
defp invalid_option(option) do
IO.puts(:stderr, "Unknown option #{list_to_binary(option)}")
halt(1)
System.halt(1)
end
defp shared_option?(list, config, callback) do
@@ -140,7 +150,7 @@ defmodule Kernel.CLI do
{ config.prepend_commands([require: list_to_binary(exec)]), t }
else
IO.puts(:stderr, "Could not find executable #{h}")
halt(1)
System.halt(1)
end
end
+8 -4
View File
@@ -19,7 +19,7 @@ defmodule Kernel.ParallelCompiler do
with its name can be optionally given as argument.
"""
def files(files, callback // default_callback) do
files_to_path(files, nil, callback)
spawn_compilers(files, nil, callback)
end
@doc """
@@ -27,6 +27,10 @@ defmodule Kernel.ParallelCompiler do
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)
end
defp spawn_compilers(files, path, callback) do
Code.ensure_loaded(Kernel.ErrorHandler)
spawn_compilers(files, path, callback, [], [], [])
end
@@ -42,9 +46,9 @@ defmodule Kernel.ParallelCompiler do
parent = self()
child = spawn_link fn ->
Process.put(:elixir_compiler_pid, parent)
Process.put(:elixir_ensure_compiled, true)
Process.flag(:error_handler, Kernel.ErrorHandler)
:erlang.put(:elixir_compiler_pid, parent)
:erlang.put(:elixir_ensure_compiled, true)
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
try do
if output do
+4 -5
View File
@@ -27,10 +27,9 @@ defmodule Kernel.ParallelRequire do
child = spawn_link fn ->
try do
new = Code.require_file(h)
result = (new || []) ++ result
new = Code.require_file(h)
callback.(h)
parent <- { :required, self }
parent <- { :required, self, new }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
@@ -42,8 +41,8 @@ defmodule Kernel.ParallelRequire do
defp wait_for_messages(files, waiting, callback, result) do
receive do
{ :required, child } ->
spawn_requires(files, List.delete(waiting, child), callback, result)
{ :required, child, new } ->
spawn_requires(files, List.delete(waiting, child), callback, (new || []) ++ result)
{ :failure, _child, kind, reason, stacktrace } ->
:erlang.raise(kind, reason, stacktrace)
end
+32 -4
View File
@@ -139,8 +139,8 @@ defmodule Kernel.SpecialForms do
import :all, List
It can also be customized to import only functions or only
macros:
It can also be customized to import only all functions or
all macros:
import :functions, List
import :macros, List
@@ -440,7 +440,7 @@ defmodule Kernel.SpecialForms do
it belonged to another file.
quote location: :keep, do: 1
#=> { :__scope__, 1,[[file: "iex"], 1] }
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
Check `quote/1` for more information.
"""
@@ -448,11 +448,39 @@ defmodule Kernel.SpecialForms do
@doc """
This is the special form used to hold aliases information.
At compilation time, it is usually compiled to an atom:
It is usually compiled to an atom:
quote do: Foo.Bar
{ :__aliases__, 0, [:Foo,:Bar] }
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
unambiguously identified by the operator `:.`. For example:
quote do: Foo.bar
{{:.,0,[{:__aliases__,0,[:Foo]},:bar]},0,[]}
Whenever an expression iterator sees a `:.` as the tuple key,
it can be sure that it represents a call and the second element
of the arguments list is an atom.
On the other hand, aliases holds some properties:
1) The head element of aliases can be any term;
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;
4) When the head element of aliases is not an atom, it is expanded at runtime:
quote do: some_var.Foo
{:__aliases__,0,[{:some_var,0,:quoted},:Bar]}
Since `some_var` is not available at compilation time, the compiler
expands such expression to:
Module.concat [some_var, Foo]
"""
defmacro __aliases__(args)
end
+376 -86
View File
@@ -1,54 +1,376 @@
defmodule Kernel.Typespec do
@moduledoc """
This is the module that converts Elixir typespecs
to Erlang typespecs syntax. Everytime @spec, @type
and @typep are used they proxy to the functions
in this module.
Holds macros and functions for working with typespecs.
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
`@callback` available in modules are handled by the equivalent
macros defined by this module.
See http://www.erlang.org/doc/reference_manual/typespec.html
for more information.
"""
defmacro deftype(name, options // []) do
_deftype(name, true, __CALLER__, options)
@doc """
Defines a type.
This macro is the one responsible to handle the attribute @type.
## Examples
@type my_type :: atom
"""
defmacro deftype(type) do
quote do
Kernel.Typespec.deftype(:type, (quote line: :keep, do: unquote(type)), __ENV__)
end
end
defmacro deftypep(name) do
_deftype(name, false, __CALLER__, [])
@doc """
Defines an opaque type.
This macro is the one responsible to handle the attribute @opaque.
## Examples
@opaque my_type :: atom
"""
defmacro defopaque(type) do
quote do
Kernel.Typespec.deftype(:opaque, (quote line: :keep, do: unquote(type)), __ENV__)
end
end
@doc """
Defines a private type.
This macro is the one responsible to handle the attribute @typep.
## Examples
@typep my_type :: atom
"""
defmacro deftypep(type) do
quote do
Kernel.Typespec.deftype(:typep, (quote line: :keep, do: unquote(type)), __ENV__)
end
end
@doc """
Defines a spec.
This macro is the one responsible to handle the attribute @spec.
## Examples
@spec add(number, number), do: number
"""
defmacro defspec(spec, block) do
_defspec(:spec, __CALLER__, spec, block)
quote do
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote spec),
(quote line: :keep, do: unquote block), __ENV__)
end
end
@doc """
Defines a callback.
This macro is the one responsible to handle the attribute @callback.
## Examples
@callback add(number, number), do: number
"""
defmacro defcallback(spec, block) do
_defspec(:callback, __CALLER__, spec, block)
quote do
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote spec),
(quote line: :keep, do: unquote block), __ENV__)
end
end
## Helpers
@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
{ kind, export } =
case kind do
:type -> { :type, true }
:typep -> { :type, false }
:opaque -> { :opaque, true }
end
Module.compile_typespec module, kind, type
if export do
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
end
type
end
@doc """
Get the types defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
Defines a `spec` by receiving Erlang's typespec.
"""
def get_types(module) do
Module.get_attribute(module, :type) ++ Module.get_attribute(module, :opaque)
def define_spec(module, tuple, definition) do
Module.compile_typespec module, :spec, { tuple, definition }
end
@doc """
Get the specs defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
Defines a `callback` by receiving Erlang's typespec.
"""
def get_specs(module) do
specs = :ets.tab2list(spec_table_for(module))
keys = :lists.ukeysort(1, specs)
lc { k, _ } inlist keys, do: { k, :proplists.append_values(k, specs) }
def define_callback(module, tuple, definition) do
Module.compile_typespec module, :callback, { tuple, definition }
end
## Typespec conversion
@doc """
Returns true if the current module defines a given type
(private, opaque or not). This function is only available
for modules being compiled.
"""
def defines_type?(module, name, arity) do
finder = match?({ ^name, _, vars } when length(vars) == arity, &1)
:lists.any(finder, Module.get_attribute(module, :type)) or
:lists.any(finder, Module.get_attribute(module, :opaque))
end
@doc """
Returns true if the current module defines a given spec.
This function is only available for modules being compiled.
"""
def defines_spec?(module, name, arity) do
tuple = { name, arity }
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :spec))
end
@doc """
Returns true if the current module defines a callback.
This function is only available for modules being compiled.
"""
def defines_callback?(module, name, arity) do
tuple = { name, arity }
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :callback))
end
@doc """
Converts a spec clause back to Elixir AST.
Returns a 2-items tuple with the spec arguments and return result.
"""
def spec_to_ast({ :type, _line, :fun, [{:type, _, :product, args},result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ args, typespec_to_ast(result) }
end
def spec_to_ast({ :type, _, :fun, [] }) do
{ [], quote do: term }
end
@doc """
Converts a type clause back to Elixir AST.
"""
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
fields = lc field inlist fields, do: typespec_to_ast(field)
args = lc arg inlist args, do: typespec_to_ast(arg)
type = { :{}, 0, [record|fields] }
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
end
def type_to_ast({ name, type, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
end
@doc """
Returns all types available from the beam.
It is returned as a list of tuples where the first
element is the type (`:typep`, `:type` and `:opaque`).
The module has to have a corresponding beam file
on the file system.
"""
def beam_types(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
exported_types = List.flatten(exported_types)
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
cond do
kind == :opaque -> { :opaque, type }
List.member?(exported_types, { name, length(args) }) -> { :type, type }
true -> { :typep, type }
end
end
_ ->
[]
end
end
@doc """
Returns all specs available from the beam.
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 file system.
"""
def beam_specs(module) do
from_abstract_code(module, :spec)
end
@doc """
Returns all callbacks available from the beam.
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 file system.
"""
def beam_callbacks(module) do
from_abstract_code(module, :callback)
end
defp from_abstract_code(module, kind) do
case abstract_code(module) do
{ :ok, abstract_code } ->
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
_ ->
[]
end
end
defp abstract_code(module) do
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
{:ok, { _, [{ :abstract_code, { raw_abstract_v1, abstract_code } }] } } ->
{ :ok, abstract_code }
_ ->
[]
end
end
defp abstract_code_beam(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
end
end
defp abstract_code_beam(binary) when is_binary(binary) do
binary
end
## Macro callbacks
@doc false
def deftype(kind, { :::, _, [type, definition] }, caller) do
do_deftype(kind, type, definition, caller)
end
def deftype(kind, type, caller) do
do_deftype(kind, type, { :term, caller.line, nil }, caller)
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
args =
if is_atom(args) do
[]
else
lc(arg inlist args, do: variable(arg))
end
vars = lc { :var, _, var } inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc { :var, _, _ } = var inlist args, do: var
type = { name, spec, vars }
define_type(caller.module, kind, type)
end
@doc false
def defspec(type, { name, line, args }, [do: return], caller) do
if is_atom(args), do: args = []
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
## To AST conversion
defp typespec_to_ast({ :type, line, :tuple, :any }) do
typespec_to_ast({:type, line, :tuple, []})
end
defp typespec_to_ast({ :type, line, :tuple, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :{}, line, args }
end
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
cond do
arg2 == 0 ->
quote line: line, do: <<_ :: unquote(arg1)>>
arg1 == 0 ->
quote line: line, do: <<_ :: _ * unquote(arg2)>>
true ->
quote line: line, do: <<_ :: unquote(arg1) * unquote(arg2)>>
end
end
defp typespec_to_ast({ :type, line, :union, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
Enum.reduce tl(args), hd(args),
fn(arg, expr) -> { :|, line, [expr, arg] } end
end
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :fun, line, args ++ [[do: typespec_to_ast(result)]] }
end
defp typespec_to_ast({ :type, line, :fun, [] }) do
{ :fun, line, [] }
end
defp typespec_to_ast({ :type, line, name, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ name, line, args }
end
defp typespec_to_ast({ :var, line, var }) do
{ var, line, nil }
end
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
dot = { :., line, [typespec_to_ast(mod), typespec_to_ast(name)] }
{ dot, line, args }
end
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
{ :::, line, [typespec_to_ast(var), typespec_to_ast(type)] }
end
defp typespec_to_ast({ :typed_record_field,
{ :record_field, line, { :atom, line1, name }},
type }) do
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
end
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
atom
end
## From AST conversion
# Handle unions
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
exprs = Enum.reverse(collect_union(exprs))
exprs = :lists.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line, :union, union }
end
@@ -58,11 +380,11 @@ defmodule Kernel.Typespec do
{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}
end
defp typespec({:<<>>, line, [{:|, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
defp typespec({:<<>>, line, [{:::, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, 0}, {:integer, line2, unit}]}
end
defp typespec({:<<>>, line, [{:|, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
defp typespec({:<<>>, line, [{:::, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, base}, {:integer, line2, 0}]}
end
@@ -71,7 +393,11 @@ defmodule Kernel.Typespec do
typespec({:range, line, args}, vars, caller)
end
# Handle aliases
# Handle special forms
defp typespec({:__MODULE__, _, atom}, vars, caller) when is_atom(atom) do
typespec(caller.module, vars, caller)
end
defp typespec({:__aliases__, _, _} = alias, vars, caller) do
atom = Macro.expand alias, caller
typespec(atom, vars, caller)
@@ -89,6 +415,13 @@ defmodule Kernel.Typespec do
{ :op, line, op, {:integer, line, integer} }
end
# Handle access macro
defp typespec({{:., line, [Kernel, :access]}, line1, [target, args]}, vars, caller) do
access = {{:., line, [Kernel, :access]}, line1,
[target, args ++ [_: (quote hygiene: false, do: any)]]}
typespec(Macro.expand(access, caller), vars, caller)
end
# Handle remote calls
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
remote = Macro.expand remote, caller
@@ -113,8 +446,8 @@ defmodule Kernel.Typespec do
# Handle funs
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
args =
case Enum.reverse(arguments) do
[[{:do,h}]|t] -> fn_args(line, Enum.reverse(t), h, vars, caller)
case :lists.reverse(arguments) do
[[{:do,h}]|t] -> fn_args(line, :lists.reverse(t), h, vars, caller)
[] -> []
_ -> [fn_args(line, arguments, vars, caller)]
end
@@ -125,13 +458,23 @@ defmodule Kernel.Typespec do
# Handle variables or local calls
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
{ :var, line, name }
{ :var, line, name }
else
typespec({name, line, []}, vars, caller)
end
end
# Handle local calls
defp typespec({:string, line, arguments}, vars, caller) do
IO.write "#{caller.file}:#{caller.line}: warning: string() type use is discouraged. For character lists, use char_list() type, for strings, String.t()\n"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, :string, arguments }
end
defp typespec({:char_list, _line, arguments}, vars, caller) do
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
end
defp typespec({name, line, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, name, arguments }
@@ -155,7 +498,7 @@ defmodule Kernel.Typespec do
end
defp typespec(l, _, _) when is_list(l) do
raise(ArgumentError, message: "Unexpected list #{inspect l}")
raise ArgumentError, message: "Unexpected list #{inspect l}"
end
defp typespec(t, vars, caller) when is_tuple(t) do
@@ -186,59 +529,6 @@ defmodule Kernel.Typespec do
{ :type, line, :product, args }
end
def _deftype({:"::", _, [name, definition]}, export, caller, opts) do
_deftype(name, definition, export, caller, opts)
end
def _deftype(name, export, caller, opts) do
_deftype(name, { :term, caller.line, nil }, export, caller, opts)
end
defp _deftype({name, _, args}, definition, export, caller, options) do
args = if is_atom(args), do: [], else: lc(arg inlist args, do: variable(arg))
vars = lc {:var, _, var} inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc ({:var, _, _} = var) inlist args, do: var
attr = if options[:opaque], do: :opaque, else: :type
export = if export do
quote do: Module.compile_type(__MODULE__, :export_type, [{name, length(vars)}])
else
nil
end
quote do
name = unquote(name)
spec = unquote(Macro.escape(spec))
vars = unquote(Macro.escape(vars))
type = { name, spec, vars }
Module.compile_type __MODULE__, unquote(attr), type
unquote(export)
{ unquote(attr), type }
end
end
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
if is_atom(args), do: args = []
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
code = Macro.escape { {type, { name, length(args) }}, [spec] }
table = spec_table_for(caller.module)
quote do
code = unquote(code)
:ets.insert(unquote(table), code)
code
end
end
defp spec_table_for(module) do
table = list_to_atom :lists.concat([:s, module])
unless table == :ets.info(table, :name), do:
raise(ArgumentError, message: "cannot manage specs for #{inspect module} because it was already compiled")
table
end
defp variable({name, line, _}) do
{:var, line, name}
end
+47 -17
View File
@@ -13,11 +13,16 @@ defmodule Keyword do
entries for a given key are removed when invoked.
"""
@type key :: atom
@type value :: any
@type t :: [{key, value}]
@doc """
Creates a Keyword from enum. Differently from `Keyword.new`
which behaves as a dict, `Keyword.from_enum` do not remove
duplicated entries.
"""
@spec from_enum(Enum.t), do: t
def from_enum(enum) when is_list(enum) do
enum
end
@@ -26,9 +31,24 @@ defmodule Keyword do
Enum.map(enum, fn(x) -> x end)
end
@doc """
Checks if the given argument is a keywords list or not
"""
@spec keyword?(term), do: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir-' ++ _ -> false
_ -> keyword?(rest)
end
end
def keyword?([]), do: true
def keyword?(_other), do: false
@doc """
Returns an empty keyword list, i.e. an empty list.
"""
@spec new, do: t
def new do
[]
end
@@ -43,6 +63,7 @@ defmodule Keyword do
#=> [a: 2, b: 1]
"""
@spec new(Enum.t), do: t
def new(pairs) do
Enum.reduce pairs, [], fn { k, v }, keywords ->
put(keywords, k, v)
@@ -60,6 +81,7 @@ defmodule Keyword do
#=> [a: :a, b: :b]
"""
@spec new(Enum.t, fun({key, value}, do: {key, value})), do: t
def new(pairs, transform) do
Enum.reduce pairs, [], fn i, keywords ->
{ k, v } = transform.(i)
@@ -83,7 +105,9 @@ defmodule Keyword do
Keyword.get [a: 1], :b, 3 #=> 3
"""
def get(keywords, key, default // nil) do
@spec get(t, key), do: value
@spec get(t, key, value), do: value
def get(keywords, key, default // nil) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> default
@@ -100,10 +124,11 @@ defmodule Keyword do
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
"""
def get!(keywords, key) do
@spec get!(t, key), do: value | no_return
def get!(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(Keyword.KeyError, key: key)
false -> raise(KeyError, key: key)
end
end
@@ -116,7 +141,8 @@ defmodule Keyword do
#=> [1,2]
"""
def get_values(keywords, key) do
@spec get_values(t, key), do: [value]
def get_values(keywords, key) when is_atom(key) do
lc { k, v } inlist keywords, key == k, do: v
end
@@ -129,6 +155,7 @@ defmodule Keyword do
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
"""
@spec keys(t), do: [key]
def keys(keywords) do
lc { key, _ } inlist keywords, do: key
end
@@ -141,6 +168,7 @@ defmodule Keyword do
Keyword.values [a: 1, b: 2] #=> [1,2]
"""
@spec values(t), do: [value]
def values(keywords) do
lc { _, value } inlist keywords, do: value
end
@@ -157,7 +185,8 @@ defmodule Keyword do
Keyword.delete [b: 2], :a #=> [b: 2]
"""
def delete(keywords, key) do
@spec delete(t, key), do: t
def delete(keywords, key) when is_atom(key) do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
end
@@ -173,8 +202,9 @@ defmodule Keyword do
#=> [a: 3, b: 2]
"""
def put(list, key, value) do
[{key, value}|delete(list, key)]
@spec put(t, key, value), do: t
def put(keywords, key, value) when is_atom(key) do
[{key, value}|delete(keywords, key)]
end
@doc """
@@ -187,6 +217,7 @@ defmodule Keyword do
#=> true
"""
@spec equal?(t, t), do: boolean
def equal?(left, right) do
:lists.sort(left) == :lists.sort(right)
end
@@ -201,6 +232,7 @@ defmodule Keyword do
#=> [a:3, b:2, d: 4]
"""
@spec merge(t, t), do: t
def merge(d1, d2) do
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
end
@@ -217,6 +249,7 @@ defmodule Keyword do
#=> [a:4, b:2, d: 4]
"""
@spec merge(t, t, fun(key, value, value, do: value)), do: t
def merge(d1, d2, fun) do
do_merge(d2, d1, fun)
end
@@ -229,12 +262,6 @@ defmodule Keyword do
acc
end
@doc false
def key?(list, key) do
IO.write "[WARNING] Keyword.key? is deprecated, please use Keyword.has_key? instead\n#{Exception.formatted_stacktrace}"
has_key?(list, key)
end
@doc """
Returns whether a given key exists in the given keywords.
@@ -246,22 +273,24 @@ defmodule Keyword do
#=> false
"""
def has_key?(keywords, key) do
@spec has_key?(t, key), do: boolean
def has_key?(keywords, key) when is_atom(key) do
:lists.keymember(key, 1, keywords)
end
@doc """
Updates the key with the given function. If the key does
not exist, raises `Keyword.KeyError`.
not exist, raises `KeyError`.
## Examples
Keyword.update([a: 1], :a, &1 * 2)
#=> [a: 2]
Keyword.update([a: 1], :b, &1 * 2)
#=> Keyword.KeyError
#=> KeyError
"""
@spec update(t, key, fun(value, do: value)), do: t | no_return
def update([{key, value}|keywords], key, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
@@ -271,7 +300,7 @@ defmodule Keyword do
end
def update([], key, _fun) when is_atom(key) do
raise(Keyword.KeyError, key: key)
raise(KeyError, key: key)
end
@doc """
@@ -286,6 +315,7 @@ defmodule Keyword do
#=> [a: 1, b: 11]
"""
@spec update(t, key, value, fun(value, do: value)), do: t
def update([{key, value}|keywords], key, _initial, fun) do
[{key, fun.(value)}|delete(keywords, key)]
end
+2 -22
View File
@@ -123,12 +123,6 @@ defmodule List do
:lists.foldr(function, acc, list)
end
@doc false
def reverse(list) do
IO.write "[WARNING] List.reverse/1 is deprecated, please use Enum.reverse/1 instead\n#{Exception.formatted_stacktrace}"
:lists.reverse(list)
end
@doc """
Returns the last element in `list` or nil if the `list` is empty.
@@ -277,7 +271,7 @@ defmodule List do
def range(first, last, step // nil)
def range(first, last, step) when is_integer(first) and is_integer(last) and first <= last do
step = case step do
case step do
nil ->
:lists.seq(first, last, 1)
x when x < 0 ->
@@ -288,7 +282,7 @@ defmodule List do
end
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
step = case step do
case step do
nil ->
:lists.seq(first, last, -1)
x when x > 0 ->
@@ -361,12 +355,6 @@ defmodule List do
[other]
end
@doc false
def zip(item1, item2) do
IO.write "[WARNING] List.zip/2 is deprecated, please use Enum.zip/2 instead\n#{Exception.formatted_stacktrace}"
do_zip(to_list(item1), to_list(item2), [])
end
@doc """
Zips corresponding elements from each list in `list_of_lists`.
@@ -420,14 +408,6 @@ defmodule List do
# zip
defp do_zip([h1|t1], [h2|t2], acc) do
do_zip t1, t2, [{h1, h2}|acc]
end
defp do_zip(_, _, acc) do
:lists.reverse acc
end
defp do_zip(list, acc) do
converter = fn x, acc -> do_zip_each(to_list(x), acc) end
{mlist, heads} = :lists.mapfoldl converter, [], list
+6 -3
View File
@@ -236,11 +236,14 @@ defmodule Macro do
defp is_kw_blocks?([_|_] = kw) do
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
end
defp is_kw_blocks?(_), do: false
defp is_kw_blocks?(_), do: false
defp module_to_binary(atom) when is_atom(atom), do: Binary.Inspect.inspect(atom, raw: true)
defp module_to_binary(other), do: call_to_binary(other)
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_binary({ :., _, [arg] }), do: call_to_binary(arg) <> "."
defp call_to_binary({ :., _, [left, right] }), do: call_to_binary(left) <> "." <> call_to_binary(right)
defp call_to_binary({ :., _, [arg] }), do: module_to_binary(arg) <> "."
defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
defp call_to_binary(other), do: to_binary(other)
defp call_to_binary_with_args(target, args) do
+23 -2
View File
@@ -1,6 +1,22 @@
defmodule Macro.Env do
Record.deffunctions([:module, :file, :line, :function,
:aliases, :context, :requires, :macros], __MODULE__)
@type name_arity :: { atom, non_neg_integer }
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{ module, module }]
@type context :: :assign | :guard | nil
@type requires :: [module]
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
fields = [:module, :file, :line, :function,
:aliases, :context, :requires, :functions, :macros]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __ENV__)
@moduledoc """
A record that contains compile time environment information,
@@ -46,6 +62,11 @@ defmodule Macro.Env do
"""
def requires(record)
@doc """
Returns a list of functions imported from each module.
"""
def functions(record)
@doc """
Returns a list of macros imported from each module.
"""
+39 -50
View File
@@ -23,43 +23,40 @@ defmodule Module do
@doc """
Evalutes the quotes contents in the given module context.
A list of environment options can also be given as argument.
Check `Code.eval` for more information.
Raises an error if the module was already compiled.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
## Examples
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __MODULE__, contents, []
Module.eval_quoted __MODULE__, contents
end
Foo.sum(1, 2) #=> 3
This function also accepts a `Macro.Env` as first argument. This
is useful to evalute the quoted contents inside an existing
environment (considering the environemnt module, line and file):
For convenience, you can my pass `__ENV__` as argument and
all options will be automatically extracted from the environment:
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __ENV__, contents, []
Module.eval_quoted __MODULE__, contents, [], __ENV__
end
Foo.sum(1, 2) #=> 3
"""
def eval_quoted(env, quoted, binding // [], opts // [])
def eval_quoted(module, quoted, binding // [], opts // [])
def eval_quoted(Macro.Env[module: module] = env, quoted, binding, opts) do
eval_quoted(module, quoted, binding, Keyword.merge(env.location, opts))
eval_quoted(module, quoted, binding, Keyword.merge(env.to_keywords, opts))
end
def eval_quoted(module, quoted, binding, Macro.Env[] = env) do
eval_quoted(module, quoted, binding, env.to_keywords)
end
def eval_quoted(module, quoted, binding, opts) do
@@ -79,7 +76,7 @@ defmodule Module do
def world, do: true
end
Module.create(Hello, contents, __ENV__)
Module.create(Hello, contents, __ENV__.location)
Hello.world #=> true
@@ -91,16 +88,15 @@ defmodule Module do
function is preferred when the module body is given
by a quoted expression.
Another important distinction is that `defmodule`
blends into the scope it is invoked, allowing you
to access all variables, imports and requires from
the module. `Module.create`, on the other hand, creates
a new scope so imports, requires, etc are not inherited.
Another important distinction is that `Module.create`
allows you to control the environment variables used
when defining the module, while `defmodule` automatically
shares the same environment.
"""
def create(module, quoted, opts // [])
def create(module, quoted, Macro.Env[] = env) do
create(module, quoted, env.location)
create(module, quoted, env.to_keywords)
end
def create(module, quoted, opts) when is_atom(module) do
@@ -196,7 +192,7 @@ defmodule Module do
end
def add_doc(module, line, kind, tuple, signature, doc) when
kind in [:def, :defmacro, :defcallback] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
kind in [:def, :defmacro] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
@@ -212,7 +208,7 @@ defmodule Module do
ETS.insert(table, {
tuple,
line,
merge_kind(old_kind, kind),
kind,
merge_signatures(old_sign, signature, 1),
doc || old_doc
})
@@ -249,10 +245,6 @@ defmodule Module do
# Merge
defp merge_kind(:defcallback, new), do: new
defp merge_kind(old, :defcallback), do: old
defp merge_kind(_old, new), do: new
defp merge_signatures([h1|t1], [h2|t2], i) do
[merge_signature(h1, h2, i)|merge_signatures(t1, t2, i + 1)]
end
@@ -371,8 +363,8 @@ defmodule Module do
ETS.delete(table, tuple)
old = get_attribute(module, :__overridable)
new = [ { tuple, { 1, [clause] } } ]
merged = :orddict.merge(fn(_k, { count, v1 }, _v2) -> { count + 1, [clause|v1] } end, old, new)
new = [ { tuple, { 1, clause, false } } ]
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
put_attribute(module, :__overridable, merged)
_ ->
@@ -386,8 +378,7 @@ defmodule Module do
Returns true if the given tuple in module is marked as overridable.
"""
def overridable?(module, tuple) do
key = List.keyfind(get_attribute(module, :__overridable), tuple, 0)
match? { _, { _, [_|_] } }, key
!! List.keyfind(get_attribute(module, :__overridable), tuple, 0)
end
@doc """
@@ -421,12 +412,6 @@ defmodule Module do
ETS.insert(table, { key, new })
end
@doc false
def add_attribute(module, key, value) when is_atom(key) do
IO.write "[WARNING] Module.add_attribute is deprecated, please use Module.put_attribute instead\n#{Exception.formatted_stacktrace}"
put_attribute(module, key, value)
end
@doc """
Gets the given attribute from a module. If the attribute
was marked as accumulate with `Module.register_attribute`,
@@ -456,12 +441,6 @@ defmodule Module do
end
end
@doc false
def read_attribute(module, key) when is_atom(key) do
IO.write "[WARNING] Module.read_attribute is deprecated, please use Module.get_attribute instead\n#{Exception.formatted_stacktrace}"
get_attribute(module, key)
end
@doc """
Deletes all attributes that matches the given key.
@@ -536,19 +515,29 @@ defmodule Module do
"""
def split(module) do
tl(String.split(to_binary(module), "-"))
tl(String.split(Binary.Chars.to_binary(module), "-"))
end
@doc """
Convert a module name to binary without the Elixir prefix.
"""
def to_binary(module) do
"Elixir-" <> rest = Binary.Chars.to_binary(module)
bc <<r>> inbits rest, do: <<to_dot(r)>>
end
defp to_dot(?-), do: ?.
defp to_dot(l), do: l
@doc false
# Used internally to compile documentation. This function
# is private and must be used only internally.
def compile_doc(env, kind, name, args, _guards, body) do
def compile_doc(env, kind, name, args, _guards, _body) do
module = env.module
line = env.line
arity = length(args)
pair = { name, arity }
doc = get_attribute(module, :doc)
kind = if kind == :def and body == nil and module != Kernel, do: :defcallback, else: kind
case add_doc(module, line, kind, pair, args, doc) do
:ok ->
@@ -565,7 +554,7 @@ defmodule Module do
@doc false
# Used internally to compile types. This function
# is private and must be used only internally.
def compile_type(module, key, value) when is_atom(key) do
def compile_typespec(module, key, value) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
+30 -7
View File
@@ -73,23 +73,46 @@ defmodule Node do
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
the list of available options.
"""
def spawn(node, fun, opts // []) do
def spawn(node, fun) do
:erlang.spawn(node, fun)
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
the list of available options.
"""
def spawn(node, fun, opts) do
:erlang.spawn_opt(node, fun, opts)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists, a useless
pid is returned.
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
the list of available options.
"""
def spawn(node, module, fun, args, opts // []) do
:erlang.spawn(node, module, fun, args, opts)
def spawn(node, module, fun, args) do
:erlang.spawn(node, module, fun, args)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists,
a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
the list of available options.
"""
def spawn(node, module, fun, args, opts) do
:erlang.spawn_opt(node, module, fun, args, opts)
end
@doc """
+16 -9
View File
@@ -44,8 +44,7 @@ defmodule OptionParser do
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
dict = Keyword.new(flags, fn(k) -> { k, false } end)
parse(argv, aliases, flags, dict, [], true)
parse(argv, aliases, flags, true)
end
@doc """
@@ -63,12 +62,15 @@ defmodule OptionParser do
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
dict = Keyword.new(flags, fn(k) -> { k, false } end)
parse(argv, aliases, flags, dict, [], false)
parse(argv, aliases, flags, false)
end
## Helpers
defp parse(argv, aliases, flags, all) do
parse(argv, aliases, flags, [], [], all)
end
defp parse(["-" <> option|t], aliases, flags, dict, args, all) do
{ option, value } = normalize_option(option, aliases)
@@ -84,16 +86,16 @@ defmodule OptionParser do
parse(t, aliases, flags, dict, args, all)
end
defp parse([], _, _, dict, args, true) do
{ dict, Enum.reverse(args) }
defp parse([], _, flags, dict, args, true) do
{ reverse_dict(dict, flags), Enum.reverse(args) }
end
defp parse([h|t], aliases, flags, dict, args, true) do
parse(t, aliases, flags, dict, [h|args], true)
end
defp parse(value, _, _, dict, _args, false) do
{ dict, value }
defp parse(value, _, flags, dict, _args, false) do
{ reverse_dict(dict, flags), value }
end
defp flag_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
@@ -108,7 +110,12 @@ defmodule OptionParser do
end
defp store_option(dict, option, value) do
Keyword.put dict, option, value
[{ option, value }|dict]
end
defp reverse_dict(dict, flags) do
flags = lc k inlist flags, not Keyword.has_key?(dict, k), do: { k, false }
Enum.reverse flags ++ dict
end
defp normalize_option(<<?-, option :: binary>>, aliases) do
+8
View File
@@ -44,6 +44,14 @@ defmodule OrdDict do
end
end
@doc false
def get!(dict(data), key) do
case :orddict.find(key, data) do
{:ok, value} -> value
:error -> raise(KeyError, key: key)
end
end
@doc false
def put(dict(data), key, value) do
dict(:orddict.store key, value, data)
+25 -8
View File
@@ -103,12 +103,19 @@ defmodule Process do
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as the `Kernel.spawn/1` function except
it also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
It behaves exactly the same as `Kernel.spawn/1`.
"""
def spawn(fun, opts // []) do
def spawn(fun) do
:erlang.spawn(fun)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
"""
def spawn(fun, opts) do
:erlang.spawn_opt(fun, opts)
end
@@ -117,12 +124,22 @@ defmodule Process do
`module.function(args)`. The new process created will be placed in the system
scheduler queue and be run some time later.
It behaves exactly the same as the `Kernel.spawn/3` function except
it also accepts extra options, for the list of available options
It behaves exactly the same as the `Kernel.spawn/3` function.
"""
def spawn(mod, fun, args) do
:erlang.spawn(mod, fun, args)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)`. The new process created will be placed in the system
scheduler queue and be run some time later.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
"""
def spawn(mod, fun, args, opts // []) do
def spawn(mod, fun, args, opts) do
:erlang.spawn_opt(mod, fun, args, opts)
end
+207 -129
View File
@@ -23,13 +23,14 @@ defmodule Protocol 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
]
# Import the new dsl that holds the new def
import Protocol.DSL
import :macros, Protocol.DSL
# Set up a clear slate to store defined functions
@functions []
@@ -38,9 +39,9 @@ defmodule Protocol do
unquote(block)
# Define callbacks and meta information
{ conversions, fallback } = Protocol.conversions_for(__MODULE__, @only, @except)
Protocol.impl_for(conversions, fallback, __ENV__)
Protocol.meta(@functions, fallback, __ENV__)
{ 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__)
end
end
end
@@ -74,10 +75,9 @@ defmodule Protocol do
end
end
@doc """
Check if the given module is a protocol. Raises an error
if not loaded or not a protocol.
"""
# Check if the given module is a protocol. Raises an error
# if not loaded or not a protocol.
@doc false
def assert_protocol(module) do
case Code.ensure_compiled(module) do
{ :module, ^module } -> nil
@@ -92,60 +92,16 @@ defmodule Protocol do
end
end
@doc """
Defines meta information about the protocol and internal callbacks.
"""
def meta(functions, fallback, env) do
contents = quote do
def __protocol__(:name), do: __MODULE__
def __protocol__(:functions), do: unquote(:lists.sort(functions))
@doc false
def behaviour_info(:callbacks), do: [{ :__impl__, 0 }|__protocol__(:functions)]
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__, [], _ }|_]|_] ->
__fallback__
end
other ->
other
end
end
def __impl_for__!(arg) do
if module = __impl_for__(arg) do
module
else
raise Protocol.UndefinedError, protocol: __MODULE__, structure: arg
end
end
defp __fallback__, do: unquote(fallback)
end
Module.eval_quoted env, contents
end
@doc """
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.
"""
def impl_for(conversions, fallback, env) do
# 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.
@doc false
def impl_for(conversions, fallback, returns_nil, env) do
contents = lc kind inlist conversions do
each_impl_for(kind, if fallback, do: conversions)
each_impl_for(kind, conversions, fallback)
end
# If we don't implement all protocols and any is not in the
# list, we need to add a final clause that returns nil.
if !L.keyfind(Any, 1, conversions) && length(conversions) < 10 do
if returns_nil do
contents = contents ++ [quote do
defp __raw_impl__(_) do
nil
@@ -153,13 +109,65 @@ defmodule Protocol do
end]
end
Module.eval_quoted env, contents
Module.eval_quoted env.module, contents, [], env.location
end
@doc """
Returns the default conversions according to the given
only/except options.
"""
# Defines meta information about the protocol and internal callbacks.
@doc false
def meta(functions, conversions, fallback, returns_nil, env) do
any = L.keyfind(Any, 1, conversions) != false
meta = quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: unquote(generate_type(conversions, any))
end
def __protocol__(:name), do: __MODULE__
def __protocol__(:functions), do: unquote(:lists.sort(functions))
end
impl_for = if L.keyfind(Record, 1, conversions) do
quote 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
else
quote 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
end
# Returns the default conversions according to the given
# only/except options.
@doc false
def conversions_for(module, only, except) do
kinds = all_types
@@ -180,65 +188,63 @@ defmodule Protocol do
nil
end
{ conversions, fallback }
# 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) -> { :|, 0, [acc, x] } end
{ _, _, first } = hd(conversions)
:lists.foldl(or_function, first, tl(conversions))
end
defp all_types do
[
{ Record, :is_record },
{ Tuple, :is_tuple },
{ Atom, :is_atom },
{ List, :is_list },
{ BitString, :is_bitstring },
{ Number, :is_number },
{ Function, :is_function },
{ PID, :is_pid },
{ Port, :is_port },
{ Reference, :is_reference },
{ Any, :is_any }
]
[ { 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: fun },
{ 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
defp is_builtin?([{h,_,_}]) do
quote do
first == unquote(h)
end
end
defp is_builtin?([{h,_}|t]) do
defp is_builtin?([{h,_,_}|t]) do
quote do
first == unquote(h) or unquote(is_builtin?(t))
end
end
# Handle records when we don't have fallbacks.
# It simply gets the first element of the tuple.
# This case assumes that, whenever a tuple is given
# it is meant to be a record, so we don't need extra
# checks.
defp each_impl_for({ _, :is_record }, nil) do
quote do
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
__MODULE__.Record
end
end
end
# Specially handle records in the case we have fallbacks.
defp each_impl_for({ _, :is_record }, conversions) do
defp each_impl_for({ _, :is_record, _ }, conversions, fallback) do
quote do
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
first = :erlang.element(1, arg)
case unquote(is_builtin?(conversions)) do
true -> __fallback__
true -> unquote(fallback)
false ->
case atom_to_list(first) do
'Elixir-' ++ _ -> __MODULE__.Record
_ -> __fallback__
_ -> unquote(fallback)
end
end
end
@@ -246,7 +252,7 @@ defmodule Protocol do
end
# Special case any as we don't need to generate a guard.
defp each_impl_for({ _, :is_any }, _) do
defp each_impl_for({ _, :is_any, _ }, _, _) do
quote do
defp __raw_impl__(_) do
__MODULE__.Any
@@ -255,10 +261,10 @@ defmodule Protocol do
end
# Generate all others protocols.
defp each_impl_for({ kind, fun }, _) do
defp each_impl_for({ kind, fun, _ }, _, _) do
quote do
defp __raw_impl__(arg) when unquote(fun).(arg) do
Module.concat __MODULE__, unquote(kind)
__MODULE__.unquote(kind)
end
end
end
@@ -267,6 +273,95 @@ 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>>), 0, :quoted }
end
{ conversions, fallback, returns_nil } = conversions_for(module)
clauses = [default_clause(name, args)]
if returns_nil do
clauses = [nil_clause()|clauses]
end
if L.keyfind(Record, 1, conversions) do
clauses = [record_clause(name, args, fallback)|clauses]
end
body =
quote do
case __raw_impl__(xA), do: unquote({ :->, 0, clauses })
end
{ args, body }
end
@doc false
def callback_from_spec(module, name, arity) do
tuple = { name, arity }
specs = Module.get_attribute(module, :spec)
found = lc { k, v } inlist specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
found != []
end
defp conversions_for(module) do
only = Module.get_attribute(module, :only)
except = Module.get_attribute(module, :except)
Protocol.conversions_for(module, only, except)
end
defp default_clause(name, args) do
quote do: { [other], apply(other, unquote(name), [unquote_splicing(args)]) }
end
defp nil_clause() do
quote do
{ [nil], raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA) }
end
end
defp record_clause(name, args, fallback) do
arity = length(args)
quote do
{ [__MODULE__.Record],
(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) ->
unquote(catch_clause(args, fallback))
end) }
end
end
defp catch_clause(args, fallback) do
if fallback do
quote do
apply unquote(fallback), name, [unquote_splicing(args)]
end
else
quote do
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
end
end
end
defmacro def(expression) do
case expression do
{ _, _, args } when args == [] or is_atom(args) ->
@@ -279,43 +374,26 @@ defmodule Protocol.DSL do
arity = length(args)
# 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.
generated = lc i inlist :lists.seq(1, arity) do
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
end
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
quote do
# Append new function to the list
@functions [unquote({name, arity})|@functions]
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))
Kernel.def unquote(name).(unquote_splicing(generated)) do
case __raw_impl__(xA) do
__MODULE__.Record ->
target = Module.concat(__MODULE__, :erlang.element(1, xA))
try do
target.unquote(name)(unquote_splicing(generated))
catch
:error, :undef, [[{ ^target, unquote(name), args, _ }|_]|_] when length(args) == unquote(arity) ->
case __fallback__ do
nil ->
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
other ->
apply other, unquote(name), [unquote_splicing(generated)]
end
end
nil ->
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
other ->
apply other, unquote(name), [unquote_splicing(generated)]
end
end
{ 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)), do: term
end
end
end
+220 -168
View File
@@ -18,7 +18,7 @@ defmodule Record do
@doc """
Main entry point for records definition. It defines a module
with the given `name` and the fields specified in `modules`.
with the given `name` and the fields specified in `values`.
This is invoked directly by `Kernel.defrecord`, so check it
for more information and documentation.
"""
@@ -29,22 +29,39 @@ defmodule Record do
quote do
defmodule unquote(name) do
@moduledoc false
Record.deffunctions(unquote(values), unquote(opts), __ENV__)
import Record.DSL
values = unquote(values)
opts = unquote(opts)
Record.deffunctions(values, opts, __ENV__)
unquote(block)
Record.deftypes(values, @record_type, opts, __ENV__)
end
end
end
@doc """
Main entry point for private records definition. It defines
a set of macros with the given `name` and the fields specified
in `values`. This is invoked directly by `Kernel.defrecordp`,
so check it for more information and documentation.
"""
def defrecordp(name, fields) do
quote do
Record.defmacros(unquote(name), unquote(fields), __ENV__)
end
end
@doc """
Defines record functions skipping the module definition.
This is called directly by `defrecord`. It expects the
module environment, the module values and a keyword list
of options.
This is called directly by `defrecord`. It expects the record
values, a set of options and the module environment.
## Examples
defmodule CustomRecord do
Record.deffunctions [:name, :age], __ENV__
Record.deftypes [:name, :age], __ENV__
end
"""
@@ -69,85 +86,83 @@ defmodule Record do
:elixir_module.eval_quoted(env, contents, [], [])
else
contents = [quote(do: @__record__ unquote(escaped))|contents]
Module.eval_quoted(env, contents)
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@doc """
Defines three macros for reading and writing records values.
These macros are private to the current module and are
basically a simple mechanism for manipulating tuples when
there isn't an interest in exposing the record as a whole.
In some ways, it is similar to Erlang records, since it is
only available at compilation time.
Defines types and specs for the record.
"""
def deftypes(values, types, opts // [], env) do
types = types || []
values = lc value inlist values do
{ name, default } = convert_value(value)
{ name, default, find_spec(types, name) }
end
extensions = Keyword.get(opts, :extensions, Record.Extensions)
contents = [
core_specs(values),
accessor_specs(values, []),
extensions_specs(values, [], extensions)
]
# Special case for bootstraping purposes
if :erlang.function_exported(Module, :eval_quoted, 2) do
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@doc """
Defines macros for manipulating records. This is called
directly by `defrecordp`. It expects the macro name, the
record values and the environment.
## Examples
defmodule CustomModule do
Record.defmacros :_user, [:name, :age], __ENV__
def new(name, age) do
_user(name: name, age: age)
end
def name(user, name) do
_user(user, name: name)
end
def age(user) do
_user(user, :age)
end
def to_keywords(user) do
_user(user)
end
def name_and_age(user) do
_user(user, [:name, :age])
end
def age_and_name(user) do
_user(user, [:age, :name])
end
defmodule CustomRecord do
Record.defmacros :user, [:name, :age], __ENV__
end
"""
def defmacros(name, values, env) do
escaped = lc value inlist values, do: Macro.escape(convert_value(value))
escaped = lc value inlist values do
{ key, value } = convert_value(value)
{ key, Macro.escape(value) }
end
contents = quote do
defmacrop unquote(name).() do
Record.access(__MODULE__, unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name).(record) when is_tuple(record) do
Record.to_keywords(__CALLER__, __MODULE__, unquote(escaped), record)
Record.to_keywords(__MODULE__, unquote(escaped), record)
end
defmacrop unquote(name).(args) do
Record.access(__CALLER__, __MODULE__, unquote(escaped), args)
Record.access(__MODULE__, unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name).(record, key) when is_atom(key) do
Record.get(__CALLER__, __MODULE__, unquote(escaped), record, key)
Record.get(__MODULE__, unquote(escaped), record, key)
end
defmacrop unquote(name).(record, args) do
Record.dispatch(__CALLER__, __MODULE__, unquote(escaped), record, args)
Record.dispatch(__MODULE__, unquote(escaped), record, args, __CALLER__)
end
end
Module.eval_quoted(env, contents)
Module.eval_quoted(env.module, contents, [], env.location)
end
defp convert_value(atom) when is_atom(atom) do
{ atom, nil }
end
defp convert_value(other), do: other
# Implements the access macro used by records.
# It returns a quoted expression that defines
# a record or a match in case the record is
# inside a match.
@doc false
def access(caller, atom, fields, keyword) do
def access(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise "expected contents inside brackets to be a Keyword"
end
@@ -184,11 +199,11 @@ defmodule Record do
# Dispatch the call to either update or to_list depending on the args given.
@doc false
def dispatch(caller, atom, fields, record, args) do
def dispatch(atom, fields, record, args, caller) do
if is_keyword(args) do
update(caller, atom, fields, record, args)
update(atom, fields, record, args, caller)
else
to_list(caller, atom, fields, record, args)
to_list(atom, fields, record, args)
end
end
@@ -196,7 +211,7 @@ defmodule Record do
# It returns a quoted expression that represents
# the access given by the keywords.
@doc false
defp update(caller, atom, fields, var, keyword) do
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise "expected contents inside brackets to be a Keyword"
end
@@ -221,7 +236,7 @@ defmodule Record do
# It returns a quoted expression that represents
# getting the value of a given field.
@doc false
def get(_caller, atom, fields, var, key) do
def get(atom, fields, var, key) do
index = find_index(fields, key, 0)
if index do
quote do
@@ -236,12 +251,12 @@ defmodule Record do
# It returns a quoted expression that represents
# converting record to keywords list.
@doc false
def to_keywords(_caller, _atom, fields, record) do
Enum.map Keyword.from_enum(fields),
fn({key, _default}) ->
def to_keywords(_atom, fields, record) do
Enum.map fields,
fn { key, _default } ->
index = find_index(fields, key, 0)
quote do
{unquote(key), :erlang.element(unquote(index + 2), unquote(record))}
{ unquote(key), :erlang.element(unquote(index + 2), unquote(record)) }
end
end
end
@@ -250,7 +265,7 @@ defmodule Record do
# It returns a quoted expression that represents
# extracting given fields from record.
@doc false
defp to_list(_caller, atom, fields, record, keys) do
defp to_list(atom, fields, record, keys) do
Enum.map keys,
fn(key) ->
index = find_index(fields, key, 0)
@@ -262,11 +277,7 @@ defmodule Record do
end
end
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
defp is_keyword_tuple(_), do: false
## Function generation
# Define __record__/1 and __record__/2 as reflection functions
# that returns the record names and fields.
@@ -378,10 +389,6 @@ defmodule Record do
end
end
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
# Implement readers. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
@@ -434,18 +441,24 @@ defmodule Record do
defp writers([], _i, acc), do: acc
# Defines update/2
# 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(find_index(values, key, 1))))
Keyword.get(keywords, unquote(key), elem(record, unquote(index)))
end
end
contents = {:{}, 0, [(quote do: __MODULE__)|fields]}
contents = { :{}, 0, [(quote do: __MODULE__)|fields] }
quote do
def update([], record) do
record
end
def update(keywords, record) do
unquote(contents)
end
@@ -459,106 +472,96 @@ defmodule Record do
end
defp extensions([], _i, acc, _), do: acc
## Types/specs generation
defp core_specs(values) do
types = lc { _, _, spec } inlist values, do: spec
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: { __MODULE__, unquote_splicing(types) }
end
@spec new, do: t
@spec new(Keyword.t | tuple), do: t
@spec to_keywords(t), do: Keyword.t
@spec update(Keyword.t, t), do: t
@spec __index__(atom), do: non_neg_integer | nil
end
end
defp accessor_specs([{ key, _default, spec }|t], acc) do
contents = quote do
@spec unquote(key)(t), do: unquote(spec)
@spec unquote(key)(unquote(spec), t), do: t
end
accessor_specs(t, [contents | acc])
end
defp accessor_specs([], acc), do: acc
defp extensions_specs([{ key, default, spec }|t], acc, extensions) do
specs = extensions.specs_for(key, default, spec)
extensions_specs(t, [specs | acc], extensions)
end
defp extensions_specs([], acc, _), do: acc
## Helpers
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
defp is_keyword_tuple(_), do: false
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
defp convert_value(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
defp find_spec(types, name) do
matches = lc { k, v } inlist types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
end
end
end
defmodule Record.Extractor do
defmodule Record.DSL do
@moduledoc false
# 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)
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
@doc """
Defines the type for each field in the record.
Expects a keyword list.
"""
defmacro record_type(opts) when is_list(opts) do
quote do
@record_type quote do: unquote(opts)
end
retrieve_record(name, realfile)
end
# 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))
case :code.lib_dir(to_char_list(app)) do
{ :error, _ } ->
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
end
end
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
records = retrieve_from_file(file)
if record = List.keyfind(records, name, 0) do
parse_record(record)
else
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_from_file(file) do
lc { :attribute, _, :record, record } inlist read_file(file), do: record
end
# Read a file and return its abstract syntax form that also
# includes record and other preprocessor modules. This is done
# by using Erlang's epp_dodger.
defp read_file(file) do
case :epp_dodger.quick_parse_file(file) do
{ :ok, form } ->
form
other ->
raise "Error parsing file #{to_binary(file)}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of second order tuples where the first element
# is the field and the second is its default value.
defp parse_record({ _name, fields }) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = :erl_eval.expr(cons, [])
list
end
defp parse_field({ :typed_record_field, record_field, _type }) do
parse_field(record_field)
end
defp parse_field({ :record_field, _, key }) do
{ :tuple, 0, [key, {:atom, 0, :nil}] }
end
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
end
defmodule Record.Extensions do
@moduledoc false
# Main entry point. It defines both default functions
# via `default_for` and extensions via `extension_for`.
# Function definition
def functions_for(key, default, i) do
[ default_for(key, default, i),
extension_for(key, default, i) ]
end
# Skip the __exception__ for defexception.
def default_for(:__exception__, _default, _i) do
defp default_for(:__exception__, _default, _i) do
nil
end
# Define the default functions for each field.
def default_for(key, _default, i) do
bin_update = "update_" <> atom_to_binary(key)
update = binary_to_atom(bin_update)
defp default_for(key, _default, i) do
update = prefix("update_", key)
quote do
def unquote(update).(function, record) do
@@ -568,11 +571,9 @@ defmodule Record.Extensions do
end
end
# Define extensions based on the default type.
def extension_for(key, default, i) when is_list(default) do
bin_key = atom_to_binary(key)
prepend = :"prepend_#{bin_key}"
merge = :"merge_#{bin_key}"
defp extension_for(key, default, i) when is_list(default) do
prepend = prefix("prepend_", key)
merge = prefix("merge_", key)
quote do
def unquote(prepend).(value, record) do
@@ -587,9 +588,8 @@ defmodule Record.Extensions do
end
end
def extension_for(key, default, i) when is_number(default) do
bin_key = atom_to_binary(key)
increment = :"increment_#{bin_key}"
defp extension_for(key, default, i) when is_number(default) do
increment = prefix("increment_", key)
quote do
def unquote(increment).(value // 1, record) do
@@ -599,9 +599,8 @@ defmodule Record.Extensions do
end
end
def extension_for(key, default, i) when is_boolean(default) do
bin_key = atom_to_binary(key)
toggle = :"toggle_#{bin_key}"
defp extension_for(key, default, i) when is_boolean(default) do
toggle = prefix("toggle_", key)
quote do
def unquote(toggle).(record) do
@@ -611,5 +610,58 @@ defmodule Record.Extensions do
end
end
def extension_for(_, _, _), do: nil
defp extension_for(_, _, _), do: nil
# Specs definition
def specs_for(key, default, spec) do
[ default_specs_for(key, default, spec),
extension_specs_for(key, default, spec) ]
end
defp default_specs_for(:__exception__, _default, _spec) do
nil
end
defp default_specs_for(key, _default, spec) do
update = prefix("update_", key)
quote do
@spec unquote(update)(fun(unquote(spec), do: unquote(spec)), t), do: t
end
end
defp extension_specs_for(key, default, _spec) when is_list(default) do
prepend = prefix("prepend_", key)
merge = prefix("merge_", key)
quote do
@spec unquote(prepend)(list, t), do: t
@spec unquote(merge)(Keyword.t, t), do: t
end
end
defp extension_specs_for(key, default, _spec) when is_number(default) do
increment = prefix("increment_", key)
quote do
@spec unquote(increment)(number, t), do: t
end
end
defp extension_specs_for(key, default, _spec) when is_boolean(default) do
toggle = prefix("toggle_", key)
quote do
@spec unquote(toggle)(t), do: t
end
end
defp extension_specs_for(_, _, _), do: nil
# Helpers
defp prefix(prefix, key) do
binary_to_atom prefix <> atom_to_binary(key)
end
end
+79
View File
@@ -0,0 +1,79 @@
defmodule Record.Extractor do
@moduledoc false
# 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)
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
end
retrieve_record(name, realfile)
end
# 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))
case :code.lib_dir(to_char_list(app)) do
{ :error, _ } ->
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
libpath ->
retrieve_record name, :filename.join([libpath|path])
end
end
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
records = retrieve_from_file(file)
if record = List.keyfind(records, name, 0) do
parse_record(record)
else
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_from_file(file) do
lc { :attribute, _, :record, record } inlist read_file(file), do: record
end
# Read a file and return its abstract syntax form that also
# includes record and other preprocessor modules. This is done
# by using Erlang's epp_dodger.
defp read_file(file) do
case :epp_dodger.quick_parse_file(file) do
{ :ok, form } ->
form
other ->
raise "Error parsing file #{to_binary(file)}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of second order tuples where the first element
# is the field and the second is its default value.
defp parse_record({ _name, fields }) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = :erl_eval.expr(cons, [])
list
end
defp parse_field({ :typed_record_field, record_field, _type }) do
parse_field(record_field)
end
defp parse_field({ :record_field, _, key }) do
{ :tuple, 0, [key, {:atom, 0, :undefined}] }
end
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
end
+21 -46
View File
@@ -43,6 +43,9 @@ defmodule Regex do
a binary, a char list will return a char list).
"""
defrecordp :regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, term, term, term }
defexception CompileError, message: "regex could not be compiled"
@doc """
@@ -57,9 +60,10 @@ defmodule Regex do
opts = translate_options(options)
re_opts = opts -- [:groups]
groups = if opts != re_opts, do: parse_groups(source)
case :re.compile(source, re_opts) do
{ :ok, compiled } ->
{ :ok, { Regex, compiled, source, options, groups } }
{ :ok, re_pattern } ->
{ :ok, regex(re_pattern: re_pattern, source: source, options: options, groups: groups) }
error ->
error
end
@@ -87,7 +91,7 @@ defmodule Regex do
Regex.index %r/e/, "abcd" #=> nil
"""
def index({ Regex, compiled, _, _, _ }, string) do
def index(regex(re_pattern: compiled), string) do
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
@@ -103,7 +107,7 @@ defmodule Regex do
Regex.match? %r/foo/, "bar" #=> false
"""
def match?({ Regex, compiled, _, _, _ }, string) do
def match?(regex(re_pattern: compiled), string) do
:re.run(string, compiled, [{ :capture, :none }]) == :match
end
@@ -118,12 +122,13 @@ defmodule Regex do
"""
def run(regex, string, options // [])
def run({ Regex, compiled, _, _, groups }, string, options) do
def run(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise "Regex was not compiled with g"
:groups -> groups || raise "regex was not compiled with g"
others -> others
end
@@ -141,7 +146,7 @@ defmodule Regex do
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
"""
def captures({ Regex, _, _, _, groups } = regex, string, options // []) do
def captures(regex(groups: groups) = regex, string, options // []) do
unless captures = Keyword.get(options, :capture) do
captures = if groups do
List.sort(groups)
@@ -154,30 +159,10 @@ defmodule Regex do
if results, do: Enum.zip captures, results
end
@doc """
Returns a list with the match indexes in the given string.
The matches are tuples where the first element is the index
(zero indexed) the match happened and the second is the length
of the match.
## Examples
Regex.indexes %r/c(d)/, "abcd" #=> [{2,2},{3,1}]
Regex.indexes %r/e/, "abcd" #=> nil
"""
def indexes({ Regex, compiled, _, _, _ }, string) do
IO.write "[WARNING] Regex.indexes is deprecated, please use Regex.run with return: :index as option instead\n#{Exception.formatted_stacktrace}"
case :re.run(string, compiled, [{ :capture, :all, :index }]) do
:nomatch -> nil
{ :match, results } -> results
end
end
@doc """
Returns the underlying re_pattern in the regular expression.
"""
def re_pattern({ Regex, compiled, _, _, _ }) do
def re_pattern(regex(re_pattern: compiled)) do
compiled
end
@@ -189,7 +174,7 @@ defmodule Regex do
Regex.source %r(foo) #=> "foo"
"""
def source({ Regex, _, source, _, _ }) do
def source(regex(source: source)) do
source
end
@@ -201,8 +186,8 @@ defmodule Regex do
Regex.opts %r(foo)m #=> 'm'
"""
def opts({ Regex, _, _, opts, _ }) do
opts
def opts(regex(options: options)) do
options
end
@doc """
@@ -213,7 +198,7 @@ defmodule Regex do
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
"""
def groups({ Regex, _, _, _, groups }) do
def groups(regex(groups: groups)) do
groups
end
@@ -231,7 +216,8 @@ defmodule Regex do
"""
def scan(regex, string, options // [])
def scan({ Regex, compiled, _, _, _ }, string, options) do
def scan(regex(re_pattern: compiled), string, options) do
return = options[:return] || return_for(string)
options = [{ :capture, :all, return }, :global]
case :re.run(string, compiled, options) do
@@ -247,12 +233,7 @@ defmodule Regex do
def split(regex, string, options // [])
def split(regex, string, options) when is_integer(options) or is_atom(options) do
IO.write "[WARNING] Passing an integer or atom to Regex.split/3 is deprecated, pass a :parts option instead\n#{Exception.formatted_stacktrace}"
split(regex, string, parts: options)
end
def split({ Regex, compiled, _, _, _ }, string, options) do
def split(regex(re_pattern: compiled), string, options) do
parts =
cond do
options[:global] == false -> 2
@@ -283,19 +264,13 @@ defmodule Regex do
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
"""
def replace({ Regex, compiled, _, _, _ }, string, replacement, options // []) do
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
opts = if options[:global] != false, do: [:global], else: []
return = options[:return] || return_for(string)
opts = [{ :return, return }|opts]
:re.replace(string, compiled, replacement, opts)
end
@doc false
def replace_all({ Regex, compiled, _, _, _ }, string, replacement) do
IO.write "[WARNING] Regex.replace_all is deprecated, simply use Regex.replace instead\n#{Exception.formatted_stacktrace}"
:re.replace(string, compiled, replacement, [{ :return, return_for(string) }, :global])
end
# Helpers
@doc false
+105 -82
View File
@@ -1,13 +1,23 @@
defmodule String do
@moduledoc """
A string in Elixir is a utf-8 binary. This module
contains function to work with utf-8 data and its
codepoints.
contains function to work with utf-8 data, its
codepoints and graphemes.
Notice that graphemes is a superset of UTF-8 codepoints
which also contains named sequences as defined per
http://www.unicode.org/reports/tr34/. In short, graphemes
also contain multiple characters that are "perceived as
a single character" by readers.
For working with raw binaries, use Erlang's :binary
module.
"""
@type t :: binary
@type codepoint :: t
@type grapheme :: t
@doc """
Checks if a string is printable considering it is encoded
as UTF-8. Returns true if so, false otherwise.
@@ -17,7 +27,7 @@ defmodule String do
String.printable?("abc") #=> true
"""
@spec printable?(t), do: boolean
# Allow basic ascii chars
def printable?(<<c, t :: binary>>) when c in ?\s..?~ do
printable?(t)
@@ -90,7 +100,6 @@ defmodule String do
def printable?(<<>>), do: true
def printable?(_), do: false
@doc """
Divides a string into sub string based on a pattern,
returning a list of these sub string. The pattern can
@@ -116,6 +125,9 @@ defmodule String do
String.split("a,b", %r{\.}) #=> ["a,b"]
"""
@spec split(t), do: [t]
@spec split(t, t | [t] | Regex.t), do: [t]
@spec split(t, t | [t] | Regex.t, Keyword.t), do: [t]
def split(binary, pattern // " ", options // [])
def split(binary, pattern, options) when is_regex(pattern) do
@@ -128,7 +140,11 @@ defmodule String do
end
@doc """
Convert all characters on the given string to upper case.
Convert all characters on the given string to upcase.
This function relies on the simple uppercase mapping
available in Unicode 6.2.0, check http://unicode.org/reports/tr44/
for more information.
## Examples
@@ -137,22 +153,15 @@ defmodule String do
String.upcase("josé") #=> "JOSÉ"
"""
def upcase(<<>>), do: <<>>
def upcase(<<195, c, t :: binary>>) when c in 160..191 do
<<195, c - 32, upcase(t) :: binary>>
end
def upcase(<<c, t :: binary>>) when c in ?a..?z do
<<c - 32, upcase(t) :: binary>>
end
def upcase(<<c, t :: binary>>) do
<<c , upcase(t) :: binary>>
end
@spec upcase(t), do: t
defdelegate upcase(binary), to: String.Unicode
@doc """
Convert all characters on the given string to down case.
Convert all characters on the given string to downcase.
This function relies on the simple lowercase mapping
available in Unicode 6.2.0, check http://unicode.org/reports/tr44/
for more information.
## Examples
@@ -161,19 +170,8 @@ defmodule String do
String.downcase("JOSÉ") #=> "josé"
"""
def downcase(<<>>), do: <<>>
def downcase(<<195, c, t :: binary>>) when c in 128..159 do
<<195, c + 32, downcase(t) :: binary>>
end
def downcase(<<c, t :: binary>>) when c in ?A..?Z do
<<c + 32, downcase(t) :: binary>>
end
def downcase(<<c, t :: binary>>) do
<<c , downcase(t) :: binary>>
end
@spec downcase(t), do: t
defdelegate downcase(binary), to: String.Unicode
@doc """
Returns a string where trailing char have been
@@ -185,8 +183,12 @@ defmodule String do
String.rstrip(" abc _", ?_) #=> " abc "
"""
@spec rstrip(t), do: t
@spec rstrip(t, char), do: t
def rstrip(string, char // ?\s)
def rstrip("", _char), do: ""
# Do a quick check before we traverse the whole
# binary. :binary.last is a fast operation (it
# does not traverse the whole binary).
@@ -220,6 +222,8 @@ defmodule String do
String.lstrip("_ abc _", ?_) #=> " abc _"
"""
@spec lstrip(t), do: t
@spec lstrip(t, char), do: t
def lstrip(string, char // ?\s)
def lstrip(<<char, rest :: binary>>, char) do
@@ -240,6 +244,8 @@ defmodule String do
String.strip("a abc a", ?a) #=> " abc "
"""
@spec strip(t), do: t
@spec strip(t, char), do: t
def strip(string, char // ?\s) do
rstrip(lstrip(string, char), char)
end
@@ -263,6 +269,8 @@ defmodule String do
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
"""
@spec replace(t, t, t), do: t
@spec replace(t, t, t, Keyword.t), do: t
def replace(subject, pattern, replacement, options // []) do
opts = translate_replace_options(options)
:binary.replace(subject, pattern, replacement, opts)
@@ -287,6 +295,7 @@ defmodule String do
String.duplicate("abc", 2) #=> "abcabc"
"""
@spec duplicate(t, pos_integer), do: t
def duplicate(subject, n) when is_integer(n) and n > 0 do
:binary.copy(subject, n)
end
@@ -301,18 +310,51 @@ defmodule String do
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
"""
def codepoints(string) do
do_codepoints(codepoint(string))
end
defp do_codepoints({char, rest}) do
[char|do_codepoints(codepoint(rest))]
end
defp do_codepoints(:no_codepoint), do: []
@spec codepoints(t), do: [codepoint]
defdelegate codepoints(string), to: String.Unicode
@doc """
Returns the first codepoint from an utf8 string.
Returns the next codepoint in a String.
The result is a tuple with the codepoint and the
remaining of the string or `:no_codepoint` in case
the String reached its end.
## Examples
String.next_codepoint("josé") #=> { "j", "osé" }
"""
@spec next_codepoint(t), do: codepoint | :no_codepoint
defdelegate next_codepoint(string), to: String.Unicode
@doc """
Returns unicode graphemes in the string
## Examples
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
"""
@spec graphemes(t), do: [grapheme]
defdelegate graphemes(string), to: String.Unicode
@doc """
Returns the next grapheme in a String.
The result is a tuple with the grapheme and the
remaining of the string or `:no_grapheme` in case
the String reached its end.
## Examples
String.next_grapheme("josé") #=> { "j", "osé" }
"""
@spec next_grapheme(t), do: grapheme | :no_grapheme
defdelegate next_grapheme(string), to: String.Unicode
@doc """
Returns the first grapheme from an utf8 string.
## Examples
@@ -320,15 +362,16 @@ defmodule String do
String.first("եոգլի") #=> "ե"
"""
@spec first(t), do: grapheme
def first(string) do
case codepoint(string) do
case next_grapheme(string) do
{ char, _ } -> char
:no_codepoint -> ""
:no_grapheme -> ""
end
end
@doc """
Returns the last codepoint from an utf8 string.
Returns the last grapheme from an utf8 string.
## Examples
@@ -336,18 +379,19 @@ defmodule String do
String.last("եոգլի") #=> "ի"
"""
@spec last(t), do: grapheme
def last(string) do
do_last(codepoint(string), "")
do_last(next_grapheme(string), "")
end
defp do_last({char, rest}, _) do
do_last(codepoint(rest), char)
do_last(next_grapheme(rest), char)
end
defp do_last(:no_codepoint, last_char), do: last_char
defp do_last(:no_grapheme, last_char), do: last_char
@doc """
Returns the number of codepoint in an utf8 string.
Returns the number of unicode graphemes in an utf8 string.
## Examples
@@ -355,18 +399,19 @@ defmodule String do
String.length("եոգլի") #=> 5
"""
@spec length(t), do: non_neg_integer
def length(string) do
do_length(codepoint(string))
do_length(next_grapheme(string))
end
defp do_length({_, rest}) do
1 + do_length(codepoint(rest))
1 + do_length(next_grapheme(rest))
end
defp do_length(:no_codepoint), do: 0
defp do_length(:no_grapheme), do: 0
@doc """
Returns the codepoint in the `position` of the given utf8 `string`.
Returns the grapheme in the `position` of the given utf8 `string`.
If `position` is greater than `string` length, than it returns `nil`.
## Examples
@@ -375,51 +420,29 @@ defmodule String do
String.at("elixir", 1) #=> "l"
String.at("elixir", 10) #=> nil
String.at("elixir", -1) #=> "r"
String.at("elixir", -10) #=> "nil"
String.at("elixir", -10) #=> nil
"""
@spec at(t, integer), do: grapheme | nil
def at(string, position) when position >= 0 do
do_at(codepoint(string), position, 0)
do_at(next_grapheme(string), position, 0)
end
def at(string, position) when position < 0 do
real_pos = do_length(codepoint(string)) - abs(position)
real_pos = do_length(next_grapheme(string)) - abs(position)
case real_pos >= 0 do
true -> do_at(codepoint(string), real_pos, 0)
true -> do_at(next_grapheme(string), real_pos, 0)
false -> ""
end
end
defp do_at({_ , rest}, desired_pos, current_pos) when desired_pos > current_pos do
do_at(codepoint(rest), desired_pos, current_pos + 1)
do_at(next_grapheme(rest), desired_pos, current_pos + 1)
end
defp do_at({char, _}, desired_pos, current_pos) when desired_pos == current_pos do
char
end
defp do_at(:no_codepoint, _, _), do: ""
# Private implementation which returns the first codepoint
# of any given utf8 string and the rest of it
# If an empty string is given, :no_codepoint is returned.
defp codepoint(<<194, char, rest :: binary>>)
when char in 161..191,
do: { <<194, char>>, rest }
defp codepoint(<<first, char, rest :: binary>>)
when first in 195..223 and char in 128..191,
do: { <<first, char>>, rest }
defp codepoint(<<first, second, char, rest :: binary>>)
when first == 224 and second in 160..191 and char in 128..191,
do: { <<first, second, char>>, rest }
defp codepoint(<<first, second, char, rest :: binary>>)
when first in 225..239 and second in 128..191 and char in 128..191,
do: { <<first, second, char>>, rest }
defp codepoint(<<other, rest :: binary>>), do: { <<other>>, rest }
defp codepoint(<<>>), do: :no_codepoint
defp do_at(:no_grapheme, _, _), do: nil
end
+50 -1
View File
@@ -24,7 +24,7 @@ defmodule System do
@doc """
Returns Elixir's version as binary.
"""
def version, do: "0.7.0"
def version, do: "0.7.1"
@doc """
Returns a keywords list with version, git tag info and date.
@@ -138,6 +138,55 @@ defmodule System do
filter_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.
* If an integer, the runtime system exits with the integer value which
is returned to the Operating System;
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system;
* If a char list, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1;
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
## Examples
System.halt(0)
System.halt(1, flush: false)
System.halt(:abort)
"""
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]), do: no_return
def halt(status // 0, options // [])
def halt(status, options) when is_integer(status) or status == :abort do
do_halt(status, options)
end
def halt(status, options) do
do_halt(to_char_list(status), options)
end
# Support R15B
if List.member?(:erlang.module_info(:exports), { :halt, 2 }) do
defp do_halt(status, options), do: :erlang.halt(status, options)
else
IO.puts "Using limited halt support. Upgrade to R15B01 or later is recommended."
defp do_halt(status, _options), do: :erlang.halt(status)
end
## Helpers
# Filter stacktrace by removing internal BOOTSTRAP calls.
+2 -2
View File
@@ -9,10 +9,10 @@ defmodule URI.Parser do
@doc """
Responsible for parsing extra URL information.
"""
defcallback parse(uri_info)
defcallback parse(uri_info :: URI.Info.t), do: URI.Info.t
@doc """
Responsible for returning the default port.
"""
defcallback default_port()
defcallback default_port(), do: integer
end
+420
View File
@@ -0,0 +1,420 @@
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
File diff suppressed because it is too large Load Diff
+136
View File
@@ -0,0 +1,136 @@
# This file has its compilation step because
# it needs to parse String.Unicode data and
# compile a digested module.
defmodule String.Unicode do
@moduledoc false
def version, do: {6,2,0}
to_binary = fn(codepoint) ->
:unicode.characters_to_binary([binary_to_integer(codepoint, 16)])
end
data_path = File.expand_path("../UnicodeData.txt", __FILE__)
codes = Enum.reduce File.iterator!(data_path), [], fn(line, acc) ->
[ codepoint, _name, _category,
_class, _bidi, _decomposition,
_numeric_1, _numeric_2, _numeric_3,
_bidi_mirror, _unicode_1, _iso,
upper, lower, _title ] = :binary.split(line, ";", [:global])
if upper != "" or lower != "" do
[{ to_binary.(codepoint), upper, lower }|acc]
else
acc
end
end
seqs_path = File.expand_path("../NamedSequences.txt", __FILE__)
seqs = Enum.map(File.iterator!(seqs_path), fn(line) ->
[ _name, codepoints ] = :binary.split(line, ";", [:global])
codepoints = Enum.filter(:binary.split(codepoints, " ", [:global, :trim]),
fn(x) -> size(x) > 0 end)
Enum.map(codepoints, fn(x) -> to_binary.(x) end)
end)
# Downcase
lc { codepoint, _upper, lower } inlist codes, lower != "" do
lower = to_binary.(lower)
args = quote do: [unquote(codepoint) <> t]
code = quote do: unquote(lower) <> downcase(t)
def :downcase, args, [], do: code
end
def downcase(<< h, t :: binary >>) do
<< h >> <> downcase(t)
end
def downcase(<< >>) do
<< >>
end
# Upcase
lc { codepoint, upper, _lower } inlist codes, upper != "" do
upper = to_binary.(upper)
args = quote do: [unquote(codepoint) <> t]
code = quote do: unquote(upper) <> upcase(t)
def :upcase, args, [], do: code
end
def upcase(<< h, t :: binary >>) do
<< h >> <> upcase(t)
end
def upcase(<< >>) do
<< >>
end
# Graphemes
lc codepoints inlist seqs do
seq_args = quote do: [<< unquote_splicing(codepoints), t :: binary >>]
seq_code = quote do: {<< unquote_splicing(codepoints) >>, t}
def :next_grapheme, seq_args, [], do: seq_code
end
def next_grapheme(<<>>) do
:no_grapheme
end
def next_grapheme(binary) when is_binary(binary) do
case next_codepoint(binary) do
:no_codepoint -> :no_grapheme
other -> other
end
end
def graphemes(binary) when is_binary(binary) do
do_graphemes(next_grapheme(binary))
end
defp do_graphemes({ c, rest }) do
[c|do_graphemes(next_grapheme(rest))]
end
defp do_graphemes(:no_grapheme) do
[]
end
# Codepoints
def next_codepoint(<<194, char, rest :: binary>>)
when char in 161..191,
do: { <<194, char>>, rest }
def next_codepoint(<<first, char, rest :: binary>>)
when first in 195..223 and char in 128..191,
do: { <<first, char>>, rest }
def next_codepoint(<<first, second, char, rest :: binary>>)
when first == 224 and second in 160..191 and char in 128..191,
do: { <<first, second, char>>, rest }
def next_codepoint(<<first, second, char, rest :: binary>>)
when first in 225..239 and second in 128..191 and char in 128..191,
do: { <<first, second, char>>, rest }
def next_codepoint(<<other, rest :: binary>>), do: { <<other>>, rest }
def next_codepoint(<<>>), do: :no_codepoint
def codepoints(binary) when is_binary(binary) do
do_codepoints(next_codepoint(binary))
end
defp do_codepoints({ c, rest }) do
[c|do_codepoints(next_codepoint(rest))]
end
defp do_codepoints(:no_codepoint) do
[]
end
end
+1 -1
View File
@@ -16,7 +16,7 @@
%% warn_missing_spec,
%% warn_untyped_record,
%% warnings_as_errors,
no_debug_info
debug_info
]}.
{yrl_opts, [
+39 -12
View File
@@ -7,6 +7,9 @@
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Top level types
-export_type([char_list/0]).
-type char_list() :: string().
% OTP APPLICATION API
@@ -50,17 +53,40 @@ start_cli() ->
%% EVAL HOOKS
scope_for_eval(Opts) ->
File = case lists:keyfind(file, 1, Opts) of
{ file, F } -> to_binary(F);
false -> <<"nofile">>
case lists:keyfind(file, 1, Opts) of
{ file, RawFile } -> File = to_binary(RawFile);
false -> File = <<"nofile">>
end,
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, L } -> L;
false -> nil
case lists:keyfind(delegate_locals_to, 1, Opts) of
{ delegate_locals_to, Local } -> Local;
false -> Local = nil
end,
#elixir_scope{file=File,local=Local}.
case lists:keyfind(aliases, 1, Opts) of
{ aliases, Aliases } -> Aliases;
false -> Aliases = []
end,
case lists:keyfind(requires, 1, Opts) of
{ requires, Requires } -> Requires;
false -> Requires = elixir_dispatch:default_requires()
end,
case lists:keyfind(functions, 1, Opts) of
{ functions, Functions } -> Functions;
false -> Functions = elixir_dispatch:default_functions()
end,
case lists:keyfind(macros, 1, Opts) of
{ macros, Macros } -> Macros;
false -> Macros = elixir_dispatch:default_macros()
end,
#elixir_scope{
file=File, local=Local,
macros=Macros, functions=Functions,
requires=Requires, aliases=Aliases }.
%% String evaluation
@@ -100,8 +126,8 @@ eval_forms(Tree, Binding, RawScope) ->
vars=binding_dict(Binding),
temp_vars=[],
quote_vars=[],
clause_vars=[],
counter=0
clause_vars=nil,
counter=[]
},
{ ParseTree, NewScope } = elixir_translator:translate(Tree, Scope),
case ParseTree of
@@ -122,9 +148,10 @@ binding_dict([], Dict) -> Dict.
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
case atom_to_list(Var) of
"_@" ++ _ -> final_binding(T, Acc, Binding, Vars);
_ ->
case lists:member($@, atom_to_list(Var)) of
true ->
final_binding(T, Acc, Binding, Vars);
false ->
RealName = orddict:fetch(Var, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
+10 -3
View File
@@ -87,9 +87,12 @@ extract_last_guards(Args) ->
% Function for translating macros with match style like case and receive.
match(Line, DecoupledClauses, RawS) ->
S = RawS#elixir_scope{clause_vars=orddict:new()},
match(Line, Clauses, #elixir_scope{clause_vars=C1} = S) ->
{ TC, TS } = do_match(Line, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
C2 = TS#elixir_scope.clause_vars,
{ TC, TS#elixir_scope{clause_vars=elixir_scope:merge_clause_vars(C1, C2)} }.
do_match(Line, DecoupledClauses, S) ->
case DecoupledClauses of
[DecoupledClause] ->
{ TDecoupledClause, TS } = each_clause(Line, DecoupledClause, S),
@@ -207,7 +210,11 @@ normalize_vars({ Var, Kind }, S) ->
normalize_vars(Var, Kind, Index, #elixir_scope{clause_vars=ClauseVars} = S) ->
Vars = element(Index, S),
{ { _, _, NewValue }, S1 } = elixir_scope:build_erl_var(0, S),
{ { _, _, NewValue }, S1 } = if
(Kind == quoted) or (S#elixir_scope.noname) -> elixir_scope:build_erl_var(0, S);
true -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
end,
S2 = setelement(Index, S1, orddict:store(Var, NewValue, Vars)),
S3 = S2#elixir_scope{clause_vars=orddict:store({ Var, Kind }, NewValue, ClauseVars)},
+17 -1
View File
@@ -6,6 +6,8 @@
argv=[],
loaded=[],
at_exit=[],
pool=[],
counter=0,
compiler_options=[{docs,true}]
}).
@@ -51,6 +53,15 @@ handle_call(argv, _From, Config) ->
handle_call(compiler_options, _From, Config) ->
{ reply, Config#elixir_code_server.compiler_options, 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} }
end;
handle_call(_Request, _From, Config) ->
{ reply, undef, Config }.
@@ -73,6 +84,9 @@ 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(_Request, Config) ->
{ noreply, Config }.
@@ -85,4 +99,6 @@ terminate(Reason, Config) ->
ok.
code_change(_Old, Config, _Extra) ->
{ ok, Config }.
{ ok, Config }.
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
+36 -53
View File
@@ -1,6 +1,6 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/5]).
-export([core/0, module/3, eval_forms/4]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -27,7 +27,7 @@ string(Contents, File) when is_list(Contents), is_binary(File) ->
try
put(elixir_compiled, []),
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
eval_forms(Forms, 1, File, [], #elixir_scope{file=File}),
eval_forms(Forms, 1, File, [], elixir:scope_for_eval([{file,File}])),
lists:reverse(get(elixir_compiled))
after
put(elixir_compiled, Previous)
@@ -49,16 +49,16 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
%% Evaluates the contents/forms by compiling them to an Erlang module.
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=nil} = S) ->
eval_forms(Forms, Line, Module, nil, Vars, S);
eval_forms(Forms, Line, Vars, #elixir_scope{module=nil} = S) ->
eval_forms(Forms, Line, nil, Vars, S);
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=Value} = S) ->
eval_forms(Forms, Line, Module, Value, Vars, S).
eval_forms(Forms, Line, Vars, #elixir_scope{module=Value} = S) ->
eval_forms(Forms, Line, Value, Vars, S).
eval_forms(Forms, Line, RawModule, Value, Vars, S) ->
eval_forms(Forms, Line, Value, Vars, S) ->
case (Value == nil) andalso allows_fast_compilation(Forms) of
true -> eval_compilation(Forms, Vars, S);
false -> code_loading_compilation(Forms, Line, RawModule, Value, Vars, S)
false -> code_loading_compilation(Forms, Line, Value, Vars, S)
end.
eval_compilation(Forms, Vars, S) ->
@@ -66,17 +66,20 @@ eval_compilation(Forms, Vars, S) ->
{ Result, _Binding, FS } = elixir:eval_forms(Forms, [{'_@MODULE',nil}|Binding], S),
{ Result, FS }.
code_loading_compilation(Forms, Line, RawModule, Value, Vars, S) ->
Module = escape_module(RawModule),
code_loading_compilation(Forms, Line, Value, Vars, S) ->
{ Module, I } = retrieve_module_name(),
{ Exprs, FS } = elixir_translator:translate(Forms, S),
ModuleForm = module_form(Exprs, Line, S#elixir_scope.file, Module, Vars),
Args = [X || { _, _, X } <- Vars],
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(Mod, _) ->
Res = Mod:'BOOTSTRAP'(Value, Args),
code:purge(Module),
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(_, _) ->
Res = Module:'BOOTSTRAP'(Value, Args),
code:delete(Module),
case code:soft_purge(Module) of
true -> return_module_name(I);
false -> ok
end,
Res
end), FS }.
@@ -110,17 +113,14 @@ module(Forms, File, Options, Bootstrap, Callback) when
core() ->
elixir:start_app(),
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()],
AllLists = [filelib:wildcard(Wildcard) || Wildcard <- core_list()],
Files = lists:append(AllLists) -- core_main(),
[core_file(File) || File <- 'Elixir.List':uniq(Files)].
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true},{debug_info,true}] }),
[core_file(File) || File <- core_main()].
%% HELPERS
no_auto_import() ->
{ attribute, 0, compile, {
no_auto_import, erlang:module_info(exports) } }.
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
{ attribute, 0, compile, { no_auto_import, Bifs } }.
module_form(Exprs, Line, File, Module, Vars) when
is_binary(File), is_list(Exprs), is_integer(Line), is_atom(Module) ->
@@ -146,25 +146,13 @@ allows_fast_compilation([{defmodule,_,_}|T]) -> allows_fast_compilation(T);
allows_fast_compilation([]) -> true;
allows_fast_compilation(_) -> false.
%% Escape the module name, removing slashes, dots,
%% so it can be loaded by Erlang.
%% Generate module names from code server.
escape_module(Module) when is_atom(Module) ->
escape_module(atom_to_list(Module));
retrieve_module_name() ->
gen_server:call(elixir_code_server, retrieve_module_name).
escape_module(Module) when is_binary(Module) ->
escape_module(binary_to_list(Module));
escape_module(Module) when is_list(Module) ->
list_to_atom(escape_each(Module)).
escape_each([H|T]) when H >= $A, H =< $Z; H >= $a, H =< $z; H >= $0, H =< $9; H == $- ->
[H|escape_each(T)];
escape_each([_|T]) ->
[$_|escape_each(T)];
escape_each([]) -> [].
return_module_name(I) ->
gen_server:cast(elixir_code_server, { return_module_name, I }).
%% Receives a module Binary and outputs it in the given path.
@@ -186,29 +174,17 @@ core_file(File) ->
exit(1)
end.
core_list() ->
[
"lib/elixir/lib/range.ex",
"lib/elixir/lib/behaviour.ex",
"lib/elixir/lib/uri/parser.ex",
"lib/elixir/lib/elixir/formatter.ex",
"lib/elixir/lib/dict.ex",
"lib/elixir/lib/dict/common.ex",
"lib/elixir/lib/access.ex",
"lib/elixir/lib/range.ex",
"lib/elixir/lib/*/*.ex",
"lib/elixir/lib/*.ex"
].
core_main() ->
[
"lib/elixir/lib/kernel.ex",
"lib/elixir/lib/keyword.ex",
"lib/elixir/lib/list.ex",
"lib/elixir/lib/kernel/typespec.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/record/extractor.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/list.ex",
"lib/elixir/lib/code.ex",
"lib/elixir/lib/protocol.ex",
"lib/elixir/lib/enum.ex",
@@ -216,7 +192,14 @@ core_main() ->
"lib/elixir/lib/binary/inspect.ex",
"lib/elixir/lib/binary/chars.ex",
"lib/elixir/lib/list/chars.ex",
"lib/elixir/lib/gen_server/behaviour.ex"
"lib/elixir/lib/io.ex",
"lib/elixir/lib/file.ex",
"lib/elixir/lib/access.ex",
"lib/elixir/lib/regex.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"
].
%% ERROR HANDLING
+13 -13
View File
@@ -6,7 +6,7 @@
reset_last/1,
wrap_definition/7,
store_definition/8,
store_each/8,
store_each/7,
unwrap_stored_definitions/1,
format_error/1]).
-include("elixir.hrl").
@@ -19,7 +19,7 @@ table(Module) -> ?ELIXIR_ATOM_CONCAT([f, Module]).
build_table(Module) ->
FunctionTable = table(Module),
ets:new(FunctionTable, [set, named_table, public]),
ets:insert(FunctionTable, { last, [] }),
reset_last(Module),
FunctionTable.
delete_table(Module) ->
@@ -81,7 +81,6 @@ store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
File = TS#elixir_scope.file,
Table = table(Module),
Stack = TS#elixir_scope.macro,
%% Store function
if
@@ -90,10 +89,10 @@ store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
compile_super(Module, TS),
CheckClauses = S#elixir_scope.check_clauses,
store_each(CheckClauses, Kind, File, Location,
Stack, Table, length(Defaults), Function)
Table, length(Defaults), Function)
end,
[store_each(false, Kind, File, Location, Stack, Table, 0,
[store_each(false, Kind, File, Location, Table, 0,
function_for_default(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity }.
@@ -215,9 +214,10 @@ unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Fu
);
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, Exports, [element(1, Fun)|Private], Def, Defmacro,
[{ element(1, Fun), element(3, Fun) }|Defmacrop], Functions
T, Exports, [Tuple|Private], Def, Defmacro,
[{ Tuple, element(3, Fun), element(5, Fun) }|Defmacrop], Functions
);
unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
@@ -226,13 +226,13 @@ unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functi
%% Helpers
function_for_stored_definition({{Name, Arity}, _, Line, _, [], _, _, Clauses}, {Functions,Tail}) ->
function_for_stored_definition({{Name, Arity}, _, Line, _, _, [], _, Clauses}, {Functions,Tail}) ->
{
[{ function, Line, Name, Arity, lists:reverse(Clauses) }|Functions],
Tail
};
function_for_stored_definition({{Name, Arity}, _, Line, _, Location, _, _, Clauses}, {Functions,Tail}) ->
function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Clauses}, {Functions,Tail}) ->
{
Functions,
[
@@ -252,22 +252,22 @@ function_for_default(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Stack, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
store_each(Check, Kind, File, Location, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
case ets:lookup(Table, {Name, Arity}) of
[{{Name, Arity}, StoredKind, _, _, StoredLocation, StoredStack, StoredDefaults, StoredClauses}] ->
[{{Name, Arity}, StoredKind, _, _, StoredCheck, StoredLocation, StoredDefaults, StoredClauses}] ->
FinalLocation = StoredLocation,
FinalDefaults = Defaults + StoredDefaults,
FinalClauses = Clauses ++ StoredClauses,
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
Check andalso (Stack == StoredStack) andalso check_valid_clause(Line, File, Name, Arity, Table);
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
[] ->
FinalLocation = Location,
FinalDefaults = Defaults,
FinalClauses = Clauses,
Check andalso ets:insert(Table, { last, { Name, Arity } })
end,
ets:insert(Table, {{Name, Arity}, Kind, Line, File, FinalLocation, Stack, FinalDefaults, FinalClauses}).
ets:insert(Table, {{Name, Arity}, Kind, Line, File, Check, FinalLocation, FinalDefaults, FinalClauses}).
%% Validations
+3 -4
View File
@@ -15,8 +15,7 @@ unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = build_match(Acc, Line, []),
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
SM = S#elixir_scope{counter=length(Base)},
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, SM),
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, S),
{ InvokeArgs, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
Call = { call, Line,
@@ -39,7 +38,7 @@ extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInv
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
H = { ?ELIXIR_ATOM_CONCAT(["_@", Counter]), Line, nil },
H = { ?ELIXIR_ATOM_CONCAT(["_@D", Counter]), Line, nil },
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
@@ -50,7 +49,7 @@ extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
build_match([], _Line, Acc) -> Acc;
build_match([_|T], Line, Acc) ->
Var = { ?ELIXIR_ATOM_CONCAT(["_@", length(T)]), Line, nil },
Var = { ?ELIXIR_ATOM_CONCAT(["_@D", length(T)]), Line, nil },
build_match(T, Line, [Var|Acc]).
% Given the invoked function name based on the kind
+3 -2
View File
@@ -69,9 +69,10 @@ rewrite_clause(Else, _) -> Else.
%% Error handling
check_unused_local_macros(File, Recorded, PMacros) ->
check_unused_local_macros(File, Recorded, Defmacrop) ->
[elixir_errors:handle_file_warning(File,
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line } <- PMacros, not lists:member(Fun, Recorded)].
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line, Check } <- Defmacrop,
Check, not lists:member(Fun, Recorded)].
format_error({unused_macro,{Name, Arity}}) ->
io_lib:format("macro ~s/~B is unused", [Name, Arity]).
+17 -15
View File
@@ -16,7 +16,7 @@ overridable(Module, Value) ->
is_defined(Module, Tuple) ->
Overridable = overridable(Module),
case orddict:find(Tuple, Overridable) of
{ ok, { _, [_|_] } } -> true;
{ ok, { _, _, _ } } -> true;
_ -> false
end.
@@ -53,37 +53,39 @@ name(Module, Function) ->
name(Module, Function, overridable(Module)).
name(_Module, { Name, _ } = Function, Overridable) ->
{ Count, _ } = orddict:fetch(Function, Overridable),
{ Count, _, _ } = orddict:fetch(Function, Overridable),
?ELIXIR_ATOM_CONCAT([Name, " (overridable ", Count, ")"]).
%% Store
store(Module, Function, GenerateName) ->
Overridable = overridable(Module),
{ Count, [H|T] } = orddict:fetch(Function, Overridable),
overridable(Module, orddict:store(Function, { Count, T }, Overridable)),
case orddict:fetch(Function, Overridable) of
{ _Count, _Clause, true } -> ok;
{ Count, Clause, false } ->
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
{ { Name, Arity }, Kind, Line, File, _Check, Location, Defaults, Clauses } = Clause,
{ { Name, Arity }, Kind, Line, File, Location, Stack, Defaults, Clauses } = H,
{ FinalKind, FinalName } = case GenerateName of
true -> { defp, name(Module, Function, Overridable) };
false -> { Kind, Name }
end,
{ FinalKind, FinalName } = case GenerateName of
true -> { defp, name(Module, Function, Overridable) };
false -> { Kind, Name }
end,
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, File, Location,
Stack, elixir_def:table(Module), Defaults, Def).
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), Defaults, Def)
end.
%% Store pending declarations that were not manually made concrete.
store_pending(Module) ->
[store(Module, X, false) || { X, { _, [_|_] } } <- overridable(Module),
[store(Module, X, false) || { X, { _, _, false } } <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)].
%% Error handling
format_error({ no_super, Module, { Name, Arity } }) ->
Bins = [ format_fa(X) || { X, { _, [_|_] } } <- overridable(Module)],
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
[Name, Arity, elixir_errors:inspect(Module), Joined]).
+9 -10
View File
@@ -59,8 +59,8 @@ dispatch_import(Line, Name, Args, S, Callback) ->
{ error, internal } ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
elixir_macros:translate({ Name, Line, Args }, S);
{ ok, Receiver, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
{ ok, _Receiver, Tree } ->
translate_expansion(Line, Tree, S)
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
@@ -87,7 +87,7 @@ dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
{ error, internal } ->
elixir_macros:translate({ Name, Line, Args }, S);
{ ok, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
translate_expansion(Line, Tree, S)
end
end.
@@ -167,10 +167,12 @@ expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv).
translate_expansion(Line, Tree, Receiver, Name, Arity, S) ->
NewS = S#elixir_scope{macro=[{Line,Receiver,Name,Arity}|S#elixir_scope.macro]},
{ TTree, TS } = elixir_translator:translate_each(elixir_quote:linify(Line, Tree), NewS),
{ TTree, TS#elixir_scope{macro=S#elixir_scope.macro} }.
translate_expansion(Line, Tree, S) ->
{ TR, TS } = elixir_translator:translate_each(
elixir_quote:linify(Line, Tree),
S#elixir_scope{check_clauses=false}
),
{ TR, TS#elixir_scope{check_clauses=S#elixir_scope.check_clauses} }.
%% Helpers
@@ -268,9 +270,6 @@ in_erlang_functions() ->
{ exit, 1 },
{ float, 1 },
{ float_to_list, 1 },
{ halt, 0 },
{ halt, 1 },
{ halt, 2 },
{ hd, 1 },
{ integer_to_list, 1 },
{ integer_to_list, 2 },
+23 -5
View File
@@ -26,6 +26,9 @@ to_dot(L) -> L.
%% Raised during macros translation.
-spec syntax_error(non_neg_integer(), file:filename(), binary() | string()) -> no_return().
-spec syntax_error(non_neg_integer(), file:filename(), binary() | string(), list()) -> no_return().
syntax_error(Line, File, Message) when is_list(Message) ->
syntax_error(Line, File, iolist_to_binary(Message));
@@ -38,6 +41,8 @@ syntax_error(Line, File, Format, Args) ->
%% Raised on tokenizing/parsing
-spec parse_error(non_neg_integer(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
parse_error(Line, File, Error, []) ->
Message = case Error of
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
@@ -64,6 +69,8 @@ parse_error(Line, File, Error, Token) ->
%% Raised during compilation
-spec form_error(non_neg_integer(), file:filename(), module(), any()) -> no_return().
form_error(Line, File, Module, Desc) ->
Message = iolist_to_binary(format_error(Module, Desc)),
raise(Line, File, 'Elixir.CompileError', Message).
@@ -95,13 +102,19 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
io:format(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
Raw = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
case elixir_compiler:get_opt(internal) of
true -> [];
false ->
Message = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
io:format(file_format(Line, File, Message))
end;
Message = case erlang:function_exported(Module, behavior_info, 1) of
true -> Raw ++ " (maybe you meant behaviour_info instead of behavior_info?)";
false -> Raw
end,
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
io:format(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 }),
io:format(file_format(Line, File, Message));
%% Default behavior
@@ -112,6 +125,8 @@ 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().
handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc).
@@ -151,6 +166,9 @@ raise(Line, File, Kind, Message) ->
file_format(Line, File, Message) ->
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
format_var(Var) ->
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
format_error([], Desc) ->
io_lib:format("~p", [Desc]);
+2 -2
View File
@@ -236,10 +236,10 @@ format_error({import_conflict,{Receiver, Name, Arity}}) ->
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({local_conflict,{_, Name, Arity}}) ->
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir internal macros", [Name, Arity]);
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir special forms", [Name, Arity]);
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir internal macros",
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir special forms",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({ no_macros, Module }) ->
-44
View File
@@ -114,28 +114,6 @@ build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Line, S, Acc) ->
{ Size, Types } = extract_bit_values(Line, V, ES),
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Size, Types }|Acc]);
%% Deprecated syntax
build_bitstr_each(Fun, [{'|',_,[H,V]}|T], Line, S, Acc) ->
{ Expr, NS } = Fun(H, S),
%% Just variables defined outside the binary can be accounted on subparts
case NS of
{ ES, _ } -> [];
ES -> []
end,
elixir_errors:deprecation(Line, ES#elixir_scope.file, "Old bitstring syntax with | is deprecated, use the new one with :: instead"),
%% Assigns can be made in subparts
{ Int, Types } = extract_bin_values(Line, V, default, [], ES#elixir_scope{context=nil}),
Final = case Types of
[] -> default;
_ -> lists:reverse(Types)
end,
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Int, Final }|Acc]);
build_bitstr_each(Fun, [H|T], Line, S, Acc) ->
{ Expr, NS } = Fun(H, S),
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, default, default }|Acc]).
@@ -221,25 +199,3 @@ handle_unknown_specifier(Line, [H|T], Size, Types, S) ->
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
join_expansion(Expanded, Tail) -> [Expanded|Tail].
%% Deprecated specifiers
extract_bin_values(Line, { '-', _Line, [Left, Right] }, Int, Types, S) ->
{ LInt, LTypes } = extract_bin_values(Line, Left, Int, Types, S),
extract_bin_values(Line, Right, LInt, LTypes, S);
extract_bin_values(Line, Value, default, Types, _S) when is_integer(Value) ->
{ { integer, Line, Value }, Types };
extract_bin_values(_Line, { Value, _, Atom } = Expr, default, Types, S) when is_atom(Value), is_atom(Atom) ->
Translated = element(1, elixir_translator:translate_each(Expr, S)),
{ Translated, Types };
extract_bin_values(Line, Value, _Int, _Types, S) when is_integer(Value) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "duplicated size specifier ~p in <<>>", [Value]);
extract_bin_values(_Line, Value, Int, Types, _S) when is_atom(Value); is_tuple(Value), tuple_size(Value) == 2 ->
{ Int, [Value|Types] };
extract_bin_values(Line, _Value, _Int, _Types, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid specifier for <<>>").
+45 -22
View File
@@ -8,7 +8,9 @@
assert_no_function_scope/3, assert_module_scope/3, assert_no_assign_or_guard_scope/3]).
-include("elixir.hrl").
-define(FUNS(), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-define(FUNS(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-define(IN_TYPES(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
-compile({parse_transform, elixir_transform}).
%% Operators
@@ -34,11 +36,11 @@ translate({ Op, Line, Exprs }, S) when is_list(Exprs),
translate({ '!', Line, [{ '!', _, [Expr] }] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
{ elixir_tree_helpers:convert_to_boolean(Line, TExpr, true, S#elixir_scope.context == guard), SE };
elixir_tree_helpers:convert_to_boolean(Line, TExpr, true, S#elixir_scope.context == guard, SE);
translate({ '!', Line, [Expr] }, S) ->
{ TExpr, SE } = translate_each(Expr, S),
{ elixir_tree_helpers:convert_to_boolean(Line, TExpr, false, S#elixir_scope.context == guard), SE };
elixir_tree_helpers:convert_to_boolean(Line, TExpr, false, S#elixir_scope.context == guard, SE);
translate({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
{ _, TExpr, TS } = translate_in(Line, Left, Right, S),
@@ -78,7 +80,7 @@ translate({ function, Line, [_,_,_] = Args }, S) when is_list(Args) ->
%% @
translate({'@', Line, [{ Name, _, Args }]}, S) when Name == typep; Name == type; Name == spec; Name == callback ->
translate({'@', Line, [{ Name, _, Args }]}, S) when is_list(Args) andalso (Name == typep orelse Name == type orelse Name == spec orelse Name == callback orelse Name == opaque) ->
case elixir_compiler:get_opt(internal) of
true -> { { nil, Line }, S };
false ->
@@ -205,23 +207,24 @@ translate({defmodule, Line, [Ref, KV]}, S) ->
{ TRef, S }
end,
{ elixir_module:translate(Line, FRef, Block, S), FS };
{ elixir_module:translate(Line, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
translate({Kind, Line, [Call]}, S) when ?FUNS() ->
translate({Kind, Line, [Call]}, S) when ?FUNS(Kind) ->
translate({Kind, Line, [Call, nil]}, S);
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Line, Kind, S),
assert_no_function_scope(Line, Kind, S),
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
{ Name, Args } = elixir_clauses:extract_args(TCall),
assert_no_aliases_name(Line, Name, Args, S),
TName = elixir_tree_helpers:abstract_syntax(Name),
TArgs = elixir_tree_helpers:abstract_syntax(Args),
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, S), S };
translate({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS() ->
translate({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
assert_module_scope(Line, Kind, S),
assert_no_function_scope(Line, Kind, S),
{ TName, NS } = translate_each(Name, S),
@@ -271,22 +274,24 @@ translate_in(Line, Left, Right, S) ->
Expr = case TRight of
{ cons, _, _, _ } ->
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
lists:foldl(fun(X, Acc) ->
{ op, Line, 'orelse', Acc, { op, Line, '==', Var, X } }
lists:foldr(fun(X, Acc) ->
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
end, { op, Line, '==', Var, H }, T);
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
{ op, Line, 'orelse',
{ op, Line, 'andalso',
{ op, Line, '=<', Start, End},
{ op, Line, 'andalso',
{ op, Line, '>=', Var, Start },
{ op, Line, '=<', Var, End } } },
{ op, Line, 'andalso',
{ op, Line, '<', End, Start},
{ op, Line, 'andalso',
{ op, Line, '=<', Var, Start },
{ op, Line, '>=', Var, End } } }
};
case { Start, End } of
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
increasing_compare(Line, Var, Start, End);
{ { K1, _, _ }, { K2, _, _ } } when ?IN_TYPES(K1), ?IN_TYPES(K2) ->
decreasing_compare(Line, Var, Start, End);
_ ->
{ op, Line, 'orelse',
{ op, Line, 'andalso',
{ op, Line, '=<', Start, End},
increasing_compare(Line, Var, Start, End) },
{ op, Line, 'andalso',
{ op, Line, '<', End, Start},
decreasing_compare(Line, Var, Start, End) } }
end;
_ ->
syntax_error(Line, S#elixir_scope.file, "invalid args for operator in")
end,
@@ -296,6 +301,16 @@ translate_in(Line, Left, Right, S) ->
false -> { Var, Expr, SV }
end.
increasing_compare(Line, Var, Start, End) ->
{ op, Line, 'andalso',
{ op, Line, '>=', Var, Start },
{ op, Line, '=<', Var, End } }.
decreasing_compare(Line, Var, Start, End) ->
{ op, Line, 'andalso',
{ op, Line, '=<', Var, Start },
{ op, Line, '>=', Var, End } }.
rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}],True}]) ->
[{do,[false],False},{do,[true],True}];
@@ -317,9 +332,17 @@ is_reserved_data(_) -> false.
spec_to_macro(type) -> deftype;
spec_to_macro(typep) -> deftypep;
spec_to_macro(opaque) -> defopaque;
spec_to_macro(spec) -> defspec;
spec_to_macro(callback) -> defcallback.
% Unpack a list of expressions from a block.
unpack([{ '__block__', _, Exprs }]) -> Exprs;
unpack(Exprs) -> Exprs.
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
Message = "function names should start with lowercase characters or underscore, invalid name ~s",
syntax_error(Line, File, Message, [atom_to_binary(Atom, utf8)]);
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
ok.
+31 -41
View File
@@ -14,7 +14,7 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
false -> Line = 1
end,
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope#elixir_scope{check_clauses=false}),
{ Value, FinalBinding }.
scope_for_eval(Module, #elixir_scope{} = S) ->
@@ -25,7 +25,6 @@ scope_for_eval(Module, Opts) ->
binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
%% TABLE METHODS
data_table(Module) ->
@@ -34,9 +33,6 @@ data_table(Module) ->
docs_table(Module) ->
?ELIXIR_ATOM_CONCAT([o, Module]).
spec_table(Module) ->
?ELIXIR_ATOM_CONCAT([s, Module]).
%% TRANSFORMATION FUNCTIONS
%% Transformation of args and scope into a compiled erlang call.
@@ -72,15 +68,15 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(Module),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
Forms1 = attributes_form(Line, File, Module, Forms0),
Forms2 = specs_form(Line, Module, Defmacro, Defmacrop, Forms1),
Forms1 = specs_form(Line, Module, Defmacro, Defmacrop, Forms0, C),
Forms2 = attributes_form(Line, File, Module, Forms1),
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
Final = [
{attribute, Line, file, {File,Line}},
{attribute, Line, module, Module} | Forms2
{ attribute, Line, file, { binary_to_list(File), Line } },
{ attribute, Line, module, Module } | Forms2
],
Binary = load_form(Line, Final, S),
@@ -88,7 +84,6 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
after
ets:delete(data_table(Module)),
ets:delete(docs_table(Module)),
ets:delete(spec_table(Module)),
elixir_def:delete_table(Module),
elixir_import:delete_table(Module)
end;
@@ -131,10 +126,6 @@ build(Line, File, Module) ->
DocsTable = docs_table(Module),
ets:new(DocsTable, [ordered_set, named_table, public]),
%% Holds specs in a format it is easier to query.
SpecTable = spec_table(Module),
ets:new(SpecTable, [duplicate_bag, named_table, public]),
%% We keep a separated table for function definitions
%% and another one for imports. We keep them in different
%% tables for organization and speed purpose (since the
@@ -145,11 +136,11 @@ build(Line, File, Module) ->
%% Receives the module representation and evaluates it.
eval_form(Line, Module, Block, Vars, RawS) ->
Temp = ?ELIXIR_ATOM_CONCAT(["COMPILE-",Module]),
S = scope_for_eval(Module, RawS),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Temp, Vars, S),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
elixir_def_overridable:store_pending(Module),
eval_callbacks(Line, Module, before_compile, [Module], NewS),
elixir_def_overridable:store_pending(Module),
Value.
%% Return the form with exports and function declarations.
@@ -188,36 +179,41 @@ attributes_form(Line, _File, Module, Current) ->
%% Specs
specs_form(Line, Module, Defmacro, Defmacrop, Forms) ->
{ Specs, Callbacks } = ets:foldl(fun(X, Acc) ->
translate_spec(X, Defmacro, Defmacrop, Acc)
end, { [], [] }, spec_table(Module)),
specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
Defmacrop = [Tuple || { Tuple, _, _ } <- DefmacropWithLine],
case elixir_compiler:get_opt(internal, C) of
true -> Forms;
_ ->
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
Temp = specs_attributes(Line, spec, Forms, Specs),
specs_attributes(Line, callback, Temp, Callbacks).
'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)
end.
specs_attributes(Line, Type, Forms, Specs) ->
Keys = lists:ukeysort(1, Specs),
lists:foldl(fun({ Tuple, _ }, Acc) ->
[{ attribute, Line, Type, { Tuple, proplists:append_values(Tuple, Specs) } }|Acc]
Values = [V || { K, V } <- Specs, K == Tuple],
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
end, Forms, Keys).
translate_spec({ { spec, Spec }, Rest }, Defmacro, Defmacrop, { Specs, Callbacks } = Acc) ->
translate_spec({ Spec, Rest }, Defmacro, Defmacrop) ->
case ordsets:is_element(Spec, Defmacrop) of
true -> Acc;
true -> { Spec, Rest };
false ->
case ordsets:is_element(Spec, Defmacro) of
true ->
{ Name, Arity } = Spec,
New = { { ?ELIXIR_MACRO(Name), Arity + 1 }, [spec_for_macro(X) || X <- Rest] },
{ [New|Specs], Callbacks };
{ { ?ELIXIR_MACRO(Name), Arity + 1 }, spec_for_macro(Rest) };
false ->
{ [{ Spec, Rest }|Specs], Callbacks }
{ Spec, Rest }
end
end;
translate_spec({ { callback, Spec }, Rest }, _, _, { Specs, Callbacks }) ->
{ Specs, [{ Spec, Rest }|Callbacks] }.
end.
spec_for_macro({ type, Line, 'fun', [{ type, _, product, Args }|T] }) ->
NewArgs = [{type,Line,term,[]}|Args],
@@ -271,8 +267,7 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
macros_clause(Line, Module, Def, Defmacro),
docs_clause(Line, Module, Docs),
moduledoc_clause(Line, Module, Docs),
compile_clause(Line),
self_clause(Line, Module),
module_clause(Line, Module),
else_clause(Line)
] },
{ [Pair|Export], [Contents|Functions] }
@@ -291,8 +286,8 @@ handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
elixir_dispatch:in_erlang_macros()), Def);
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
self_clause(Line, Module) ->
{ clause, Line, [{ atom, Line, self }], [], [{ atom, Line, Module }] }.
module_clause(Line, Module) ->
{ clause, Line, [{ atom, Line, module }], [], [{ atom, Line, Module }] }.
docs_clause(Line, Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
@@ -308,11 +303,6 @@ moduledoc_clause(Line, Module, true) ->
moduledoc_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, moduledoc }], [], [{ atom, Line, nil }] }.
compile_clause(Line) ->
Info = { call, Line, { atom, Line, module_info }, [{ atom, Line, compile }] },
WrappedInfo = ?ELIXIR_WRAP_CALL(Line, 'Elixir.Keyword', 'from_enum', [Info]),
{ clause, Line, [{ atom, Line, compile }], [], [WrappedInfo] }.
else_clause(Line) ->
Info = { call, Line, { atom, Line, module_info }, [{ var, Line, atom }] },
{ clause, Line, [{ var, Line, atom }], [], [Info] }.
+13 -7
View File
@@ -13,7 +13,7 @@ Nonterminals
open_curly close_curly
open_bit close_bit
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
call_args call_args_parens call_args_no_parens parens_call
call_args call_args_parens call_args_parens_not_one call_args_no_parens parens_call
stab_expr stab_expr_list
kw_eol kw_expr kw_comma kw_base
matched_kw_expr matched_kw_comma matched_kw_base
@@ -80,6 +80,7 @@ grammar -> '$empty' : [nil].
% Note expressions are on reverse order
expr_list -> expr : ['$1'].
expr_list -> open_paren ')' : [nil].
expr_list -> expr_list eol expr : ['$3'|'$1'].
expr -> matched_expr : '$1'.
@@ -147,6 +148,7 @@ block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1
fn_expr -> fn_paren call_args_parens fn_block : build_fn('$1', '$2', '$3').
fn_expr -> fn call_args_no_parens fn_block : build_fn('$1', '$2', '$3').
fn_expr -> fn call_args_parens_not_one fn_block : build_fn('$1', '$2', '$3').
fn_expr -> fn fn_block : build_fn('$1', [], '$2').
fn_expr -> call_expr : '$1'.
@@ -163,7 +165,6 @@ max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
max_expr -> parens_call call_args_parens call_args_parens : { build_identifier('$1', '$2'), ?line('$1'), '$3' }.
max_expr -> dot_ref : '$1'.
max_expr -> base_expr : '$1'.
max_expr -> open_paren ')' : build_block([]).
max_expr -> open_paren expr_list close_paren : build_block('$2').
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
@@ -211,7 +212,8 @@ stab_expr_list -> stab_expr : ['$1'].
stab_expr_list -> stab_expr_list eol stab_expr : ['$3'|'$1'].
stab_expr -> expr : '$1'.
stab_expr -> comma_expr stab_op expr : build_op('$2', lists:reverse('$1'), '$3').
stab_expr -> call_args_no_parens stab_op expr : build_op('$2', '$1', '$3').
stab_expr -> call_args_parens_not_one stab_op expr : build_op('$2', '$1', '$3').
block_item -> block_eol stab_expr_list eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
block_item -> block_eol : { ?exprs('$1'), nil }.
@@ -400,6 +402,10 @@ call_args -> comma_expr : lists:reverse('$1').
call_args_parens -> open_paren ')' : [].
call_args_parens -> open_paren call_args close_paren : '$2'.
call_args_parens_not_one -> open_paren ')' : [].
call_args_parens_not_one -> open_paren kw_base close_paren : ['$2'].
call_args_parens_not_one -> open_paren expr ',' call_args close_paren : ['$2'|'$4'].
% KV
kw_eol -> kw_identifier : '$1'.
@@ -477,7 +483,6 @@ build_tuple(Marker, Args) ->
build_block(Exprs) -> build_block(Exprs, true).
build_block([], _) -> nil;
build_block([nil], _) -> { '__block__', 0, [nil] };
build_block([{Op,_,[_]}]=Exprs, _) when ?rearrange_uop(Op) -> { '__block__', 0, Exprs };
build_block([Expr], _) when not is_list(Expr) -> Expr;
@@ -530,7 +535,8 @@ build_fn(Op, Args, Expr) ->
%% Access
build_access(Expr, Access) ->
{ access, ?line(Access), [ Expr, ?op(Access) ] }.
Line = ?line(Access),
{ { '.', Line, ['Elixir-Kernel', access] }, ?line(Access), [ Expr, ?op(Access) ] }.
%% Interpolation aware
@@ -541,10 +547,10 @@ build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', Line, Args }.
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> binary_to_list(H);
build_list_string({ list_string, Line, Args }) -> { binary_to_list, Line, [{ '<<>>', Line, Args}] }.
build_list_string({ list_string, Line, Args }) -> { { '.', Line, [erlang, binary_to_list] }, Line, [{ '<<>>', Line, Args}] }.
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
build_atom({ atom, Line, Args }) -> { binary_to_atom, Line, [{ '<<>>', Line, Args }, utf8] }.
build_atom({ atom, Line, Args }) -> { { '.', Line, [erlang, binary_to_atom] }, Line, [{ '<<>>', Line, Args }, utf8] }.
%% Keywords
+29 -8
View File
@@ -1,5 +1,5 @@
-module(elixir_quote).
-export([quote/3, linify/2]).
-export([quote/3, linify/2, join_quoted/5]).
-include("elixir.hrl").
%% Apply the line from site call on quoted contents.
@@ -15,9 +15,28 @@ linify(Line, List) when is_list(List) ->
linify(_, Else) -> Else.
%% Join quoted checks for quote arguments at runtime
%% in order to properly insert them into the tree
join_quoted(Line, Left, { '__aliases__', _, Args }, nil, _File) ->
{ '__aliases__', Line, [Left|Args] };
join_quoted(Line, Left, Right, nil, _File) when is_atom(Right) ->
case atom_to_list(Right) of
"Elixir-" ++ _ ->
{ '__aliases__', Line, [Left, Right] };
_ ->
{ { '.', Line, [Left, Right] }, Line, [] }
end;
join_quoted(Line, Left, Right, Args, _File) when is_atom(Right) ->
{ { '.', Line, [Left, Right] }, Line, Args };
join_quoted(Line, _Left, _Right, _Args, File) ->
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom or an alias").
%% Translation
quote({ 'unquote_splicing', Line, _ } = Expr, Q, S) ->
quote({ 'unquote_splicing', Line, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
do_quote({ '__block__', Line, [Expr] }, Q, S);
quote(Else, Q, S) ->
@@ -26,13 +45,15 @@ quote(Else, Q, S) ->
do_quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
elixir_translator:translate_each(Expr, S);
do_quote({ { { '.', Line, [Left, unquote] }, _Line, [Expr] }, CallLine, Args }, Q, S) ->
Rewritten = { { '.', Line, [Left, { unquote, Line, [Expr] }] }, CallLine, Args },
do_quote(Rewritten, Q, S);
do_quote({ { { '.', Line, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Line, [Expr] }, Args, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?ELIXIR_WRAP_CALL(Line, elixir_quote, join_quoted, [line(Line, Q)|TAll]), TS };
do_quote({ { '.', Line, [Left, unquote] }, _Line, [Expr] }, Q, S) ->
Rewritten = { { '.', Line, [Left, { unquote, Line, [Expr] }] }, Line, [] },
do_quote(Rewritten, Q, S);
do_quote({ { '.', Line, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
All = [Left, { unquote, Line, [Expr] }, nil, S#elixir_scope.file],
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
{ ?ELIXIR_WRAP_CALL(Line, elixir_quote, join_quoted, [line(Line, Q)|TAll]), TS };
do_quote({ Left, Line, nil }, Q, S) when is_atom(Left) ->
Tuple = { tuple, Line, [
+50 -24
View File
@@ -1,11 +1,14 @@
%% Convenience functions used to manipulate scope
%% and its variables.
-module(elixir_scope).
-export([translate_var/4,
build_erl_var/2, build_ex_var/2,
build_erl_var/3, build_ex_var/3,
build_erl_var/4, build_ex_var/4,
serialize/1, deserialize/1, deserialize/2,
to_erl_env/1, to_ex_env/1, filename/1,
umergev/2, umergec/2
umergev/2, umergec/2, merge_clause_vars/2
]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
@@ -14,7 +17,8 @@ translate_var(Line, Name, Kind, S) ->
Vars = S#elixir_scope.vars,
case Name of
'_' -> { {var, Line, Name}, S };
'_' ->
{ { var, Line, Name }, S };
_ ->
case S#elixir_scope.context of
assign ->
@@ -23,16 +27,21 @@ translate_var(Line, Name, Kind, S) ->
{ true, { ok, Kind } } ->
{ {var, Line, orddict:fetch(Name, Vars) }, S };
{ Else, _ } ->
{ NewVar, NS } = case Else or S#elixir_scope.noname of
true -> build_erl_var(Line, S);
false -> { {var, Line, Name}, S }
{ NewVar, NS } = if
Kind == quoted -> build_erl_var(Line, S);
Else -> build_erl_var(Line, Name, S);
S#elixir_scope.noname -> build_erl_var(Line, Name, S);
true -> { {var, Line, Name}, S }
end,
RealName = element(3, NewVar),
ClauseVars = S#elixir_scope.clause_vars,
{ NewVar, NS#elixir_scope{
vars=orddict:store(Name, RealName, Vars),
temp_vars=orddict:store(Name, Kind, TempVars),
clause_vars=orddict:store({ Name, Kind }, RealName, ClauseVars)
clause_vars=if
ClauseVars == nil -> nil;
true -> orddict:store({ Name, Kind }, RealName, ClauseVars)
end
} }
end;
_ ->
@@ -45,14 +54,24 @@ translate_var(Line, Name, Kind, S) ->
% Handle variables translation
build_erl_var(Line, #elixir_scope{counter=Counter} = S) ->
NS = S#elixir_scope{counter=Counter+1},
Var = { var, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
build_ex_var(Line, Key, S) -> build_ex_var(Line, Key, Key, S).
build_erl_var(Line, Key, S) -> build_erl_var(Line, Key, Key, S).
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
New = orddict:update_counter(Key, 1, Counter),
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
build_erl_var(Line, Key, Name, S) ->
{ Counter, NS } = build_var_counter(Key, S),
Var = { var, Line, ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]) },
{ Var, NS }.
build_ex_var(Line, #elixir_scope{counter=Counter} = S) ->
NS = S#elixir_scope{counter=Counter+1},
Var = { ?ELIXIR_ATOM_CONCAT(["_@", Counter]), Line, nil },
build_ex_var(Line, Key, Name, S) ->
{ Counter, NS } = build_var_counter(Key, S),
Var = { ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]), Line, quoted },
{ Var, NS }.
% Handle Macro.Env conversion
@@ -65,8 +84,8 @@ to_ex_env({ Line, Tuple }) when element(1, Tuple) == 'Elixir.Macro.Env' ->
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
function=Function,aliases=Aliases,context=Context,
requires=Requires,macros=Macros} }) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Macros }.
requires=Requires,macros=Macros,functions=Functions} }) ->
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
filename(#elixir_scope{file=File}) -> File;
filename(Other) -> element(3, Other).
@@ -75,7 +94,7 @@ filename(Other) -> element(3, Other).
serialize(S) ->
elixir_tree_helpers:abstract_syntax(
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses, S#elixir_scope.macro,
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases, S#elixir_scope.scheduled }
).
@@ -83,18 +102,17 @@ serialize(S) ->
deserialize(Tuple) -> deserialize(Tuple, []).
deserialize({ File, Functions, CheckClauses, Macro, Requires, Macros, Aliases, Scheduled }, Vars) ->
deserialize({ File, Functions, CheckClauses, Requires, Macros, Aliases, Scheduled }, Vars) ->
#elixir_scope{
file=File,
functions=Functions,
check_clauses=CheckClauses,
macro=Macro,
requires=Requires,
macros=Macros,
aliases=Aliases,
scheduled=Scheduled,
vars=orddict:from_list(Vars),
counter=length(Vars)
counter=[{'',length(Vars)}]
}.
% Receives two scopes and return a new scope based on the second
@@ -110,7 +128,7 @@ umergev(S1, S2) ->
S2#elixir_scope{
vars=orddict:merge(fun var_merger/3, V1, V2),
quote_vars=orddict:merge(fun var_merger/3, Q1, Q2),
clause_vars=orddict:merge(fun clause_var_merger/3, C1, C2)
clause_vars=merge_clause_vars(C1, C2)
}.
% Receives two scopes and return a new scope based on the first
@@ -127,16 +145,24 @@ umergec(S1, S2) ->
% Merge variables trying to find the most recently created.
merge_clause_vars(nil, _C2) -> nil;
merge_clause_vars(_C1, nil) -> nil;
merge_clause_vars(C1, C2) ->
orddict:merge(fun clause_var_merger/3, C1, C2).
clause_var_merger({ Var, _ }, K1, K2) ->
var_merger(Var, K1, K2).
var_merger(Var, Var, K2) -> K2;
var_merger(Var, K1, Var) -> K1;
var_merger(_Var, K1, K2) ->
V1 = var_number(atom_to_list(K1)),
V2 = var_number(atom_to_list(K2)),
if V1 > V2 -> K1;
true -> K2
V1 = var_number(atom_to_list(K1), []),
V2 = var_number(atom_to_list(K2), []),
case V1 > V2 of
true -> K1;
false -> K2
end.
var_number([_,_|T]) -> list_to_integer(T).
var_number([$@|T], _Acc) -> var_number(T, []);
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
+5 -6
View File
@@ -773,13 +773,12 @@ terminator('<<') -> '>>'.
handle_keyword(Line, paren_identifier, fn, Tokens) ->
[{ 'fn_paren', Line }|Tokens];
handle_keyword(Line, Identifier, Atom, Tokens) when Identifier == bracket_identifier; Identifier == paren_identifier ->
case keyword(Atom) of
true -> [{ Atom, Line }|Tokens];
_ -> [{ Identifier, Line, Atom }|Tokens]
end;
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
[{ Identifier, Line, Atom }|Tokens];
handle_keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifier == do_identifier ->
handle_keyword(Line, Identifier, Atom, Tokens) when
Identifier == identifier; Identifier == do_identifier;
Identifier == bracket_identifier; Identifier == paren_identifier ->
case keyword(Atom) of
true -> [{ Atom, Line }|Tokens];
op -> add_token_with_nl({ Atom, Line }, Tokens);
+7 -19
View File
@@ -61,7 +61,7 @@ translate_each({ '__block__', Line, Args }, S) when is_list(Args) ->
{ TArgs, NS } = translate(Args, S),
{ { block, Line, TArgs }, NS };
translate_each({ '__scope__', _Line, [[{file,File}],Expr] }, S) ->
translate_each({ '__scope__', _Line, [[{file,File}],[{do,Expr}]] }, S) ->
Old = S#elixir_scope.file,
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
{ TExpr, TS#elixir_scope{file=Old} };
@@ -259,7 +259,7 @@ translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
{ DefaultLine, WrappedExprs } = case lists:keyfind(location, 1, T) of
{ location, keep } ->
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],Exprs] },
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],[{do,Exprs}]] },
{ keep, Scoped };
_ ->
{ 0, Exprs}
@@ -408,14 +408,6 @@ translate_each({ { '.', _, [Left, Right] }, Line, Args } = Original, S) when is_
Callback = fun() -> translate_apply(Line, TLeft, TRight, Args, S, SL, SR) end,
case TLeft of
{ atom, _, 'Elixir.Erlang' } ->
elixir_errors:deprecation(Line, S#elixir_scope.file, "Erlang. syntax is deprecated"),
case Args of
[] -> { { atom, Line, Right }, S };
_ ->
Message = "invalid args for Erlang.~s expression",
syntax_error(Line, S#elixir_scope.file, Message, [Right])
end;
{ atom, _, Receiver } ->
elixir_dispatch:dispatch_require(Line, Receiver, Right, Args, umergev(SL, SR), Callback);
_ ->
@@ -565,7 +557,7 @@ rellocate_ambiguous_op({ Call, Line, [H|T] }, Var) ->
translate_comprehension(Line, Kind, Args, S) ->
case elixir_tree_helpers:split_last(Args) of
{ Cases, [{do,Expr}] } ->
{ TCases, SC } = lists:mapfoldl(fun translate_comprehension_clause/2, S, Cases),
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Line, C, Acc) end, S, Cases),
{ TExpr, SE } = translate_comprehension_do(Line, Kind, Expr, SC),
{ { Kind, Line, TExpr, TCases }, umergec(S, SE) };
_ ->
@@ -581,20 +573,16 @@ translate_comprehension_do(Line, bc, _Expr, S) ->
translate_comprehension_do(_Line, _Kind, Expr, S) ->
translate_each(Expr, S).
translate_comprehension_clause({inbits, Line, [Left, Right]}, S) ->
translate_comprehension_clause(_Line, {inbits, Line, [Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ { b_generate, Line, TLeft, TRight }, SL };
translate_comprehension_clause({inlist, Line, [Left, Right]}, S) ->
translate_comprehension_clause(_Line, {inlist, Line, [Left, Right]}, S) ->
{ TRight, SR } = translate_each(Right, S),
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
{ { generate, Line, TLeft, TRight }, SL };
translate_comprehension_clause(X, S) ->
translate_comprehension_clause(Line, X, S) ->
{ TX, TS } = translate_each(X, S),
Line = case X of
{ _, L, _ } -> L;
_ -> 0
end,
{ elixir_tree_helpers:convert_to_boolean(Line, TX, true, false), TS }.
elixir_tree_helpers:convert_to_boolean(Line, TX, true, false, TS).
+33 -20
View File
@@ -5,7 +5,7 @@
-module(elixir_tree_helpers).
-compile({parse_transform, elixir_transform}).
-export([abstract_syntax/1, split_last/1, cons_to_list/1,
convert_to_boolean/4, returns_boolean/1,
convert_to_boolean/5, returns_boolean/1,
build_list/4, build_list/5, build_simple_list/2,
build_reverse_list/4, build_reverse_list/5, build_simple_reverse_list/2]).
-include("elixir.hrl").
@@ -84,34 +84,47 @@ returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } },
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_] }) when
Fun == is_function -> true;
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_,_] }) when
Fun == function_exported -> true;
returns_boolean({ atom, _, Bool }) when Bool == true; Bool == false -> true;
returns_boolean({ 'case', _, _, Clauses }) ->
lists:all(fun
({clause,_,_,_,[Expr]}) -> returns_boolean(Expr);
(_) -> false
end, Clauses);
returns_boolean(_) -> false.
convert_to_boolean(Line, Expr, Bool, InGuard) ->
convert_to_boolean(Line, Expr, Bool, InGuard, S) ->
case { returns_boolean(Expr), Bool } of
{ true, true } -> Expr;
{ true, false } -> { op, Line, 'not', Expr };
_ -> do_convert_to_boolean(Line, Expr, Bool, InGuard)
{ true, true } -> { Expr, S };
{ true, false } -> { { op, Line, 'not', Expr }, S };
_ -> do_convert_to_boolean(Line, Expr, Bool, InGuard, S)
end.
do_convert_to_boolean(Line, Expr, Bool, false) ->
Any = [{var, Line, '_'}],
False = [{atom,Line,false}],
Nil = [{atom,Line,nil}],
%% Notice we use a temporary var and bundle nil
%% and false checks in the same clause since
%% it makes dialyzer happy.
do_convert_to_boolean(Line, Expr, Bool, false, S) ->
Any = { var, Line, '_' },
{ Var, TS } = elixir_scope:build_erl_var(Line, S),
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
FalseResult = [{atom,Line,not Bool}],
TrueResult = [{atom,Line,Bool}],
FalseResult = { atom,Line,not Bool },
TrueResult = { atom,Line,Bool },
{ 'case', Line, Expr, [
{ clause, Line, False, [], FalseResult },
{ clause, Line, Nil, [], FalseResult },
{ clause, Line, Any, [], TrueResult }
] };
{ { 'case', Line, Expr, [
{ clause, Line, [Var], [[OrElse]], [FalseResult] },
{ clause, Line, [Any], [], [TrueResult] }
] }, TS };
do_convert_to_boolean(Line, Expr, true, true) ->
do_guarded_convert_to_boolean(Line, Expr, 'andalso', '/=');
do_convert_to_boolean(Line, Expr, true, true, S) ->
{ do_guarded_convert_to_boolean(Line, Expr, 'andalso', '/='), S };
do_convert_to_boolean(Line, Expr, false, true) ->
do_guarded_convert_to_boolean(Line, Expr, 'orelse', '==').
do_convert_to_boolean(Line, Expr, false, true, S) ->
{ do_guarded_convert_to_boolean(Line, Expr, 'orelse', '=='), S }.
do_guarded_convert_to_boolean(Line, Expr, Op, Comp) ->
Left = { op, Line, Comp, Expr, { atom, Line, false } },
+1 -1
View File
@@ -240,7 +240,7 @@ validate_rescue_access(Line, { '=', _, [Left, Right] }, S) ->
validate_rescue_access(Line, Left, S),
validate_rescue_access(Line, Right, S);
validate_rescue_access(Line, { 'access', _, [Element, _] }, S) ->
validate_rescue_access(Line, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
case elixir_translator:translate_each(Element, S) of
{ { atom, _, Atom }, _ } ->
case lists:member(Atom, erlang_rescues()) of
+32 -6
View File
@@ -6,16 +6,42 @@ defmodule BehaviourTest do
defmodule Sample do
use Behaviour
@doc "Doc"
defcallback foo(foo, bar // [], bat // [])
@doc "Foo"
defcallback foo(atom, binary), do: binary
@doc "Bar"
defcallback bar(External.hello, my_var :: binary), do: binary
end
test :docs_are_defined do
assert [_, _] = Sample.__info__(:docs)
test :docs do
docs = Enum.qsort(Sample.__behaviour__(:docs))
assert docs == [
{{:bar,2},13,"Bar"},
{{:foo,2},10,"Foo"}
]
end
test :callbacks do
assert [{:foo,3},{:foo,2},{:foo,1}] = Sample.behaviour_info(:callbacks)
assert Sample.__behaviour__(:callbacks) == [foo: 2, bar: 2]
end
test :specs do
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 2
end
test :default_is_not_supported do
assert_raise ArgumentError, fn ->
defmodule WithDefault do
use Behaviour
defcallback hello(num // 0 :: integer), do: integer
end
end
assert_raise ArgumentError, fn ->
defmodule WithDefault do
use Behaviour
defcallback hello(num :: integer // 0), do: integer
end
end
end
end
@@ -110,6 +110,10 @@ defmodule Binary.Inspect.TupleTest do
assert inspect({ List, 1 }) == "{List,1}"
end
test :with_record_like_pseudo_exception do
assert inspect({ Other, :__exception__, 1 }) == "{Other,:__exception__,1}"
end
defrecord Config, a: 1, b: []
test :with_record do
@@ -155,6 +159,10 @@ defmodule Binary.Inspect.ListTest do
assert inspect(["123": 1]) == %b(["123": 1])
end
test :non_keyword do
assert inspect([{ Regex, 1 }]) == "[{Regex,1}]"
end
test :non_printable do
assert inspect([{:b, 1},{:a,1}]) == "[b: 1, a: 1]"
end
+1 -1
View File
@@ -59,7 +59,7 @@ bar
test :pattern_match do
s = 16
assert <<a, b :: size(s)>> = "foo"
assert <<_a, _b :: size(s)>> = "foo"
end
test :partial_application do
+5
View File
@@ -28,6 +28,11 @@ defmodule CodeTest do
assert CodeTest.Sample.eval_quoted_info() == { CodeTest.Sample, "sample.ex", 13 }
end
test :eval_quoted_with_env do
alias :lists, as: MyList
assert Code.eval_quoted(quote(do: MyList.flatten [[1,2,3]]), [], __ENV__) == { [1,2,3],[] }
end
test :require do
Code.require_file fixture_path("code_sample.exs")
assert fixture_path("code_sample.exs") in Code.loaded_files
+8
View File
@@ -34,6 +34,14 @@ defmodule DictTest.Common do
assert "default" == Dict.get(empty_dict, "first_key", "default")
end
test :get! do
assert 1 == Dict.get!(new_dict, "first_key")
assert 2 == Dict.get!(new_dict, "second_key")
assert_raise KeyError, fn ->
Dict.get!(new_dict, "other_key")
end
end
test :put do
dict = Dict.put(new_dict, "first_key", {1})
assert {1} == Dict.get dict, "first_key"
-27
View File
@@ -3,31 +3,6 @@ Code.require_file "../test_helper.exs", __FILE__
defmodule EnumTest do
use ExUnit.Case, async: true
test :times do
try do
Process.put(:times_with_arity, nil)
assert Enum.times(0, fn -> Process.put(:times_with_arity, :ok) end) == 0
assert Process.get(:times_with_arity) == nil
assert Enum.times(3, fn -> Process.put(:times_with_arity, :ok) end) == 3
assert Process.get(:times_with_arity) == :ok
after
Process.delete(:times_with_arity)
end
end
test :times_with_iterator do
try do
assert Enum.times(5, fn x -> Process.put(:times_with_arity, x) end) == 5
assert Process.get(:times_with_arity) == 4
after
Process.delete(:times_with_arity)
end
end
test :times_with_iterator_and_acumulator do
assert Enum.times(5, 0, fn acc, x -> acc + x end) == 10
end
test :reverse do
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
[{ "baz", "bat" }, { "foo", "bar" }]
@@ -712,8 +687,6 @@ defmodule EnumTest.Range do
assert Enum.take_while(range, fn(x) -> x > 3 end) == []
assert Enum.take_while(range, fn(x) -> x <= 1 end) == [1]
assert Enum.take_while(range, fn(x) -> x <= 3 end) == [1,2,3]
range = Range.new(first: 1, last: 0)
assert Enum.take_while([], fn(_) -> true end) == []
end
+14 -2
View File
@@ -589,7 +589,7 @@ defmodule FileTest do
end
test :open_utf8_by_default do
{ :ok, file } = File.open(fixture_path("utf8.txt"))
{ :ok, file } = File.open(fixture_path("utf8.txt"), [:utf8])
assert IO.gets(file, "") == "Русский\n"
assert File.close(file) == :ok
end
@@ -612,8 +612,20 @@ defmodule FileTest do
end
end
test :open_with_binwrite_permission do
fixture = tmp_path("tmp_text.txt")
try do
{ :ok, file } = File.open(fixture, [:write])
assert IO.binwrite(file, "Русский") == :ok
assert File.close(file) == :ok
assert File.read(fixture) == { :ok, "Русский" }
after
File.rm(fixture)
end
end
test :open_utf8_and_charlist do
{ :ok, file } = File.open(fixture_path("utf8.txt"), [:charlist])
{ :ok, file } = File.open(fixture_path("utf8.txt"), [:charlist, :utf8])
assert IO.gets(file, "") == [1056,1091,1089,1089,1082,1080,1081,10]
assert File.close(file) == :ok
end
+17 -4
View File
@@ -10,11 +10,17 @@ defmodule IOTest do
end
test :read_with_utf8_and_binary do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
assert "Русский" == IO.read(file, 7)
assert File.close(file) == :ok
end
test :binread do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
assert "Русский" == IO.binread(file, 14)
assert File.close(file) == :ok
end
test :getb do
{ :ok, file } = File.open(File.expand_path('../fixtures/foo.txt', __FILE__))
assert "F" == IO.getb(file, "")
@@ -32,7 +38,7 @@ defmodule IOTest do
end
test :getb_with_utf8_and_binary do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
assert "Русский" == IO.getb(file, "", 7)
assert File.close(file) == :ok
end
@@ -45,7 +51,7 @@ defmodule IOTest do
end
test :gets_with_utf8_and_binary do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
assert "Русский\n" == IO.gets(file, "")
assert "日\n" == IO.gets(file, "")
assert File.close(file) == :ok
@@ -59,9 +65,16 @@ defmodule IOTest do
end
test :readline_with_utf8_and_binary do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__), [:utf8])
assert "Русский\n" == IO.readline(file)
assert "日\n" == IO.readline(file)
assert File.close(file) == :ok
end
test :binreadline do
{ :ok, file } = File.open(File.expand_path('../fixtures/utf8.txt', __FILE__))
assert "Русский\n" == IO.binreadline(file)
assert "日\n" == IO.binreadline(file)
assert File.close(file) == :ok
end
end
+7 -1
View File
@@ -77,7 +77,7 @@ defmodule Kernel.CLI.CompileTest do
import PathHelpers
test :compile_code do
fixture = fixture_path "compile_sample.exs"
fixture = fixture_path "compile_sample.ex"
assert elixirc('#{fixture} -o #{tmp_path}') ==
'Compiled #{fixture}\n'
assert File.regular?(tmp_path "Elixir-CompileSample.beam")
@@ -88,6 +88,12 @@ defmodule Kernel.CLI.ParallelCompilerTest do
use ExUnit.Case, async: true
import PathHelpers
test :files do
fixtures = [fixture_path("compile_sample.ex")]
assert [{ CompileSample, binary }] = Kernel.ParallelCompiler.files fixtures
assert is_binary(binary)
end
test :compile_code do
output = elixirc('#{fixture_path("parallel_compiler")} -o #{tmp_path}')
assert :string.str(output, 'message_from_foo') > 0,
+2 -2
View File
@@ -17,8 +17,8 @@ defmodule Kernel.DocTest do
assert CompiledWithDocs.__info__(:docs) == expected
assert CompiledWithDocs.__info__(:moduledoc) == { 1, "moduledoc" }
after
:code.purge CompiledWithDocs
:code.delete CompiledWithDocs
:code.purge CompiledWithDocs
end
end
@@ -33,8 +33,8 @@ defmodule Kernel.DocTest do
assert CompiledWithDocs.__info__(:moduledoc) == nil
after
Code.compiler_options(docs: true)
:code.purge CompiledWithDocs
:code.delete CompiledWithDocs
:code.purge CompiledWithDocs
end
end
end
@@ -103,6 +103,11 @@ defmodule Kernel.ErrorsTest do
format_rescue 'defmodule Foo do\ndefmacrop foo, do: 1\ndefmacro bar, do: __MODULE__.foo\ndefmacro baz, do: bar\nend'
end
test :function_definition_with_alias do
assert "nofile:1: function names should start with lowercase characters or underscore, invalid name Bar" ==
format_rescue 'defmodule Foo do def Bar do :baz end\nend\n'
end
test :erlang_function_conflict do
assert "nofile:1: function exit/1 already imported from Kernel" ==
format_rescue 'defmodule Foo do import Kernel.ErrorsTest.UnproperMacro, only: [exit: 1]\nend'
@@ -153,7 +158,7 @@ defmodule Kernel.ErrorsTest do
end
test :cant_define_local_due_to_in_erlang_macros_conflict do
assert "nofile:1: cannot define local quote/1 because it conflicts with Elixir internal macros" ==
assert "nofile:1: cannot define local quote/1 because it conflicts with Elixir special forms" ==
format_rescue 'defmodule Foo do\ndef quote(x), do: x\ndef bar(x), do: quote(do: x)\nend'
end
@@ -273,7 +278,7 @@ defmodule Kernel.ErrorsTest do
:elixir.eval(to_char_list(expr), [])
nil
rescue
error -> System.stacktrace
_ -> System.stacktrace
end
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
@@ -27,7 +27,7 @@ defmodule Kernel.Overridable do
{ super, super?, 1 }
end
false = Module.overridable? __MODULE__, { :explicit_nested_super, 0 }
true = Module.overridable? __MODULE__, { :explicit_nested_super, 0 }
defoverridable [explicit_nested_super: 0]
@@ -94,6 +94,10 @@ defmodule Kernel.Overridable do
17
end
def many_clauses(3) do
super(0) + super(1)
end
def many_clauses(_) do
super
end
@@ -139,6 +143,7 @@ defmodule Kernel.OverridableTest do
assert Overridable.many_clauses(0) == 11
assert Overridable.many_clauses(1) == 13
assert Overridable.many_clauses(2) == 17
assert Overridable.many_clauses(3) == 24
end
test "overridable definitions are private" do
+8 -1
View File
@@ -69,7 +69,7 @@ defmodule Kernel.QuoteTest do
67,
[
[file: __FILE__],
{ :bar, 67, [1,2,3] }
[do: { :bar, 67, [1,2,3] }]
]
}
end
@@ -87,6 +87,13 @@ defmodule Kernel.QuoteTest do
assert quote(do: foo.unquote(:bar)) == quote(do: foo.bar)
assert quote(do: foo.unquote(:bar)(1)) == quote(do: foo.bar(1))
assert quote(do: foo.unquote(:bar)(1) do 2 + 3 end) == quote(do: foo.bar(1) do 2 + 3 end)
assert Code.eval_quoted(quote(do: Foo.unquote(Bar))) == { Foo.Bar, [] }
assert Code.eval_quoted(quote(do: Foo.unquote(quote do: Bar))) == { Foo.Bar, [] }
assert_raise SyntaxError, fn ->
quote(do: foo.unquote(1))
end
end
test :splice_on_root do
@@ -31,7 +31,7 @@ defmodule Kernel.RescueTest do
rescue
RuntimeError -> true
catch
:error, value -> false
:error, _ -> false
end
assert result
+48
View File
@@ -204,6 +204,54 @@ defmodule KernelTest do
defp bang(_), do: :truthy
end
defmodule IfScope do
use ExUnit.Case, async: true
test :variables_on_nested_if do
if true do
a = 1
if true do
b = 2
end
end
assert a == 1
assert b == 2
end
test :variables_on_siblings_if do
if true do
a = 1
if true do
b = 2
end
if true do
c = 3
end
end
assert a == 1
assert b == 2
assert c == 3
end
test :variables_counter_on_nested_if do
r = (fn() -> 3 end).() # supresses warning at (if r < 0...)
r = r - 1
r = r - 1
r = r - 1
if true do
r = r - 1
if r < 0, do: r = 0
end
assert r == 0
end
end
defmodule Destructure do
use ExUnit.Case, async: true
+9 -2
View File
@@ -28,6 +28,13 @@ defmodule KeywordTest do
assert Keyword.from_enum(dict) == [a: 1, b: 2, c: 3]
end
test :keyword? do
assert Keyword.keyword?([])
assert Keyword.keyword?([a: 1])
refute Keyword.keyword?([{}])
refute Keyword.keyword?(<<>>)
end
test :new do
assert Keyword.new == []
end
@@ -50,7 +57,7 @@ defmodule KeywordTest do
test :get! do
assert Keyword.get!(create_keywords, :first_key) == 1
error = assert_raise Keyword.KeyError, fn ->
error = assert_raise KeyError, fn ->
Keyword.get!(create_keywords, :unknown)
end
@@ -99,7 +106,7 @@ defmodule KeywordTest do
test :update do
assert Keyword.update([a: 1], :a, &1 * 2) == [a: 2]
assert_raise Keyword.KeyError, fn ->
assert_raise KeyError, fn ->
assert Keyword.update([a: 1], :b, &1 * 2)
end
end
+8
View File
@@ -128,6 +128,14 @@ defmodule MacroTest do
assert Macro.to_binary(quote do: foo.bar([1, 2, 3])) == "foo.bar([1, 2, 3])"
end
test :low_atom_remote_call_to_binary do
assert Macro.to_binary(quote do: :foo.bar(1, 2, 3)) == ":foo.bar(1, 2, 3)"
end
test :big_atom_remote_call_to_binary do
assert Macro.to_binary(quote do: Foo.Bar.bar(1, 2, 3)) == "Foo.Bar.bar(1, 2, 3)"
end
test :remote_and_fun_call_to_binary do
assert Macro.to_binary(quote do: foo.bar.(1, 2, 3)) == "foo.bar().(1, 2, 3)"
assert Macro.to_binary(quote do: foo.bar.([1, 2, 3])) == "foo.bar().([1, 2, 3])"
+28 -10
View File
@@ -170,6 +170,16 @@ defmodule ModuleTest do
end)
end
def __on_definition__(env, kind, name, args, guards, expr) do
Process.put(env.module, :called)
assert env.module == ModuleTest.OnDefinition
assert kind == :def
assert name == :hello
assert [{ :foo, _, _ }, { :bar, _ ,_ }] = args
assert [] = guards
assert [do: { :+, _, [{ :foo, _, _ }, { :bar, _, _ }] }] = expr
end
test :on_definition do
defmodule OnDefinition do
@on_definition ModuleTest
@@ -182,16 +192,6 @@ defmodule ModuleTest do
assert Process.get(ModuleTest.OnDefinition) == :called
end
def __on_definition__(env, kind, name, args, guards, expr) do
Process.put(env.module, :called)
assert env.module == ModuleTest.OnDefinition
assert kind == :def
assert name == :hello
assert [{ :foo, _, _ }, { :bar, _ ,_ }] = args
assert [] = guards
assert [do: { :+, _, [{ :foo, _, _ }, { :bar, _, _ }] }] = expr
end
test :create do
contents =
quote do
@@ -201,4 +201,22 @@ defmodule ModuleTest do
Module.create(ModuleCreateSample, contents, __ENV__)
assert ModuleCreateSample.world
end
defmacro __before_compile__(_) do
quote do
def constant, do: 1
defoverridable constant: 0
end
end
test :overridable_inside_before_compile do
defmodule OverridableWithBeforeCompile do
@before_compile ModuleTest
end
assert OverridableWithBeforeCompile.constant == 1
end
test :to_binary do
assert Module.to_binary(Hello.World) == "Hello.World"
end
end
+11 -13
View File
@@ -16,11 +16,11 @@ defmodule OptionParserTest do
end
test "parses more than one boolean option" do
assert OptionParser.parse(["--docs", "--compile"]) == { [compile: true, docs: true], [] }
assert OptionParser.parse(["--docs", "--compile"]) == { [docs: true, compile: true], [] }
end
test "parses more than one boolean options as the alias" do
assert OptionParser.parse(["--d", "--compile"], aliases: [d: :docs]) == { [compile: true, docs: true], [] }
assert OptionParser.parse(["--d", "--compile"], aliases: [d: :docs]) == { [docs: true, compile: true], [] }
end
test "parses key/value option" do
@@ -57,7 +57,7 @@ defmodule OptionParserTest do
assert OptionParser.parse(["--docs", "foo"], flags: [:docs]) == { [docs: true], ["foo"] }
end
test "flags default to false even when not given" do
test "not parsed flags defaults to false" do
assert OptionParser.parse(["foo"], flags: [:docs]) == { [docs: false], ["foo"] }
end
@@ -71,27 +71,25 @@ defmodule OptionParserTest do
end
test "parses more than one key/value options" do
options = OptionParser.parse(["--source", "from_docs/", "--docs", "false"])
assert_equal options, { [docs: false, source: "from_docs/"], [] }
assert OptionParser.parse(["--source", "from_docs/", "--docs", "false"]) == { [source: "from_docs/", docs: false], [] }
end
test "accumulates options accumulators" do
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"]) == { [require: "foo", require: "bar"], ["baz"] }
end
test "parses mixed options" do
options = OptionParser.parse(["--source", "from_docs/", "--docs", "false", "--compile", "-x"])
assert_equal options, { :orddict.from_list([docs: false, source: "from_docs/", compile: true, x: true]), [] }
assert options == { [source: "from_docs/", docs: false, compile: true, x: true], [] }
end
test "stops on first non option arguments" do
options = OptionParser.parse_head(["--source", "from_docs/", "test/enum_test.exs", "--verbose"])
assert_equal options, { [source: "from_docs/"], ["test/enum_test.exs", "--verbose"] }
assert options == { [source: "from_docs/"], ["test/enum_test.exs", "--verbose"] }
end
test "goes beyond the first non option arguments" do
options = OptionParser.parse(["--source", "from_docs/", "test/enum_test.exs", "--verbose"])
assert_equal options, { [source: "from_docs/", verbose: true], ["test/enum_test.exs"] }
end
defp assert_equal({ opts1, tail1 }, { opts2, tail2 }) do
assert Keyword.equal?(opts1, opts2)
assert tail1 == tail2
assert options == { [source: "from_docs/", verbose: true], ["test/enum_test.exs"] }
end
end
+15
View File
@@ -1,12 +1,14 @@
Code.require_file "../test_helper.exs", __FILE__
defprotocol ProtocolTest.WithAll do
@type t :: any
@doc "Blank"
def blank(thing)
end
defprotocol ProtocolTest.WithExcept do
@except [Atom, Number, List]
@spec blank(t), do: boolean
def blank(thing)
end
@@ -140,6 +142,19 @@ defmodule ProtocolTest do
assert catch_error(ProtocolTest.Multi.test("a")) == :undef
end
test :protocol_callback do
assert get_callbacks(ProtocolTest.WithOnly, :blank, 1) ==
[{:type,17,:fun,[{:type,17,:product,[{:type,17,:t,[]}]},{:type,17,:term,[]}]}]
assert get_callbacks(ProtocolTest.WithExcept, :blank, 1) ==
[{:type,11,:fun,[{:type,11,:product,[{:type,11,:t,[]}]},{:type,11,:boolean,[]}]}]
end
defp get_callbacks(module, name, arity) do
callbacks = lc { :callback, info } inlist module.__info__(:attributes), do: hd(info)
List.keyfind(callbacks, { name, arity }, 0) /> elem(1)
end
# Assert that the given protocol is going to be dispatched.
defp assert_protocol_for(target, impl, thing) do
joined = Module.concat(target, impl)
+20 -4
View File
@@ -3,6 +3,9 @@ Code.require_file "../test_helper.exs", __FILE__
defrecord RecordTest.FileInfo,
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
defrecord RecordTest.SomeRecord, a: 0, b: 1
defrecord RecordTest.WithNoField, []
name = RecordTest.DynamicName
defrecord name, a: 0, b: 1 do
def get_a(RecordTest.DynamicName[a: a]) do
@@ -10,12 +13,22 @@ defrecord name, a: 0, b: 1 do
end
end
defrecord RecordTest.SomeRecord, a: 0, b: 1
## With types
defrecord RecordTest.WithNoField, []
defrecord RecordTest.WithTypeOverriden, a: 0, b: 1 do
@type t :: __MODULE__[a: integer, b: any]
end
defrecord RecordTest.WithRecordType, a: 0, b: 1 do
record_type a: integer
end
defmodule RecordTest.Macros do
Record.defmacros :_user, [:name, :age], __ENV__
defrecordp :_user, name: "José", age: 25
def new() do
_user()
end
def new(name, age) do
_user(name: name, age: age)
@@ -58,7 +71,7 @@ defmodule RecordTest do
test :record_constructor_with_dict do
record = RecordTest.FileInfo.new(type: :regular)
assert record.type == :regular
assert record.access == nil
assert record.access == :undefined
end
test :record_accessors do
@@ -115,6 +128,9 @@ defmodule RecordTest do
end
test :record_macros do
record = RecordTest.Macros.new
assert record.name == "José"
record = RecordTest.Macros.new("Foo", 25)
assert record.name == "Foo"
+18 -1
View File
@@ -42,6 +42,7 @@ defmodule StringTest do
end
test :rstrip do
assert String.rstrip("") == ""
assert String.rstrip(" abc ") == " abc"
assert String.rstrip(" abc a") == " abc a"
assert String.rstrip(" abc aa", ?a) == " abc "
@@ -49,12 +50,14 @@ defmodule StringTest do
end
test :lstrip do
assert String.lstrip("") == ""
assert String.lstrip(" abc ") == "abc "
assert String.lstrip("a abc a") == "a abc a"
assert String.lstrip("__ abc _", ?_) == " abc _"
end
test :strip do
assert String.strip("") == ""
assert String.strip(" abc ") == "abc"
assert String.strip("___ abc ___", ?_) == " abc "
end
@@ -97,6 +100,15 @@ defmodule StringTest do
assert String.codepoints("ϖͲϥЫݎߟΈټϘለДШव׆ש؇؊صلټܗݎޥޘ߉ऌ૫ሏᶆ℆ℙℱ ⅚Ⅷ↠∈⌘①ffi") == ["ϖ","Ͳ","ϥ","Ы","ݎ","ߟ","Έ","ټ","Ϙ","ለ","Д","Ш","व","׆","ש","؇","؊","ص","ل","ټ","ܗ","ݎ","ޥ","ޘ","߉","ऌ","૫","ሏ","ᶆ","℆","ℙ","ℱ"," ","⅚","Ⅷ","↠","∈","⌘","①","ffi"]
end
test :graphemes do
assert String.graphemes("Ā̀stute") == ["Ā̀","s","t","u","t","e"]
end
test :next_grapheme do
assert String.next_grapheme("Ā̀stute") == {"Ā̀","stute"}
assert String.next_grapheme("") == :no_grapheme
end
test :first do
assert String.first("elixir") == "e"
assert String.first("íelixr") == "í"
@@ -105,6 +117,7 @@ defmodule StringTest do
assert String.first("ελιξήριο") == "ε"
assert String.first("סם חיים") == "ס"
assert String.first("がガちゃ") == "が"
assert String.first("Ā̀stute") == "Ā̀"
assert String.first("") == ""
end
@@ -117,6 +130,7 @@ defmodule StringTest do
assert String.last("סם ייםח") == "ח"
assert String.last("がガちゃ") == "ゃ"
assert String.last("") == ""
assert String.last("Ā̀") == "Ā̀"
end
test :length do
@@ -127,6 +141,7 @@ defmodule StringTest do
assert String.length("ειξήριολ") == 8
assert String.length("סם ייםח") == 7
assert String.length("がガちゃ") == 4
assert String.length("Ā̀stute") == 6
assert String.length("") == 0
end
@@ -134,10 +149,12 @@ defmodule StringTest do
assert String.at("л", 0) == "л"
assert String.at("elixir", 1) == "l"
assert String.at("がガちゃ", 2) == "ち"
assert String.at("л", 10) == ""
assert String.at("л", 10) == nil
assert String.at("elixir", -1) == "r"
assert String.at("がガちゃ", -2) == "ち"
assert String.at("л", -3) == ""
assert String.at("Ā̀stute", 1) == "s"
assert String.at("elixir",6) == nil
end
end
+187 -63
View File
@@ -6,7 +6,7 @@ defmodule Typespec.Test.Type do
# This macro allows us to focus on the result of the
# definition and not on the hassles of handling test
# module
defmacro test_module([{:do, block}]) do
defmacrop test_module([{:do, block}]) do
quote do
{ :module, T, _binary, result } = defmodule T do
unquote(block)
@@ -21,42 +21,42 @@ defmodule Typespec.Test.Type do
spec = test_module do
@type mytype
end
assert {:type,{:mytype,{:type,_,:term,[]},[]}} = spec
assert {:mytype,{:type,_,:term,[]},[]} = spec
end
test "@type with a single type" do
spec = test_module do
@type mytype :: term
end
assert {:type,{:mytype,{:type,_,:term,[]},[]}} = spec
assert {:mytype,{:type,_,:term,[]},[]} = spec
end
test "@type with an atom" do
spec = test_module do
@type mytype :: :atom
end
assert {:type,{:mytype,{:atom,_,:atom},[]}} = spec
assert {:mytype,{:atom,_,:atom},[]} = spec
end
test "@type with an atom alias" do
spec = test_module do
@type mytype :: Atom
end
assert {:type,{:mytype,{:atom,_,Atom},[]}} = spec
assert {:mytype,{:atom,_,Atom},[]} = spec
end
test "@type with an integer" do
spec = test_module do
@type mytype :: 10
end
assert {:type,{:mytype,{:integer,_,10},[]}} = spec
assert {:mytype,{:integer,_,10},[]} = spec
end
test "@type with a negative integer" do
spec = test_module do
@type mytype :: -10
end
assert {:type,{:mytype,{:op, _, :-, {:integer,_,10}},[]}} = spec
assert {:mytype,{:op, _, :-, {:integer,_,10}},[]} = spec
end
test "@type with a remote type" do
@@ -65,50 +65,50 @@ defmodule Typespec.Test.Type do
t2 = @type mytype_arg :: Remote.type(integer)
{t1, t2}
end
assert {:type,{:mytype,{:remote_type, _, [{:atom, _, Remote.Some},{:atom, _, :type}, []]},[]}} = spec1
assert {:type,{:mytype_arg,{:remote_type, _, [{:atom, _, Remote},{:atom, _, :type},[{:type,_, :integer,[]}]]},[]}} = spec2
assert {:mytype,{:remote_type, _, [{:atom, _, Remote.Some},{:atom, _, :type}, []]},[]} = spec1
assert {:mytype_arg,{:remote_type, _, [{:atom, _, Remote},{:atom, _, :type},[{:type,_, :integer,[]}]]},[]} = spec2
end
test "@type with a binary" do
spec = test_module do
@type mytype :: binary
end
assert {:type,{:mytype,{:type,_,:binary, []},[]}} = spec
assert {:mytype,{:type,_,:binary, []},[]} = spec
end
test "@type with an empty binary" do
spec = test_module do
@type mytype :: <<>>
end
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 0}]},[]}} = spec
assert {:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 0}]},[]} = spec
end
test "@type with a binary with a base size" do
spec = test_module do
@type mytype :: <<_|3>>
@type mytype :: <<_ :: 3>>
end
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 3},{:integer, _, 0}]},[]}} = spec
assert {:mytype,{:type,_,:binary, [{:integer, _, 3},{:integer, _, 0}]},[]} = spec
end
test "@type with a binary with a unit size" do
spec = test_module do
@type mytype :: <<_|_ * 8>>
@type mytype :: <<_ :: _ * 8>>
end
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 8}]},[]}} = spec
assert {:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 8}]},[]} = spec
end
test "@type with a range" do
spec = test_module do
@type mytype :: range(1,10)
end
assert {:type,{:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]}} = spec
assert {:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]} = spec
end
test "@type with a range op" do
spec = test_module do
@type mytype :: 1..10
end
assert {:type,{:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]}} = spec
assert {:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]} = spec
end
test "@type with a tuple" do
@@ -117,50 +117,58 @@ defmodule Typespec.Test.Type do
t2 = @type mytype1 :: {}
{t1, t2}
end
assert {:type,{:mytype,{:type,_,:tuple, :any},[]}} = spec1
assert {:type,{:mytype1,{:type,_,:tuple, :any},[]}} = spec2
assert {:mytype,{:type,_,:tuple, :any},[]} = spec1
assert {:mytype1,{:type,_,:tuple, :any},[]} = spec2
end
test "@type with a fun" do
spec = test_module do
@type mytype :: fun
end
assert {:type,{:mytype,{:type,_,:fun, []},[]}} = spec
assert {:mytype,{:type,_,:fun, []},[]} = spec
end
test "@type with a fun with arguments and return type" do
spec = test_module do
@type mytype :: fun(integer, integer, do: integer)
end
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :product,
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
{:type, _, :integer, []}]},[]}} = spec
assert {:mytype,{:type,_,:fun, [{:type, _, :product,
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
{:type, _, :integer, []}]},[]} = spec
end
test "@type with a fun with no arguments and return type" do
spec = test_module do
@type mytype :: fun(do: integer)
end
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :product, []},
{:type, _, :integer, []}]}, []}} = spec
assert {:mytype,{:type,_,:fun, [{:type, _, :product, []},
{:type, _, :integer, []}]}, []} = spec
end
test "@type with a fun with any arity and return type" do
spec = test_module do
@type mytype :: fun(..., do: integer)
end
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :any},
{:type, _, :integer, []}]}, []}} = spec
assert {:mytype,{:type,_,:fun, [{:type, _, :any},
{:type, _, :integer, []}]}, []} = spec
end
test "@type with a union" do
spec = test_module do
@type mytype :: integer | string
@type mytype :: integer | char_list
| atom
end
assert {:type,{:mytype,{:type,_,:union, [{:type, _, :integer, []},
{:type, _, :string, []},
{:type, _, :atom, []}]}, []}} = spec
assert {:mytype,{:type,_,:union, [{:type, _, :integer, []},
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]},
{:type, _, :atom, []}]}, []} = spec
end
test "@type with an access macro" do
spec = test_module do
@type mytype :: Range[first: integer]
end
assert {:mytype,{:type, _, :tuple,
[{:atom, _, Range}, {:type, _, :integer, []}, {:type, _, :any, []}]}, []} = spec
end
test "@type with parameters" do
@@ -170,9 +178,9 @@ defmodule Typespec.Test.Type do
t3 = @type mytype2(x,y) :: {x,y}
{t1,t2,t3}
end
assert {:type,{:mytype,{:var,_,:x},[{:var,_,:x}]}} = spec1
assert {:type,{:mytype1,{:type,_,:list,[{:var,_,:x}]},[{:var,_,:x}]}} = spec2
assert {:type,{:mytype2,{:type,_,:tuple,[{:var,_,:x},{:var,_,:y}]},[{:var,_,:x},{:var, _, :y}]}} = spec3
assert {:mytype,{:var,_,:x},[{:var,_,:x}]} = spec1
assert {:mytype1,{:type,_,:list,[{:var,_,:x}]},[{:var,_,:x}]} = spec2
assert {:mytype2,{:type,_,:tuple,[{:var,_,:x},{:var,_,:y}]},[{:var,_,:x},{:var, _, :y}]} = spec3
end
test "@type with annotations" do
@@ -181,28 +189,37 @@ defmodule Typespec.Test.Type do
t2 = @type mytype1 :: fun(a :: integer, do: integer)
{t1,t2}
end
assert {:type, {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []}} = spec1
assert {:type, {:mytype1,{:type,_,:fun,[{:type,_,:product,[{:ann_type,_,[{:var,_,:a},{:type,_,:integer,[]}]}]},{:type,_,:integer,[]}]},[]}} = spec2
assert {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []} = spec1
assert {:mytype1,{:type,_,:fun,[{:type,_,:product,[{:ann_type,_,[{:var,_,:a},{:type,_,:integer,[]}]}]},{:type,_,:integer,[]}]},[]} = spec2
end
test "opaque @type" do
test "@opaque(type)" do
spec = test_module do
@type mytype(x) :: x, opaque: true
@opaque mytype(x) :: x
end
assert {:opaque,{:mytype,{:var,_,:x},[{:var,_,:x}]}} = spec
assert {:mytype,{:var,_,:x},[{:var,_,:x}]} = spec
end
test "get_types" do
test "@type + opaque" do
types = test_module do
import Kernel.Typespec
@type mytype :: tuple
@type mytype1 :: {}, opaque: true
get_types(__MODULE__)
@opaque mytype1 :: {}
@type ++ @opaque
end
assert [{:mytype,_,[]},{:mytype1,_,[]}] = types
end
test "@spec" do
test "defines_type?" do
test_module do
@type mytype :: tuple
@type mytype(a) :: [a]
assert Kernel.Typespec.defines_type?(__MODULE__, :mytype, 0)
assert Kernel.Typespec.defines_type?(__MODULE__, :mytype, 1)
refute Kernel.Typespec.defines_type?(__MODULE__, :mytype, 2)
end
end
test "@spec(spec)" do
{spec1, spec2, spec3} = test_module do
def myfun(x), do: x
def myfun(), do: :ok
@@ -212,40 +229,147 @@ defmodule Typespec.Test.Type do
t3 = @spec myfun(integer, integer), do: {integer, integer}
{t1,t2,t3}
end
assert {{:spec, {:myfun,1}},[{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}]} = spec1
assert {{:spec, {:myfun,0}},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:integer,[]}]}]} = spec2
assert {{:spec, {:myfun,2}},[{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}]} = spec3
assert {{:myfun,1},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}} = spec1
assert {{:myfun,0},{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:integer,[]}]}} = spec2
assert {{:myfun,2},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}} = spec3
end
test "@callback" do
test "@callback(callback)" do
{spec1, spec2, spec3} = test_module do
t1 = @callback myfun(integer), do: integer
t2 = @callback myfun(), do: integer
t3 = @callback myfun(integer, integer), do: {integer, integer}
{t1,t2,t3}
end
assert {{:callback, {:myfun,1}},[{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}]} = spec1
assert {{:callback, {:myfun,0}},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:integer,[]}]}]} = spec2
assert {{:callback, {:myfun,2}},[{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}]} = spec3
assert {{:myfun,1},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}} = spec1
assert {{:myfun,0},{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:integer,[]}]}} = spec2
assert {{:myfun,2},{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]},{:type,_,:integer,[]}]},{:type,_,:tuple,[{:type,_,:integer,[]},{:type,_,:integer,[]}]}]}} = spec3
end
test "get_specs" do
specs = test_module do
import Kernel.Typespec
test "@spec + @callback" do
{ specs, callbacks } = test_module do
def myfun(x), do: x
@spec myfun(integer), do: integer
@spec myfun(string), do: string
@spec myfun(char_list), do: char_list
@callback cb(integer), do: integer
get_specs(__MODULE__)
{ @spec, @callback }
end
assert [
{{:callback, {:cb, 1}},[
{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]}
]},
{{:spec, {:myfun,1}},[
{:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]},
{:type,_,:fun,[{:type,_,:product,[{:type,_,:string,[]}]},{:type,_,:string,[]}]}]}
] = List.sort(specs)
{ {:cb, 1}, {:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]} }
] = List.sort(callbacks)
assert [
{ {:myfun,1}, {:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]} },
{ {:myfun,1}, {:type,_,:fun,[{:type,_,:product,[
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]}]},
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]}]} }
] = List.sort(specs)
end
# Conversion to AST
test "type_to_ast" do
quoted = [
(quote do: @type with_ann() :: (t :: atom())),
(quote do: @type empty_tuple_type() :: {}),
(quote do: @type imm_type_1() :: 1),
(quote do: @type imm_type_2() :: :atom),
(quote do: @type simple_type() :: integer()),
(quote do: @type param_type(p) :: list(p)),
(quote do: @type union_type() :: integer() | binary() | boolean()),
(quote do: @type binary_type1() :: <<_ :: _ * 8>>),
(quote do: @type binary_type2() :: <<_ :: 3 * 8>>),
(quote do: @type binary_type3() :: <<_ :: 3>>),
(quote do: @type tuple_type() :: {integer()}),
(quote do: @type ftype() :: fun() | fun(do: integer()) | fun(integer(), do: integer())),
]
types = test_module do
Module.eval_quoted __MODULE__, quote do: unquote_splicing(quoted)
@type ++ @opaque
end
types = Enum.reverse(types)
lc { type, definition } inlist Enum.zip(types, quoted) do
ast = Kernel.Typespec.type_to_ast(type)
assert Macro.to_binary(quote do: @type unquote(ast)) == Macro.to_binary(definition)
end
end
test "type_to_ast for records" do
record_type = { { :record, :my_record },
[
{ :typed_record_field,
{ :record_field, 0, { :atom, 0, :field1 }},
{ :type, 0, :atom, [] } },
{ :typed_record_field,
{ :record_field, 0, { :atom, 0, :field2 }},
{ :type, 0, :integer, [] } },
],
[]}
assert Kernel.Typespec.type_to_ast(record_type) == quote hygiene: false, do: my_record() :: {:my_record, field1 :: atom(), field2 :: integer() }
end
test "spec_to_ast" do
specs = [
(quote do: @spec a(), do: integer()),
(quote do: @spec a(atom()), do: integer()),
]
compiled = test_module do
def a, do: 1
def a(a), do: a
Module.eval_quoted __MODULE__, quote do: unquote_splicing(specs)
Enum.reverse @spec
end
lc { { { name, _ }, spec }, definition } inlist Enum.zip(compiled, specs) do
{ args, result } = Kernel.Typespec.spec_to_ast(spec)
quoted = quote do: @spec unquote(name)(unquote_splicing(args)), do: unquote(result)
assert Macro.to_binary(quoted) == Macro.to_binary(definition)
end
end
# types/specs retrieval
test "specs retrieval" do
Code.compiler_options debug_info: true
{ :module, T, binary, _ } = defmodule T do
@spec a, do: any
def a, do: nil
end
:code.delete(T)
:code.purge(T)
assert [{{:a,_},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:any,[]}]}]}] = Kernel.Typespec.beam_specs(binary)
after
Code.compiler_options debug_info: false
end
test "types retrieval" do
Code.compiler_options debug_info: true
{ :module, T, binary, _ } = defmodule T do
@type a :: any
@typep b :: any
@spec t(b), do: b
def t(b), do: b
@opaque c :: any
end
:code.delete(T)
:code.purge(T)
assert [
{:opaque, {:c,{:type,_,:any,[]},[]}},
{:type, {:a,{:type,_,:any,[]},[]}},
{:typep, {:b,{:type,_,:any,[]},[]}},
] = Kernel.Typespec.beam_types(binary)
after
Code.compiler_options debug_info: false
end
end
+2
View File
@@ -1,3 +1,5 @@
Code.require_file "../test_helper.exs", __FILE__
defmodule URITest do
use ExUnit.Case, async: true
+16
View File
@@ -35,6 +35,22 @@ function_apply_with_args_test() ->
function_apply_and_clojure_test() ->
{3,_} = eval("b = 1; a = fn -> b + 2 end; apply a, []").
function_parens_test() ->
{0,_} = eval("(fn() -> 0 end).()"),
{1,_} = eval("(fn(1) -> 1 end).(1)"),
{3,_} = eval("(fn(1, 2) -> 3 end).(1, 2)"),
{0,_} = eval("(fn () -> 0 end).()"),
{1,_} = eval("(fn (1) -> 1 end).(1)"),
{3,_} = eval("(fn (1, 2) -> 3 end).(1, 2)").
function_macro_parens_test() ->
{0,_} = eval("(function do () -> 0 end).()"),
{1,_} = eval("(function do 1 -> 1 end).(1)"),
{3,_} = eval("(function do 1, 2 -> 3 end).(1, 2)"),
{1,_} = eval("(function do (1) -> 1 end).(1)"),
{3,_} = eval("(function do (1, 2) -> 3 end).(1, 2)").
%% Function calls
function_call_test() ->
+2 -2
View File
@@ -29,8 +29,8 @@ run_and_remove(Fun, Modules) ->
% Throws an error with the Erlang Abstract Form from the Elixir string
throw_elixir(String) ->
Forms = elixir_translator:'forms!'(String, 1, "nofile"),
Tree = elixir_translator:translate(Forms, #elixir_scope{}),
Forms = elixir_translator:'forms!'(String, 1, "nofile", []),
Tree = elixir_translator:translate(Forms, elixir:scope_for_eval([])),
erlang:error(io:format("~p~n", [Tree])).
% Throws an error with the Erlang Abstract Form from the Erlang string
@@ -79,6 +79,11 @@ dot_test() ->
{'.',1},
{identifier,1,baz}] = tokenize("foo.bar.baz").
dot_keyword_test() ->
[{identifier,1,foo},
{'.',1},
{identifier,1,do}] = tokenize("foo.do").
newline_test() ->
[{identifier,1,foo},
{'.',2},
+1 -1
View File
@@ -8,7 +8,7 @@ eval(Forms) ->
Result.
eval(Forms, Binding) ->
{ Transformed, _ } = elixir_translator:translate(Forms, #elixir_scope{}),
{ Transformed, _ } = elixir_translator:translate(Forms, elixir:scope_for_eval([])),
{ value, Result, NewBinding } = erl_eval:exprs(Transformed, Binding),
{ Result, NewBinding }.
+36 -4
View File
@@ -43,8 +43,26 @@ defmodule ExUnit do
Start ExUnit. Required to be invoked before loading
any file that uses ExUnit.Case. Check `configure/1`
to see the supported options.
This function will also try to read a user config from the following
locations, in this order:
* $EXUNIT_CONFIG environment variable
* $HOME/.ex_unit.exs
If none found, no user config will be read.
User config is an elixir file which should return a keyword list
with ex_unit options. Please note that explicit options passed to start/1
will take precedence over user options.
# User config example (~/.ex_unit.exs)
[formatter: ExUnit.Formatter.ANSI]
"""
def start(options // []) do
options = Keyword.merge(user_options, options)
ExUnit.Server.start_link
configure(options)
System.at_exit fn status ->
@@ -52,6 +70,21 @@ defmodule ExUnit do
end
end
@doc false
def user_options(user_config // nil) do
user_config = user_config ||
System.get_env("EXUNIT_CONFIG") ||
File.join(System.get_env("HOME"), ".ex_unit.exs")
case File.read(user_config) do
{ :ok, contents } ->
{ config, _ } = Code.eval(contents, [], file: user_config)
config
_ ->
[]
end
end
@doc """
Configure ExUnit.
@@ -80,9 +113,8 @@ defmodule ExUnit do
need to call it directly.
"""
def run do
config = ExUnit.Runner.Config.new ExUnit.Server.options
config = config.formatter(config.formatter.start)
failures = ExUnit.Runner.loop config
if failures > 0, do: halt(1), else: halt(0)
config = ExUnit.Runner.Config.new ExUnit.Server.options
failures = ExUnit.Runner.run config
if failures > 0, do: System.halt(1), else: System.halt(0)
end
end
+5 -10
View File
@@ -69,15 +69,8 @@ defmodule ExUnit.Assertions do
quote do: (var!(op) == :! or var!(op) == :not)
end
defp translate_assertion({ :=, _, [expected, received] }, _else) do
quote do
try do
unquote(expected) = unquote(received)
rescue
x in [MatchError] ->
raise ExUnit.AssertionError, message: x.message
end
end
defp translate_assertion({ :=, _, [_, _] } = expr, _else) do
expr
end
defp translate_assertion({ :==, _, [left, right] }, _else) do
@@ -132,7 +125,7 @@ defmodule ExUnit.Assertions do
unquote(expected) = x = unquote(received)
flunk "Unexpected right side #{inspect x} match"
rescue
x in [MatchError] -> true
_ in [MatchError] -> true
end
end
end
@@ -387,6 +380,8 @@ defmodule ExUnit.Assertions do
flunk "This should raise an error"
"""
@spec flunk, do: no_return
@spec flunk(String.t), do: no_return
def flunk(message // "Epic Fail!") do
raise ExUnit.AssertionError, message: message
end
+91
View File
@@ -0,0 +1,91 @@
defmodule ExUnit.CLIFormatter do
@moduledoc """
Formatter responsible for printing raw text
on the CLI
"""
@behaviour ExUnit.Formatter
use GenServer.Behaviour
import Exception, only: [format_stacktrace: 1]
defrecord Config, counter: 0, failures: []
## Behaviour
def suite_started do
:gen_server.start_link({ :local, __MODULE__ }, __MODULE__, [], [])
end
def suite_finished do
:gen_server.call(__MODULE__, :suite_finished)
end
def case_started(_) do
:ok
end
def case_finished(_) do
:ok
end
def test_started(_test_case, _test) do
:ok
end
def test_finished(test_case, test, result) do
:gen_server.cast(__MODULE__, { :test_finished, test_case, test, result })
end
## Callbacks
def init(_args) do
{ :ok, Config.new }
end
def handle_call(:suite_finished, _from, config) do
IO.write "\n\n"
Enum.reduce Enum.reverse(config.failures), 1, print_failure(&1, &2)
failures_count = length(config.failures)
IO.puts "#{config.counter} tests, #{failures_count} failures."
{ :stop, :normal, failures_count, config }
end
def handle_call(_, _, _) do
super
end
def handle_cast({ :test_finished, _test_case, _test, nil }, config) do
IO.write "."
{ :noreply, config.increment_counter }
end
def handle_cast({ :test_finished, test_case, test, failure }, config) do
IO.write "F"
{ :noreply, config.increment_counter.
prepend_failures([{test_case, test, failure}]) }
end
def handle_cast(_, _) do
super
end
defp print_failure({test_case, test, { kind, reason, stacktrace }}, acc) do
IO.puts "#{acc}) #{test} (#{inspect test_case})"
IO.puts " ** #{format_catch(kind, reason)}\n stacktrace:"
Enum.each filter_stacktrace(stacktrace), fn(s) -> IO.puts " #{format_stacktrace(s)}" end
IO.write "\n"
acc + 1
end
defp format_catch(:error, exception) do
"(#{inspect exception.__record__(:name)}) #{exception.message}"
end
defp format_catch(kind, reason) do
"(#{kind}) #{inspect(reason)}"
end
defp filter_stacktrace([{ ExUnit.Assertions, _, _, _ }|t]), do: filter_stacktrace(t)
defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
defp filter_stacktrace([]), do: []
end
+15 -57
View File
@@ -1,63 +1,21 @@
defmodule ExUnit.Formatter do
# TODO: Eventually we need to come up with a
# public API for the formatter to allow customization
@moduledoc false
@moduledoc """
This module simply defines the callbacks
expected by an ExUnit.Formatter.
"""
use GenServer.Behaviour
use Behaviour
defrecord Config, counter: 0, failures: []
@type test_case :: module
@type test :: atom
@type result :: { kind :: atom, reason :: term, stacktrace :: list } | nil
import Exception, only: [format_stacktrace: 1]
defcallback suite_started(), do: any
defcallback suite_finished(), do: non_neg_integer
def start do
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
pid
end
defcallback case_started(test_case), do: any
defcallback case_finished(test_case), do: any
def init(_args) do
{ :ok, Config.new }
end
def handle_call({:each, _test_case, _test, nil }, _from, config) do
IO.write "."
{ :reply, :ok, config.increment_counter }
end
def handle_call({:each, test_case, test, failure }, _from, config) do
IO.write "F"
{ :reply, :ok, config.increment_counter.
prepend_failures([{test_case, test, failure}]) }
end
def handle_call({:each_case, _test_case}, _from, config) do
{ :reply, :ok, config }
end
def handle_call(:finish, _from, config) do
IO.write "\n\n"
Enum.reduce Enum.reverse(config.failures), 1, print_failure(&1, &2)
failures_count = length(config.failures)
IO.puts "#{config.counter} tests, #{failures_count} failures."
{ :reply, failures_count, config }
end
defp print_failure({test_case, test, { kind, reason, stacktrace }}, acc) do
IO.puts "#{acc}) #{test} (#{inspect test_case})"
IO.puts " ** #{format_catch(kind, reason)}\n stacktrace:"
Enum.each filter_stacktrace(stacktrace), fn(s) -> IO.puts " #{format_stacktrace(s)}" end
IO.write "\n"
acc + 1
end
defp format_catch(:error, exception) do
"(#{inspect exception.__record__(:name)}) #{exception.message}"
end
defp format_catch(kind, reason) do
"(#{kind}) #{inspect(reason)}"
end
defp filter_stacktrace([{ ExUnit.Assertions, _, _, _ }|t]), do: filter_stacktrace(t)
defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
defp filter_stacktrace([]), do: []
end
defcallback test_started(test_case, test), do: any
defcallback test_finished(test_case, test, result), do: any
end
+27 -22
View File
@@ -1,9 +1,14 @@
defmodule ExUnit.Runner do
@moduledoc false
defrecord Config, formatter: ExUnit.Formatter, max_cases: 4, taken_cases: 0
defrecord Config, formatter: ExUnit.CLIFormatter, max_cases: 4, taken_cases: 0
def loop(config) do
def run(config) do
config.formatter.suite_started
loop config
end
defp loop(config) do
available = config.max_cases - config.taken_cases
cond do
@@ -13,7 +18,7 @@ defmodule ExUnit.Runner do
# Slots are available, start with async cases
cases = ExUnit.Server.take_async_cases(available) ->
spawn_cases(cases, config)
spawn_cases(config, cases)
# No more async cases, wait for them to finish
config.taken_cases > 0 ->
@@ -21,11 +26,11 @@ defmodule ExUnit.Runner do
# So we can start all sync cases
cases = ExUnit.Server.take_sync_cases ->
spawn_cases(cases, config)
spawn_cases(config, cases)
# No more cases, we are done!
true ->
call_formatter config, :finish
config.formatter.suite_finished
end
end
@@ -34,40 +39,44 @@ defmodule ExUnit.Runner do
# cases counter and attempt to spawn new ones.
defp wait_until_available(config) do
receive do
{ pid, :each, { test_case, test, final } } ->
call_formatter config, { :each, test_case, test, final }
{ pid, :test_finished, { test_case, test, final } } ->
config.formatter.test_finished(test_case, test, final)
wait_until_available config
{ pid, :each_case, test_case } ->
call_formatter config, { :each_case, test_case }
{ pid, :case_finished, test_case } ->
config.formatter.case_finished(test_case)
loop config.increment_taken_cases(-1)
end
end
defp spawn_cases(cases, config) do
Enum.each cases, spawn_case(&1)
defp spawn_cases(config, cases) do
Enum.each cases, spawn_case(config, &1)
loop config.increment_taken_cases(length(cases))
end
defp spawn_case(test_case) do
defp spawn_case(config, test_case) do
pid = self()
spawn_link fn ->
ExUnit.Server.run_after_spawn
run_tests(pid, test_case)
run_tests(config, pid, test_case)
end
end
defp run_tests(pid, test_case) do
defp run_tests(config, pid, test_case) do
config.formatter.case_started(test_case)
try do
tests = tests_for(test_case)
context = run_setup_all(test_case)
Enum.each tests, run_test(pid, test_case, context, &1)
Enum.each tests, run_test(config, pid, test_case, context, &1)
run_teardown_all(test_case, context)
after
pid <- { self, :each_case, test_case }
pid <- { self, :case_finished, test_case }
end
end
defp run_test(pid, test_case, setup_context, test) do
defp run_test(config, pid, test_case, setup_context, test) do
config.formatter.test_started(test_case, test)
final = try do
context = run_setup(test_case, setup_context, test)
@@ -92,7 +101,7 @@ defmodule ExUnit.Runner do
{ kind2, error2, System.stacktrace }
end
pid <- { self, :each, { test_case, test, final } }
pid <- { self, :test_finished, { test_case, test, final } }
end
defp run_setup_all(test_case) do
@@ -141,10 +150,6 @@ defmodule ExUnit.Runner do
end
end
defp call_formatter(config, message) do
:gen_server.call(config.formatter, message)
end
defp tests_for(mod) do
exports = mod.__info__(:functions)
lc { function, 0 } inlist exports, is_test?(atom_to_list(function)), do: function

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