Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9033b217d9 | ||
|
|
1d9548fd28 | ||
|
|
9a6c195880 | ||
|
|
3eb938a0ba | ||
|
|
66f1e74e0c | ||
|
|
7c382b94af | ||
|
|
9297a9167e | ||
|
|
0410a1cec9 | ||
|
|
b545a1e491 | ||
|
|
c08b5b11c6 | ||
|
|
8939b39fc2 | ||
|
|
e12a0c561e | ||
|
|
61c3e82123 | ||
|
|
d43b41695d | ||
|
|
fdca8b2657 | ||
|
|
dec06d8c82 | ||
|
|
11c84cbd2e | ||
|
|
67501bcddc | ||
|
|
1f05e7ce73 | ||
|
|
02c279af12 | ||
|
|
2c2fb820ab | ||
|
|
1ad1d6d6b0 | ||
|
|
83d8161959 | ||
|
|
b87ea61713 | ||
|
|
c355ab6482 | ||
|
|
f0ed2d48ce | ||
|
|
d23a46fa9f | ||
|
|
6c0cbbd73e | ||
|
|
d58ef57e13 | ||
|
|
1a9493534e | ||
|
|
45fe1cb084 | ||
|
|
3f0e7c5613 | ||
|
|
49e76833d6 | ||
|
|
7963e44a5e | ||
|
|
3ba5e44dae | ||
|
|
1d74ddac78 | ||
|
|
c1e3d4f976 | ||
|
|
b4fc2b35fc | ||
|
|
18a5d8affb | ||
|
|
cd80994a70 | ||
|
|
e02d7bf913 | ||
|
|
fe5ff41514 | ||
|
|
e9887a760e | ||
|
|
0559fe5927 | ||
|
|
1a05bff45d | ||
|
|
4d6670342b | ||
|
|
193e85882e | ||
|
|
a58c007e0c | ||
|
|
ce6f4e6d15 | ||
|
|
321096665a | ||
|
|
b93aae9f86 | ||
|
|
a503069479 | ||
|
|
120ea0fc8a | ||
|
|
d725c66d93 | ||
|
|
fab917dba3 | ||
|
|
23fb92f444 | ||
|
|
db61109495 | ||
|
|
c4e8ad0d65 | ||
|
|
ce77fa54b9 | ||
|
|
eee921bf7b | ||
|
|
73c6a0be8b | ||
|
|
7644bd87a5 | ||
|
|
9e2c5a29a4 | ||
|
|
09498422d0 | ||
|
|
a697bab7f2 | ||
|
|
36e81d587e | ||
|
|
17a0abd57a | ||
|
|
e6cfc20556 | ||
|
|
906f03102b | ||
|
|
6a594e2bba | ||
|
|
10dd8f843b | ||
|
|
53ecea8b03 | ||
|
|
efba8d8bfa | ||
|
|
5a201065dc | ||
|
|
307675284c | ||
|
|
38a9513111 | ||
|
|
578f798cc1 | ||
|
|
efbdf4b9b0 | ||
|
|
9679bed4d7 | ||
|
|
b6c0045fc2 | ||
|
|
10612ddf22 | ||
|
|
cbcbbcf876 | ||
|
|
95b81c2a13 | ||
|
|
a525f71e13 | ||
|
|
750d2a3f7a | ||
|
|
61e9fafb73 | ||
|
|
aa48f6a881 | ||
|
|
b5e2d45b7d | ||
|
|
29f2eb7dab | ||
|
|
a3754e3841 | ||
|
|
337c040da8 | ||
|
|
b4d465e2ed | ||
|
|
0bb3d1c6bd | ||
|
|
86476615ef | ||
|
|
860b3cc5a7 | ||
|
|
bb5e6f65f5 | ||
|
|
95127516f7 | ||
|
|
ef0c495f16 | ||
|
|
2472bb79ab | ||
|
|
04b9174d75 | ||
|
|
d31a58f0d9 | ||
|
|
f97ab2649c | ||
|
|
d969d42784 | ||
|
|
17c4c4d993 | ||
|
|
0f7a968389 | ||
|
|
272ec8ca47 | ||
|
|
a0e8e1c0fb | ||
|
|
b477dba34f | ||
|
|
8d524ef971 | ||
|
|
83cb5152fd | ||
|
|
3c100575a3 | ||
|
|
d1b6161f2e | ||
|
|
785781d089 | ||
|
|
b2ac4c9394 | ||
|
|
50ac792747 | ||
|
|
7d433e80e5 | ||
|
|
f9cb812bc1 | ||
|
|
f8711f2e36 | ||
|
|
0251f564d9 | ||
|
|
e4acc08cf5 | ||
|
|
2ab5d25787 | ||
|
|
acde8078f8 | ||
|
|
487ac9c1c4 | ||
|
|
f0f65f8bec | ||
|
|
23943b6180 | ||
|
|
d9125b0a81 | ||
|
|
d1d46db907 | ||
|
|
02056d1030 | ||
|
|
14a3686a3f | ||
|
|
c04b182c6c | ||
|
|
f5462d98e0 | ||
|
|
75d0c07b46 | ||
|
|
1de8dcb695 | ||
|
|
0e167bef27 | ||
|
|
34df817638 | ||
|
|
311a5790d6 | ||
|
|
b4c75f97b4 | ||
|
|
70692e6618 | ||
|
|
529d584262 | ||
|
|
c0fb275d5b | ||
|
|
24f46be144 | ||
|
|
0fe3da9dea | ||
|
|
03d0ad3f03 | ||
|
|
a87f43701f | ||
|
|
dce3a3a2a5 | ||
|
|
7b20084b28 | ||
|
|
542808102f | ||
|
|
fd18b6a41f | ||
|
|
4c9c95f80f | ||
|
|
89460154bb | ||
|
|
b026e8877a | ||
|
|
597381b2e0 | ||
|
|
df70729b14 | ||
|
|
61b116c1a5 | ||
|
|
d37a10bbd0 | ||
|
|
5d75c5c861 | ||
|
|
db4089cc71 | ||
|
|
f9f4f14edf | ||
|
|
58946ce879 | ||
|
|
de1d5ff793 | ||
|
|
a4be8fb260 | ||
|
|
031a27f5fa | ||
|
|
f3c1931cac | ||
|
|
2e48d81c20 | ||
|
|
e7ef3fc95d | ||
|
|
2d01aa1c6a | ||
|
|
ae07124550 | ||
|
|
1f744f4dac | ||
|
|
ad88ac642f | ||
|
|
f7956e9d80 | ||
|
|
da6892aa96 | ||
|
|
7a3bd20804 | ||
|
|
f7015166dc | ||
|
|
c6f346a355 | ||
|
|
911a3ba97f | ||
|
|
a1668a6b28 | ||
|
|
a4cb74a83f | ||
|
|
bbf9552186 | ||
|
|
e621b5828e | ||
|
|
68e282c50a | ||
|
|
403a50fbcd | ||
|
|
8e98cd4f41 | ||
|
|
499c385000 | ||
|
|
aecafc099d | ||
|
|
785c7d700a | ||
|
|
cecc8a0f95 | ||
|
|
52ff7e9686 | ||
|
|
e2eb2cfdb1 | ||
|
|
a5b4c1c04d | ||
|
|
e38f960d7e | ||
|
|
9e74ee686e | ||
|
|
e068625d7d | ||
|
|
00e8446a58 | ||
|
|
2aecfede5b | ||
|
|
9eebefb976 | ||
|
|
b709cba371 | ||
|
|
1d919f59e3 | ||
|
|
0e8d3f9208 | ||
|
|
8931f34582 | ||
|
|
843706e36e | ||
|
|
172bfdb128 | ||
|
|
2c741ec6ae | ||
|
|
12a457829f | ||
|
|
0ff6850237 | ||
|
|
63f6088476 | ||
|
|
206f6413f8 | ||
|
|
a2c0424b37 | ||
|
|
6ea7d3cef6 | ||
|
|
2cafd8c060 | ||
|
|
7271dfebfb | ||
|
|
2eff3590c1 | ||
|
|
b578f45060 | ||
|
|
a34ccfe129 | ||
|
|
8f4442f2df | ||
|
|
598e42e00b | ||
|
|
b93912b9dc | ||
|
|
71eca8a5d6 | ||
|
|
f0bb0e1f7f | ||
|
|
2cb0639208 | ||
|
|
081e3b36ec | ||
|
|
d521d7927f | ||
|
|
7313d26720 | ||
|
|
577a07d47f | ||
|
|
f6093d40d7 | ||
|
|
1dd034d4aa | ||
|
|
2417b196a4 | ||
|
|
50bb712e22 | ||
|
|
7b0747c907 | ||
|
|
b878e414de | ||
|
|
c447868e0f | ||
|
|
681d5aa9c4 | ||
|
|
0263dcc621 | ||
|
|
1ae24c5c5d | ||
|
|
6c02ebe4af | ||
|
|
a08e44c5a9 | ||
|
|
08f30098db | ||
|
|
5dbd91e4fa | ||
|
|
5b2eb54794 | ||
|
|
2c17007693 | ||
|
|
2770c32f3d | ||
|
|
8c8f6d8346 | ||
|
|
3950130e73 | ||
|
|
c8bcd89c7c | ||
|
|
7f7aaa115f | ||
|
|
53aeb26407 | ||
|
|
bba0d12510 | ||
|
|
035d8227a5 | ||
|
|
003a4da87e | ||
|
|
1977631b7f | ||
|
|
c9a0ec000a | ||
|
|
ba842ba113 | ||
|
|
6304919d0d | ||
|
|
d2d27e96e5 | ||
|
|
fc6f7c532c | ||
|
|
1bee85e518 | ||
|
|
66b211987d | ||
|
|
15fdae4de6 | ||
|
|
5b4497fa6b | ||
|
|
612e124ec9 | ||
|
|
abd2b35738 | ||
|
|
2afb875099 | ||
|
|
88507f65f8 | ||
|
|
275d1f5494 | ||
|
|
e6420c8d13 | ||
|
|
60bcf1cadb | ||
|
|
d86ca5826c | ||
|
|
2af00f4c33 | ||
|
|
0fe86e77c1 | ||
|
|
1ca451f779 | ||
|
|
ebe0c8cf8e | ||
|
|
de9feb26d5 | ||
|
|
b8528f6c8e | ||
|
|
c9fbbac391 | ||
|
|
98e1b4ccf8 | ||
|
|
ae19491620 | ||
|
|
64f3760b19 | ||
|
|
0b38bc050f | ||
|
|
9431338654 | ||
|
|
88be750a32 | ||
|
|
032c8b8291 | ||
|
|
2d90d8ac60 | ||
|
|
a8b2cb99ee | ||
|
|
68274311fb | ||
|
|
88f84f80b5 | ||
|
|
2bf898e8fe | ||
|
|
d45f451943 | ||
|
|
c0a075724f | ||
|
|
f1a39375c9 | ||
|
|
b9c1b22163 | ||
|
|
be75e3be17 | ||
|
|
9793caa1ef | ||
|
|
0792fe2c52 | ||
|
|
4408aa3450 | ||
|
|
e4ba8d5d21 | ||
|
|
689f14a867 | ||
|
|
aa5b414e8e | ||
|
|
07467a733b | ||
|
|
74391b793c | ||
|
|
f0b8bc1756 | ||
|
|
3e54b95bf2 | ||
|
|
313d26cb3b | ||
|
|
44b85be014 | ||
|
|
e344b1f9d8 | ||
|
|
f5f4205969 | ||
|
|
538c717ded | ||
|
|
8dd81e661a | ||
|
|
26b61b98f9 | ||
|
|
ab9450d92f | ||
|
|
a4a23b9a93 | ||
|
|
2f83ea5a7f | ||
|
|
7940d223ec | ||
|
|
c6fa951520 | ||
|
|
e5c8194a70 | ||
|
|
4a7e3dd11f | ||
|
|
638fccb79d | ||
|
|
e9d84ec43b | ||
|
|
bb35b9afb1 | ||
|
|
1496c04592 | ||
|
|
3e9725a5a8 | ||
|
|
1565ec7658 | ||
|
|
f1aa2f01c7 | ||
|
|
7284542116 | ||
|
|
4f17e8fd1c | ||
|
|
f536507b9d | ||
|
|
592300a0f0 | ||
|
|
28b1bd16de | ||
|
|
19fdd84f80 | ||
|
|
631e2a051a | ||
|
|
df0f7b5f95 | ||
|
|
a0dfc4160e | ||
|
|
ee8331023e | ||
|
|
a2ee71f5ab | ||
|
|
60163194e2 | ||
|
|
ba37682300 | ||
|
|
15265b9352 | ||
|
|
0a929310de | ||
|
|
a8670c1cfa | ||
|
|
ebd18d45fa | ||
|
|
ccbfec611a | ||
|
|
98a0999c2e | ||
|
|
e504142d3a | ||
|
|
c04e13169f | ||
|
|
96a4bff67b | ||
|
|
46de5ea462 | ||
|
|
22257b8342 | ||
|
|
a0be9fe272 | ||
|
|
1fd5a2a38e | ||
|
|
13aec18bd5 | ||
|
|
a85938d092 | ||
|
|
0d4af57abe | ||
|
|
e00db53112 | ||
|
|
b941618a10 | ||
|
|
d895c762a1 | ||
|
|
427c4b01c9 | ||
|
|
9d5a728cb8 | ||
|
|
1ed99d4ac7 | ||
|
|
ceb17fac7d | ||
|
|
c638f45542 | ||
|
|
c09905d537 | ||
|
|
117a428030 | ||
|
|
2ada7c40d7 | ||
|
|
cd162bf3d6 | ||
|
|
e6e71675ac | ||
|
|
c54e0caf6f | ||
|
|
b64552780e | ||
|
|
add8c66e82 | ||
|
|
aecbd2c16a | ||
|
|
32a649c1cd | ||
|
|
a1e657cb75 | ||
|
|
6bb4c4de03 | ||
|
|
ea5b56d434 | ||
|
|
a4082748cf | ||
|
|
4d763846c3 | ||
|
|
d56cc88f5f | ||
|
|
5e27b7b72b | ||
|
|
260d6d16ab | ||
|
|
1a82f126a4 | ||
|
|
3cf3eee2ea | ||
|
|
9e8a41b2d3 | ||
|
|
5f853f8e71 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
/.eunit
|
||||
/.release
|
||||
/docs
|
||||
/doc
|
||||
/ebin
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
|
||||
@@ -8,3 +8,4 @@ notifications:
|
||||
otp_release:
|
||||
- 17.0
|
||||
- 17.1
|
||||
- 17.3
|
||||
|
||||
+172
@@ -1,5 +1,177 @@
|
||||
# Changelog
|
||||
|
||||
## v1.0.5 (2015-06-29)
|
||||
|
||||
* Enhancements
|
||||
* [Elixir] Support 18.0
|
||||
* [IEx] Rely only on loaded applications for autocompletion on IEx
|
||||
* [Record] Expand attributes and macros in record extractor
|
||||
* [String] `Optimize String.rstrip/1`
|
||||
* [String] `Optimize String.downcase/1`
|
||||
* [String] `Optimize String.upcase/1`
|
||||
|
||||
* Bug fixes
|
||||
* [EEx] Ensure blocks do not clobber EEx buffers
|
||||
* [Enum] Ensure `Enum.take/2` does not consume one extra item when halting on the last emittable item
|
||||
* [ExUnit] Fix `StringIO` processes leakage in the `ExUnit.CaptureIO` when there are errors inside the `capture_io` block
|
||||
* [Float] Avoid rounding errors on `Float.parse/1`
|
||||
* [GenEvent] Fix `GenEvent` detection of modules that aren't loaded
|
||||
* [IO] Read 4K blocks instead of lines in `IO.binread/2`. This fixes a bug where CRLF were being ignored and is also going to improve performance
|
||||
* [Logger] Handle `:undefined` arity in `Logger.Translator` (we get :undefined when a temporary worker of a `simple_one_for_one` supervisor crashes)
|
||||
* [Mix] Ensure config is escaped before being injected into escripts. This fixes a bug where escripts failed to build when containing values like maps in config files
|
||||
* [Mix] Ensure we properly underscore acronyms followed by paths, for example, `HTTP.Foo`
|
||||
* [Stream] Ensure `Stream.flat_map/2` does not consume more items than necessary when piped to another `Stream.flat_map/2` that halts in the inner stream
|
||||
* [Version] Fix `to_string` for versions with numeric pre releases
|
||||
|
||||
## v1.0.4 (2015-04-07)
|
||||
|
||||
* Enhancements
|
||||
* [Elixir] Support Erlang 17.5 and 18.0-rc
|
||||
* [Mix] Support "--search PATTERN" in "mix help"
|
||||
* [Mix] Support `:start_permanent` that starts the application as permanent
|
||||
* [Mix] Support `:build_embedded` that compile protocols, avoid symlinks and ensure protocols are loaded on boot
|
||||
* [Mix] Make "mix app.start" public and support "--permanent" and "--temporary" options
|
||||
* [URI] Speed decoding operations
|
||||
|
||||
* Bug fixes
|
||||
* [Elixir] Produce clearer error messages for syntax errors before a sigil
|
||||
* [Elixir] Return value when matching on `_` in the last line of a block
|
||||
* [Elixir] Ensure changes in child deps forces the parent to recompile
|
||||
* [Kernel] Fix a bug where `=~/2` returned false when matching on an empty string. The current behaviour was changed to match the behaviour of `String.contains?/2`
|
||||
* [Mix] Store relative paths in erlang manifests
|
||||
* [Mix] Fix httpc proxy option profile for downloading hex and other safe packges
|
||||
* [Mix] Fix missing behaviour warning when compiling Erlang modules
|
||||
|
||||
## v1.0.3 (2015-02-12)
|
||||
|
||||
* Enhancements
|
||||
* [Elixir] Do not show exported vars warning
|
||||
* [Elixir] Improve error messages when using invalid expressions inside maps
|
||||
* [Elixir] Skip aliases and import warnings when code cannot compile
|
||||
* [IEx] Improve autocompletion for Erlang/Elixir based on loaded applications
|
||||
* [IO] Make `IO.ANSI.Docs` readable on white background
|
||||
* [Mix] Provide clearer error message when updating archives on Windows
|
||||
* [Mix] Don't go through hex.pm API server to install Hex
|
||||
|
||||
* Bug fixes
|
||||
* [Elixir] Ensure all Elixir applications work with code upgrades
|
||||
* [Elixir] Don't leave spurious processes and stale data when the parallel compiler fails
|
||||
* [Inspect] Keep original stacktrace on Inspect errors
|
||||
* [IO] Fix `IO.ANSI.Docs` handling of `*`, `**`, `_` where they would be activated in the middle of words
|
||||
* [Logger] Fix logging with `metadata[:function]`
|
||||
* [Mix] Don't show hex update message on mix `local.hex`
|
||||
* [Mix] Ensure tasks are properly underscored and camelized
|
||||
* [Mix] Ensure Mix does not use ANSI escapes on Windows unless ANSI is enabled
|
||||
* [Mix] Only shutdown Logger if the Mix application is being actually started
|
||||
* [Mix] Ensure priv/include are copied on Windows even if source did not change
|
||||
* [Mix] Ensure rebar dependencies work when user path has a space on Windows
|
||||
* [Regex] Fix `Regex.replace/4` failures when the number of substitutions in pattern is more than the number of matches and support `\g{...}` patterns
|
||||
* [String] Fix LFCR being treated as a grapheme instead of CRLF
|
||||
* [URI] Do more strict matching on URI schema
|
||||
|
||||
## v1.0.2 (2014-10-21)
|
||||
|
||||
* Bug fixes
|
||||
* [EEx] Remove invalid warning when attempting to inline code in EEx
|
||||
* [EEx] Unify syntax errors to always include `file:line:`
|
||||
* [IEx] Set null prompt function in dumb term mode
|
||||
* [Kernel] Do not define an ets table with the same name as the module as it clashes with user code
|
||||
* [Kernel] Fail compiling try expressions if they lack one of catch/after/rescue/else clauses
|
||||
* [Kernel] Improve error messages when parsing fails on aliases
|
||||
* [Kernel] Improve warnings coming from compile-time evaluation of arguments
|
||||
* [String] Fix `String.replace/4` typespec
|
||||
|
||||
## v1.0.1 (2014-10-07)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Add MIX_ENV output to archive.build (as on escript.build)
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Fix Elixir CLI when running on Cygwin
|
||||
* [ExUnit] Include stacktraces in ExUnit timeouts
|
||||
* [Mix] Load dependencies before `deps.check` compiles dependencies
|
||||
* [Mix] Ensure `Mix.Config` is deep merged
|
||||
* [Mix] Change version requirement in new apps to `~> MAJOR.MINOR`
|
||||
* [Mix] Report correct location if `local.hex` fails and give higher priority to powershell install on Windows
|
||||
* [Mix] Ensure `--elixirc-paths` option for `compile.elixir` is a subset of the project `:elixirc_paths` configuration and it does not remove skipped entries from the manifest
|
||||
* [Mix] Ensure `compile.elixir` writes to manifest when files are removed
|
||||
* [Mix] Ensure `compile.elixir` purges and deletes modules before compiling
|
||||
* [Mix] Do not crash on malformed proxy env var
|
||||
* [Stream] Ensure `chunk/4` works correctly when halted
|
||||
* [System] Do not allow relative paths in `System.cmd/3` as documented
|
||||
* [System] Fix `:stderr_to_stdout` option in `System.cmd/3`
|
||||
|
||||
## v1.0.0 (2014-09-10)
|
||||
|
||||
* Enhancements
|
||||
* [Logger] Add `Logger.enable/1` and `Logger.disable/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] No longer support `{:swap_handler, ...}` as callback return value
|
||||
* [List] `List.unzip/2` was removed
|
||||
|
||||
## v1.0.0-rc2 (2014-09-07)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Print process mailbox on failing `assert_receive`
|
||||
* [ExUnit] Tag all doctests with the `:doctest` tag
|
||||
* [Kernel] Friendlier `unquote_splicing` error on inline quote
|
||||
* [Kernel] Add `<~`, `~>`, `<<~`, `~>>`, `<~>`, `<|>` as operators
|
||||
* [Mix] `mix deps.unlock` warns when given a missing dependency
|
||||
* [Mix] Fetch hex using HTTPS
|
||||
|
||||
* Bug fixes
|
||||
* [File] Ensure `File.cwd` generates lowercase drive letters on Windows
|
||||
* [GenEvent] Ensure the event manager does not crash on `:sys.replace_state/3`
|
||||
* [GenEvent] Do not consider swap handlers to be failures in a stream
|
||||
* [Logger] Fix `Logger.log/2` not to raise on Logger exit
|
||||
* [Logger] Recompute sync/async mode on Logger configuration
|
||||
* [System] Ensure `System.cwd` generates lowercase drive letters on Windows
|
||||
|
||||
* Deprecations
|
||||
* [GenEvent] The `:monitor` option in `add_handler/4` and `swap_handler/6` is deprecated in favor of `add_mon_handler/3` and `swap_mon_handler/5`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Supervisor] Make `max_restarts` default to 3 (closer to upcoming Erlang defaults)
|
||||
|
||||
## v1.0.0-rc1 (2014-08-30)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Add timeouts to tests (which is configurable via tags)
|
||||
* [GenEvent] Add support for `GenEvent.ack_notify/2`
|
||||
* [GenEvent] Add support for monitored handlers to `GenEvent.add_handler/4` that removes the drawbacks in the previous linked handlers
|
||||
* [Logger] Allow any value that implements `String.Chars` to be logger
|
||||
* [Logger] Allow `Logger.log/3` timeout to be configured
|
||||
* [Mix] Add `--force` option to `mix local.rebar`
|
||||
* [Mix] Fix `--no-color`/`--color` option for `mix test`
|
||||
* [Mix] Add a `:language` option to improve the usability of Erlang projects using Mix
|
||||
* [Mix] Accept wildcards on `Mix.Config.import_config/1`
|
||||
* [Mix] Support protocol consolidation on escripts
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Do not print the underlying type for `@opaque` in the `t` helper
|
||||
* [GenEvent] Do not allow a handler to be added more than once
|
||||
* [GenServer] Do not treat bad arguments as exits in `GenServer.call/2`
|
||||
* [GenServer] Do not deliver out of order messages on `GenServer.cast/2` on distributed mode
|
||||
* [Mix] Do not pre-compile a Mix project if an alias was found
|
||||
* [Mix] Properly handle compilation errors in the Erlang compiler
|
||||
* [Mix] Always try to compile project if task cannot be found
|
||||
* [Mix] Purge in memory modules before consolidating
|
||||
* [Mix] Always compile a project if a task cannot be found
|
||||
|
||||
* Deprecations
|
||||
* [Collectable] Deprecate `Collectable.empty/1` and `Enum.traverse/2`
|
||||
* [Integer] `odd?/1` and `even?/1` are deprecated in favor of `is_odd/1` and `is_even/1`
|
||||
* [Kernel] `nil?/1` is deprecated in favor of `is_nil/1`
|
||||
* [Kernel] `x.Alias` is deprecated in favor of an explicit `Module.concat/2`
|
||||
* [Record] `record?/1` and `record?/2` are deprecated in favor of `is_record/1` and `is_record/2`
|
||||
* [Stream] Returning `{item, acc} | nil` from `Stream.resource/2` is deprecated, instead return `{[item], acc} | {:halt, acc}` (similar to `Stream.transform/3`)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.cancel_stream/1` no longer is
|
||||
* [GenEvent] The `:mode` and `:duration` options are deprecated and no longer supported in `GenEvent.stream/2` (the mode is now a property of the sender)
|
||||
* [GenEvent] The `:linked` option is deprecated and no longer supported in `GenEvent.add_handler/4` (check monitored handlers instead)
|
||||
|
||||
## v0.15.1 (2014-08-10)
|
||||
|
||||
* Enhancements
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ Use the issues tracker for:
|
||||
* [bug reports](#bugs-reports)
|
||||
* [submitting pull requests](#pull-requests)
|
||||
|
||||
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
|
||||
Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
|
||||
|
||||
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
|
||||
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
|
||||
@@ -18,7 +18,7 @@ Please **do not** use the issues tracker for personal support requests nor featu
|
||||
|
||||
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
|
||||
|
||||
We do our best to keep the issues tracker tidy and organized, making it useful
|
||||
We do our best to keep the issue tracker tidy and organized, making it useful
|
||||
for everyone. For example, we classify open issues per application and perceived
|
||||
difficulty of the issue, making it easier for developers to
|
||||
[contribute to Elixir](#contributing).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2012-2013 Plataformatec.
|
||||
Copyright 2012 Plataformatec
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
REBAR := rebar
|
||||
DOCS := v1.0
|
||||
CANONICAL := stable
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -124,17 +126,18 @@ clean_exbeam:
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o docs/$(2) -p http://elixir-lang.org/docs.html
|
||||
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
|
||||
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}\c")
|
||||
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "v$(VERSION)" $(call LOGO_PATH) -o doc/$(2) -a http://elixir-lang.org/docs/$(CANONICAL)/$(2)/ -p http://elixir-lang.org/docs.html
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
$(Q) rm -rf docs
|
||||
$(call DOCS,Elixir,elixir,Kernel)
|
||||
$(call DOCS,EEx,eex,EEx)
|
||||
$(call DOCS,Mix,mix,Mix)
|
||||
$(call DOCS,IEx,iex,IEx)
|
||||
$(call DOCS,ExUnit,ex_unit,ExUnit)
|
||||
$(call DOCS,Logger,logger,Logger)
|
||||
$(Q) rm -rf doc
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
|
||||
$(call COMPILE_DOCS,EEx,eex,EEx)
|
||||
$(call COMPILE_DOCS,Mix,mix,Mix)
|
||||
$(call COMPILE_DOCS,IEx,iex,IEx)
|
||||
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit)
|
||||
$(call COMPILE_DOCS,Logger,logger,Logger)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -142,12 +145,21 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../docs
|
||||
rm -rf ../docs/master
|
||||
mv docs ../docs/master
|
||||
rm -rf ../docs/$(DOCS)/*/
|
||||
mv doc/* ../docs/$(DOCS)
|
||||
|
||||
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
|
||||
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
|
||||
|
||||
publish_mix: compile
|
||||
cd lib/mix && MIX_ENV=prod mix escript.build
|
||||
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/v$(VERSION)/mix --acl public-read
|
||||
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/mix --acl public-read
|
||||
rm lib/mix/mix
|
||||
rm -rf lib/mix/_build
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
@@ -183,7 +195,7 @@ test_stdlib: compile
|
||||
|
||||
#==> Dialyzer tasks
|
||||
|
||||
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wrace_conditions -Wunderspecs
|
||||
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
|
||||
PLT = .elixir.plt
|
||||
|
||||
$(PLT):
|
||||
|
||||
+23
-11
@@ -1,28 +1,40 @@
|
||||
# Release process
|
||||
|
||||
## All releases
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1. Remove all `-dev` extension from versions (see below for all files)
|
||||
1. Ensure you are running on the oldest supported Erlang version
|
||||
|
||||
2. Ensure CHANGELOG is updated and add current date
|
||||
2. Remove all `-dev` extension from versions (see below for all files)
|
||||
|
||||
3. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
3. Ensure CHANGELOG is updated and add current date
|
||||
|
||||
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
4. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
5. Push master and the new tag
|
||||
5. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
6. Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
6. Ensure minimum supported Hex works with new release (instructions upcoming)
|
||||
|
||||
7. Release new zip with `make release_zip`, push new zip to GitHub Releases, name it `Precompiled.zip`
|
||||
7. Push branch and the new tag
|
||||
|
||||
8. Fast-forward merge master into stable branch with `git merge master --ff` and push it
|
||||
8. Release new docs with `make release_docs`, copy docs to `docs/stable` if appropriate, and push
|
||||
|
||||
9. After release, bump versions, add `-dev` back and commit
|
||||
9. Release new zip with `make release_zip`, push `Precompiled.zip` to GitHub Releases
|
||||
|
||||
10. `make release_docs` once again and push it to `elixir-lang/docs`
|
||||
10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
11. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
|
||||
11. Build and push standalone Mix with `make publish_mix` (requires AWS credentials)
|
||||
|
||||
## New vMAJOR.MINOR releases
|
||||
|
||||
12. Create a new branch "vMAJOR.MINOR"
|
||||
|
||||
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
|
||||
|
||||
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
||||
|
||||
15. `make release_docs` and push it to `elixir-lang/docs`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ done
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m $SCRIPT_PATH); fi
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
|
||||
|
||||
# Check for terminal support
|
||||
|
||||
+5
-1
@@ -1,5 +1,9 @@
|
||||
defmodule EEx.SyntaxError do
|
||||
defexception [:message]
|
||||
defexception [:message, :file, :line]
|
||||
|
||||
def message(exception) do
|
||||
"#{exception.file}:#{exception.line}: #{exception.message}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx do
|
||||
|
||||
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
|
||||
def compile(source, opts) do
|
||||
file = opts[:file] || "nofile"
|
||||
line = opts[:line] || 1
|
||||
tokens = EEx.Tokenizer.tokenize(source, line)
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
generate_buffer(tokens, "", [], state)
|
||||
case EEx.Tokenizer.tokenize(source, line) do
|
||||
{:ok, tokens} ->
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
generate_buffer(tokens, "", [], state)
|
||||
{:error, line, message} ->
|
||||
raise EEx.SyntaxError, line: line, file: file, message: message
|
||||
end
|
||||
end
|
||||
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
|
||||
{buffer, t}
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
|
||||
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
|
||||
raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([], buffer, [], state) do
|
||||
state.engine.handle_body(buffer)
|
||||
end
|
||||
|
||||
defp generate_buffer([], _buffer, _scope, _state) do
|
||||
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>."
|
||||
defp generate_buffer([], _buffer, _scope, state) do
|
||||
raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
|
||||
file: state.file, line: state.line
|
||||
end
|
||||
|
||||
# Creates a placeholder and wrap it inside the expression block
|
||||
|
||||
@@ -98,19 +98,16 @@ defmodule EEx.Engine do
|
||||
"""
|
||||
def handle_expr(buffer, "=", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
tmp <> (case unquote(expr) do
|
||||
bin when is_binary(bin) -> bin
|
||||
oth -> String.Chars.to_string(oth)
|
||||
end)
|
||||
tmp1 = unquote(buffer)
|
||||
tmp1 <> String.Chars.to_string(unquote(expr))
|
||||
end
|
||||
end
|
||||
|
||||
def handle_expr(buffer, "", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
tmp2 = unquote(buffer)
|
||||
unquote(expr)
|
||||
tmp
|
||||
tmp2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,8 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
@doc """
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
It returns {:ok, list} with the following tokens:
|
||||
|
||||
* `{:text, contents}`
|
||||
* `{:expr, line, marker, contents}`
|
||||
@@ -11,33 +12,43 @@ defmodule EEx.Tokenizer do
|
||||
* `{:middle_expr, line, marker, contents}`
|
||||
* `{:end_expr, line, marker, contents}`
|
||||
|
||||
Or `{:error, line, error}` in case of errors.
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(String.to_char_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
Enum.reverse(tokenize(list, line, [], []))
|
||||
tokenize(list, line, [], [])
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, line, buffer, acc) do
|
||||
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
case expr(t, line, [?%, ?<|buffer]) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, buffer, new_line, rest} ->
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, line, buffer, acc) do
|
||||
{_, new_line, rest} = tokenize_expr t, line, []
|
||||
tokenize rest, new_line, buffer, acc
|
||||
case expr(t, line, []) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, _, new_line, rest} ->
|
||||
tokenize rest, new_line, buffer, acc
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('<%' ++ t, line, buffer, acc) do
|
||||
{marker, t} = retrieve_marker(t)
|
||||
{expr, new_line, rest} = tokenize_expr t, line, []
|
||||
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
case expr(t, line, []) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, expr, new_line, rest} ->
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, line, buffer, acc) do
|
||||
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize([], _line, buffer, acc) do
|
||||
tokenize_text(buffer, acc)
|
||||
{:ok, Enum.reverse(tokenize_text(buffer, acc))}
|
||||
end
|
||||
|
||||
# Retrieve marker for <%
|
||||
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%, ?>|t], line, buffer) do
|
||||
{buffer, line, t}
|
||||
defp expr([?%, ?>|t], line, buffer) do
|
||||
{:ok, buffer, line, t}
|
||||
end
|
||||
|
||||
defp tokenize_expr('\n' ++ t, line, buffer) do
|
||||
tokenize_expr t, line + 1, [?\n|buffer]
|
||||
defp expr('\n' ++ t, line, buffer) do
|
||||
expr t, line + 1, [?\n|buffer]
|
||||
end
|
||||
|
||||
defp tokenize_expr([h|t], line, buffer) do
|
||||
tokenize_expr t, line, [h|buffer]
|
||||
defp expr([h|t], line, buffer) do
|
||||
expr t, line, [h|buffer]
|
||||
end
|
||||
|
||||
defp tokenize_expr([], _line, _buffer) do
|
||||
raise EEx.SyntaxError, message: "missing token: %>"
|
||||
defp expr([], line, _buffer) do
|
||||
{:error, line, "missing token '%>'"}
|
||||
end
|
||||
|
||||
# Receive an expression content and check
|
||||
|
||||
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
|
||||
require EEx.Tokenizer, as: T
|
||||
|
||||
test "simple chars lists" do
|
||||
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
|
||||
assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
|
||||
end
|
||||
|
||||
test "simple strings" do
|
||||
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
|
||||
assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
|
||||
assert T.tokenize('foo <% bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, "", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
|
||||
assert T.tokenize('foo <%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, "=", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo\n'}, {:expr, 2, "=", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -32,50 +35,50 @@ baz %>
|
||||
<% foo %>
|
||||
'''
|
||||
|
||||
assert T.tokenize(string, 1) == [
|
||||
assert T.tokenize(string, 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:expr, 1, "=", ' bar\n\nbaz '},
|
||||
{:text, '\n'},
|
||||
{:expr, 4, "", ' foo '},
|
||||
{:text, '\n'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
assert T.tokenize('foo <%% true %>', 1) == {:ok, [
|
||||
{:text, 'foo <% true %>'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
|
||||
{:text, 'foo <% true do %>bar<% end %>'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
|
||||
{:text, 'foo '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
|
||||
{:text, 'foo bar'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' cond do '},
|
||||
{:middle_expr, 1, "", ' false -> '},
|
||||
@@ -83,23 +86,21 @@ baz %>
|
||||
{:middle_expr, 1, "", ' true -> '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, "", ' else '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "raise syntax error when there is start mark and no end mark" do
|
||||
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
|
||||
T.tokenize('foo <% :bar', 1)
|
||||
end
|
||||
assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,25 +111,25 @@ defmodule EExTest do
|
||||
end
|
||||
|
||||
test "raises a syntax error when the token is invalid" do
|
||||
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
|
||||
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
|
||||
EEx.compile_string "foo <%= bar"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when end expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
|
||||
EEx.compile_string "foo <% end %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when start expression is found without an end expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
|
||||
EEx.compile_string "foo <% if true do %>"
|
||||
assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
|
||||
EEx.compile_string "foo\n<% if true do %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when nested end expression is found without an start expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
|
||||
test "raises a syntax error when nested end expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
|
||||
EEx.compile_string "foo <% if true do %><% end %><% end %>"
|
||||
end
|
||||
end
|
||||
@@ -252,7 +252,7 @@ Number <%= x %>
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "do not consider already finished functions" do
|
||||
test "properly handle functions on the left side of clauses" do
|
||||
expected = """
|
||||
foo
|
||||
|
||||
@@ -287,6 +287,18 @@ foo
|
||||
assert_eval "\n\n Good\n \n", string
|
||||
end
|
||||
|
||||
test "evaluates expressions with buffers" do
|
||||
string = """
|
||||
<%= 123 %>
|
||||
<% if true do %>
|
||||
<%= 456 %>
|
||||
<% end %>
|
||||
<%= 789 %>
|
||||
"""
|
||||
|
||||
assert_eval "123\n\n789\n", string
|
||||
end
|
||||
|
||||
test "for comprehensions" do
|
||||
string = """
|
||||
<%= for _name <- packages || [] do %>
|
||||
|
||||
@@ -201,7 +201,7 @@ defmodule Application do
|
||||
`:applications` in the `.app` file in case they were not previously
|
||||
started.
|
||||
"""
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
|
||||
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_all_started(app, type)
|
||||
end
|
||||
|
||||
+5
-13
@@ -51,16 +51,7 @@ defmodule Code do
|
||||
:code.del_path(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Re-add paths given to the command line to keep their position
|
||||
on the overall code path.
|
||||
|
||||
Some tools may change the code path by prepending new items but
|
||||
still want the paths given by the user to have higher priority.
|
||||
Calling this function guarantees the paths are re-added on
|
||||
top of the user given ones.
|
||||
"""
|
||||
@spec readd_paths() :: :ok
|
||||
@doc false
|
||||
def readd_paths() do
|
||||
{pa, pz} = :elixir_code_server.call(:paths)
|
||||
:code.add_pathsa(pa)
|
||||
@@ -260,7 +251,7 @@ defmodule Code do
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
While `load_file` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file` simply evalutes the file contents and returns the
|
||||
byte code, `eval_file` simply evaluates the file contents and returns the
|
||||
evaluation result and its bindings.
|
||||
"""
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
@@ -325,7 +316,7 @@ defmodule Code do
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
:elixir_code_server.call :compiler_options
|
||||
:elixir_config.get :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -364,7 +355,8 @@ defmodule Code do
|
||||
bad = bad |> Keyword.keys |> Enum.join(", ")
|
||||
raise ArgumentError, message: "unknown compiler options: #{bad}"
|
||||
end
|
||||
:elixir_code_server.cast {:compiler_options, opts}
|
||||
update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
|
||||
:elixir_config.update :compiler_options, update
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -8,12 +8,6 @@ defprotocol Collectable do
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
|
||||
The `Enumerable` protocol is useful to take values out of a collection.
|
||||
@@ -27,25 +21,13 @@ defprotocol Collectable do
|
||||
shape where just the range limits are stored.
|
||||
|
||||
The `Collectable` module was designed to fill the gap left by the
|
||||
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
|
||||
|
||||
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
|
||||
`Enumerable` is about taking values out, `Collectable.into/1` is about
|
||||
collecting those values into a structure.
|
||||
|
||||
`empty/1` receives a collectable and returns an empty version of the
|
||||
same collectable. By combining the enumerable functionality with `into/1`
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
`Enumerable` protocol. `into/1` can be seen as the opposite of
|
||||
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
|
||||
`Collectable.into/1` is about collecting those values into a structure.
|
||||
"""
|
||||
|
||||
@type command :: {:cont, term} | :done | :halt
|
||||
|
||||
@doc """
|
||||
Receives a collectable structure and returns an empty one.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(collectable)
|
||||
|
||||
@doc """
|
||||
Returns a function that collects values alongside
|
||||
the initial accumulation value.
|
||||
@@ -65,10 +47,6 @@ defprotocol Collectable do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: List do
|
||||
def empty(_list) do
|
||||
[]
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{[], fn
|
||||
list, {:cont, x} -> [x|list]
|
||||
@@ -79,10 +57,6 @@ defimpl Collectable, for: List do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def empty(_bitstring) do
|
||||
""
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
|
||||
@@ -92,22 +66,7 @@ defimpl Collectable, for: BitString do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Function do
|
||||
def empty(function) do
|
||||
function
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
IO.write :stderr, "warning: passing a function as Collectable is deprecated\n#{Exception.format_stacktrace}"
|
||||
{function, function}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def empty(_map) do
|
||||
%{}
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
map, {:cont, {k, v}} -> :maps.put(k, v, map)
|
||||
|
||||
+20
-32
@@ -341,7 +341,7 @@ defmodule Enum do
|
||||
{_, {acc, {buffer, i}}} =
|
||||
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
|
||||
|
||||
if nil?(pad) || i == 0 do
|
||||
if is_nil(pad) || i == 0 do
|
||||
:lists.reverse(acc)
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
|
||||
@@ -593,11 +593,11 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec fetch(t, integer) :: {:ok, element} | :error
|
||||
def fetch(collection, n) when is_list(collection) and n >= 0 do
|
||||
def fetch(collection, n) when is_list(collection) and is_integer(n) and n >= 0 do
|
||||
do_fetch(collection, n)
|
||||
end
|
||||
|
||||
def fetch(collection, n) when n >= 0 do
|
||||
def fetch(collection, n) when is_integer(n) and n >= 0 do
|
||||
res =
|
||||
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
|
||||
if acc == n do
|
||||
@@ -613,7 +613,7 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(collection, n) when n < 0 do
|
||||
def fetch(collection, n) when is_integer(n) and n < 0 do
|
||||
do_fetch(reverse(collection), abs(n + 1))
|
||||
end
|
||||
|
||||
@@ -1261,6 +1261,15 @@ defmodule Enum do
|
||||
:lists.foldl(fun, acc, collection)
|
||||
end
|
||||
|
||||
def reduce(%{__struct__: _} = collection, acc, fun) do
|
||||
Enumerable.reduce(collection, {:cont, acc},
|
||||
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||||
end
|
||||
|
||||
def reduce(%{} = collection, acc, fun) do
|
||||
:maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, collection)
|
||||
end
|
||||
|
||||
def reduce(collection, acc, fun) do
|
||||
Enumerable.reduce(collection, {:cont, acc},
|
||||
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||||
@@ -1722,23 +1731,23 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
def take(collection, count) when is_list(collection) and count > 0 do
|
||||
def take(collection, count) when is_list(collection) and is_integer(count) and count > 0 do
|
||||
do_take(collection, count)
|
||||
end
|
||||
|
||||
def take(collection, count) when count > 0 do
|
||||
def take(collection, count) when count > 0 and is_integer(count) do
|
||||
{_, {res, _}} =
|
||||
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
|
||||
if count > 1 do
|
||||
{:cont, {[entry|list], count - 1}}
|
||||
else
|
||||
{:halt, {[entry|list], count}}
|
||||
case count do
|
||||
0 -> {:halt, {list, count}}
|
||||
1 -> {:halt, {[entry|list], count - 1}}
|
||||
_ -> {:cont, {[entry|list], count - 1}}
|
||||
end
|
||||
end)
|
||||
:lists.reverse(res)
|
||||
end
|
||||
|
||||
def take(collection, count) when count < 0 do
|
||||
def take(collection, count) when count < 0 and is_integer(count) do
|
||||
Stream.take(collection, count).({:cont, []}, &{:cont, [&1|&2]})
|
||||
|> elem(1) |> :lists.reverse
|
||||
end
|
||||
@@ -1798,27 +1807,6 @@ defmodule Enum do
|
||||
reverse(collection) |> :lists.reverse
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Traverses the given enumerable keeping its shape.
|
||||
|
||||
It also expects the enumerable to implement the `Collectable` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
"""
|
||||
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
|
||||
def traverse(collection, transform) when is_list(collection) do
|
||||
:lists.map(transform, collection)
|
||||
end
|
||||
|
||||
def traverse(collection, transform) do
|
||||
into(collection, Collectable.empty(collection), transform)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enumerates the collection, removing all duplicated items.
|
||||
|
||||
|
||||
@@ -564,6 +564,14 @@ defmodule BadStructError do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadMapError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"expected a map, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule MatchError do
|
||||
defexception [term: nil]
|
||||
|
||||
@@ -613,12 +621,17 @@ defmodule BadArityError do
|
||||
end
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
defexception [module: nil, function: nil, arity: nil, self: false]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
def message(%{function: function, module: module, arity: arity, self: self}) do
|
||||
if function do
|
||||
formatted = Exception.format_mfa module, function, arity
|
||||
suffix = if self or is_nil(module) or :code.is_loaded(module) do
|
||||
""
|
||||
else
|
||||
" (module #{inspect module} is not available)"
|
||||
end
|
||||
"undefined function: #{formatted}" <> suffix
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
@@ -664,7 +677,12 @@ defmodule KeyError do
|
||||
defexception key: nil, term: nil
|
||||
|
||||
def message(exception) do
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
msg = "key #{inspect exception.key} not found"
|
||||
if exception.term != nil do
|
||||
msg <> " in: #{inspect exception.term}"
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -763,6 +781,20 @@ defmodule ErlangError do
|
||||
%MatchError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badmap, term}, _stacktrace) do
|
||||
%BadMapError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badkey, key}, stacktrace) do
|
||||
term =
|
||||
case stacktrace || :erlang.get_stacktrace do
|
||||
[{:maps, :update, [_, _, map], _}|_] -> map
|
||||
[{:maps, :get, [_, map], _}|_] -> map
|
||||
_ -> nil
|
||||
end
|
||||
%KeyError{key: key, term: term}
|
||||
end
|
||||
|
||||
def normalize({:case_clause, term}, _stacktrace) do
|
||||
%CaseClauseError{term: term}
|
||||
end
|
||||
@@ -772,8 +804,9 @@ defmodule ErlangError do
|
||||
end
|
||||
|
||||
def normalize(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
|
||||
stacktrace = stacktrace || :erlang.get_stacktrace
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
|
||||
end
|
||||
|
||||
def normalize(:function_clause, stacktrace) do
|
||||
@@ -800,4 +833,7 @@ defmodule ErlangError do
|
||||
defp from_stacktrace(_) do
|
||||
{nil, nil, nil}
|
||||
end
|
||||
|
||||
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
|
||||
defp from_self(_), do: false
|
||||
end
|
||||
|
||||
+31
-15
@@ -75,6 +75,12 @@ defmodule File do
|
||||
@type posix :: :file.posix()
|
||||
@type io_device :: :file.io_device()
|
||||
@type stat_options :: [time: :local | :universal | :posix]
|
||||
@type mode :: :append | :binary | :compressed | :delayed_write | :exclusive |
|
||||
:raw | :read | :read_ahead | :sync | :write |
|
||||
{:encoding , :latin1 | :unicode | :utf16 | :utf32 | :utf8 |
|
||||
{:utf16, :big | :little} | {:utf32, :big | :little}} |
|
||||
{:read_ahead, pos_integer} |
|
||||
{:delayed_write, non_neg_integer, non_neg_integer}
|
||||
|
||||
@doc """
|
||||
Returns `true` if the path is a regular file.
|
||||
@@ -612,7 +618,7 @@ defmodule File do
|
||||
|
||||
Check `File.open/2` for other available options.
|
||||
"""
|
||||
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
|
||||
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(IO.chardata_to_string(path), content, modes)
|
||||
end
|
||||
@@ -620,7 +626,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
|
||||
"""
|
||||
@spec write!(Path.t, iodata, list) :: :ok | no_return
|
||||
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
|
||||
def write!(path, content, modes \\ []) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.write_file(path, content, modes) do
|
||||
@@ -902,7 +908,8 @@ defmodule File do
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
|
||||
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
|
||||
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
@@ -935,7 +942,7 @@ defmodule File do
|
||||
end)
|
||||
|
||||
"""
|
||||
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
def open(path, modes, function) do
|
||||
case open(path, modes) do
|
||||
{:ok, device} ->
|
||||
@@ -953,7 +960,7 @@ defmodule File do
|
||||
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list) :: io_device | no_return
|
||||
@spec open!(Path.t, [mode]) :: io_device | no_return
|
||||
def open!(path, modes \\ []) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes) do
|
||||
@@ -968,7 +975,7 @@ defmodule File do
|
||||
|
||||
Returns the function result otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
|
||||
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
|
||||
def open!(path, modes, function) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes, function) do
|
||||
@@ -989,18 +996,27 @@ defmodule File do
|
||||
@spec cwd() :: {:ok, binary} | {:error, posix}
|
||||
def cwd() do
|
||||
case F.get_cwd do
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
|
||||
case :os.type() do
|
||||
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
|
||||
_ -> original
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter(original), do: original
|
||||
|
||||
@doc """
|
||||
The same as `cwd/0`, but raises an exception if it fails.
|
||||
"""
|
||||
@spec cwd!() :: binary | no_return
|
||||
def cwd!() do
|
||||
case F.get_cwd do
|
||||
{:ok, cwd} -> IO.chardata_to_string(cwd)
|
||||
case cwd() do
|
||||
{:ok, cwd} -> cwd
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "get current working directory"
|
||||
end
|
||||
@@ -1124,7 +1140,7 @@ defmodule File do
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
"""
|
||||
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chmod(path, mode) do
|
||||
F.change_mode(IO.chardata_to_string(path), mode)
|
||||
end
|
||||
@@ -1132,7 +1148,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chmod!(Path.t, integer) :: :ok | no_return
|
||||
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chmod!(path, mode) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chmod(path, mode) do
|
||||
@@ -1147,7 +1163,7 @@ defmodule File do
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chgrp(path, gid) do
|
||||
F.change_group(IO.chardata_to_string(path), gid)
|
||||
end
|
||||
@@ -1155,7 +1171,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chgrp!(Path.t, integer) :: :ok | no_return
|
||||
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chgrp!(path, gid) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chgrp(path, gid) do
|
||||
@@ -1170,7 +1186,7 @@ defmodule File do
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
@spec chown(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chown(path, uid) do
|
||||
F.change_owner(IO.chardata_to_string(path), uid)
|
||||
end
|
||||
@@ -1178,7 +1194,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chown!(Path.t, integer) :: :ok | no_return
|
||||
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chown!(path, uid) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chown(path, uid) do
|
||||
|
||||
+18
-54
@@ -28,75 +28,39 @@ defmodule Float do
|
||||
"""
|
||||
@spec parse(binary) :: {float, binary} | :error
|
||||
def parse("-" <> binary) do
|
||||
case parse_unsign(binary) do
|
||||
case parse_unsigned(binary) do
|
||||
:error -> :error
|
||||
{number, remainder} -> {-number, remainder}
|
||||
end
|
||||
end
|
||||
|
||||
def parse(binary) do
|
||||
parse_unsign(binary)
|
||||
parse_unsigned(binary)
|
||||
end
|
||||
|
||||
defp parse_unsign("-" <> _), do: :error
|
||||
defp parse_unsign(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{integer_part, after_integer} -> parse_unsign after_integer, integer_part
|
||||
end
|
||||
end
|
||||
defp parse_unsigned(<<char, rest::binary>>) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, false, false, <<char>>)
|
||||
|
||||
# Dot followed by digit is required afterwards or we are done
|
||||
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse_unsign(rest, char - ?0, 1, int)
|
||||
end
|
||||
defp parse_unsigned(binary) when is_binary(binary), do:
|
||||
:error
|
||||
|
||||
defp parse_unsign(rest, int) do
|
||||
{:erlang.float(int), rest}
|
||||
end
|
||||
defp parse_unsigned(<<char, rest :: binary>>, dot?, e?, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, dot?, e?, <<acc::binary, char>>)
|
||||
|
||||
# Handle decimal points
|
||||
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
end
|
||||
defp parse_unsigned(<<?., char, rest :: binary>>, false, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, false, <<acc::binary, ?., char>>)
|
||||
|
||||
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
case Integer.parse after_e do
|
||||
:error ->
|
||||
# Note we rebuild the binary here instead of breaking it apart at
|
||||
# the function clause because the current approach copies a binary
|
||||
# just on this branch. If we broke it apart in the function clause,
|
||||
# the copy would happen when calling Integer.parse/1.
|
||||
{floatify(int, float, decimal), << ?e, after_e :: binary >>}
|
||||
{exponential, after_exponential} ->
|
||||
{floatify(int, float, decimal, exponential), after_exponential}
|
||||
end
|
||||
end
|
||||
defp parse_unsigned(<<?e, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, char>>)
|
||||
|
||||
defp parse_unsign(bitstring, float, decimal, int) do
|
||||
{floatify(int, float, decimal), bitstring}
|
||||
end
|
||||
defp parse_unsigned(<<?e, ?-, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, ?-, char>>)
|
||||
|
||||
defp floatify(int, float, decimal, exponential \\ 0) do
|
||||
multiplier = if int < 0, do: -1.0, else: 1.0
|
||||
defp parse_unsigned(rest, dot?, _e?, acc), do:
|
||||
{:erlang.binary_to_float(add_dot(acc, dot?)), rest}
|
||||
|
||||
# Try to ensure the minimum amount of rounding errors
|
||||
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
|
||||
|
||||
# Try avoiding stuff like this:
|
||||
# iex(1)> 0.0001 * 75
|
||||
# 0.007500000000000001
|
||||
# Due to IEEE 754 floating point standard
|
||||
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
|
||||
final_decimal_places = decimal - exponential
|
||||
if final_decimal_places > 0 do
|
||||
decimal_power_round = :math.pow(10, final_decimal_places)
|
||||
trunc(result * decimal_power_round) / decimal_power_round
|
||||
else
|
||||
result
|
||||
end
|
||||
end
|
||||
defp add_dot(acc, true), do: acc
|
||||
defp add_dot(acc, false), do: acc <> ".0"
|
||||
|
||||
@doc """
|
||||
Rounds a float to the largest integer less than or equal to `num`.
|
||||
|
||||
+661
-306
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
defmodule GenEvent.Stream do
|
||||
@moduledoc """
|
||||
Defines a `GenEvent` stream.
|
||||
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
|
||||
"""
|
||||
defstruct manager: nil, timeout: :infinity
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
manager: GenEvent.manager,
|
||||
timeout: timeout}
|
||||
|
||||
@doc false
|
||||
def init({_pid, _ref} = state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(event, _state) do
|
||||
exit({:bad_event, event})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(msg, _state) do
|
||||
exit({:bad_call, msg})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: GenEvent.Stream do
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _item) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp wrap_reducer(fun) do
|
||||
fn
|
||||
{:ack, manager, ref, event}, acc ->
|
||||
send manager, {ref, :ok}
|
||||
fun.(event, acc)
|
||||
{:async, _manager, _ref, event}, acc ->
|
||||
fun.(event, acc)
|
||||
{:sync, manager, ref, event}, acc ->
|
||||
try do
|
||||
fun.(event, acc)
|
||||
after
|
||||
send manager, {ref, :ok}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start(%{manager: manager} = stream) do
|
||||
try do
|
||||
{:ok, {pid, ref}} = :gen.call(manager, self(),
|
||||
{:add_process_handler, self(), self()}, :infinity)
|
||||
mon_ref = Process.monitor(pid)
|
||||
{pid, ref, mon_ref}
|
||||
catch
|
||||
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp next(%{timeout: timeout} = stream, {pid, ref, mon_ref} = acc) do
|
||||
self = self()
|
||||
|
||||
receive do
|
||||
# Got an async event.
|
||||
{_from, {^pid, ^ref}, {:notify, event}} ->
|
||||
{[{:async, pid, ref, event}], acc}
|
||||
|
||||
# Got a sync event.
|
||||
{_from, {^pid, ^ref}, {:sync_notify, event}} ->
|
||||
{[{:sync, pid, ref, event}], acc}
|
||||
|
||||
# Got an ack event.
|
||||
{_from, {^pid, ^ref}, {:ack_notify, event}} ->
|
||||
{[{:ack, pid, ref, event}], acc}
|
||||
|
||||
# The handler was removed. Stop iteration, resolve the
|
||||
# event later. We need to demonitor now, otherwise DOWN
|
||||
# appears with higher priority in the shutdown process.
|
||||
{:gen_event_EXIT, {^pid, ^ref}, _reason} = event ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
send(self, event)
|
||||
{:halt, {:removed, acc}}
|
||||
|
||||
# The manager died. Stop iteration, resolve the event later.
|
||||
{:DOWN, ^mon_ref, _, _, _} = event ->
|
||||
send(self, event)
|
||||
{:halt, {:removed, acc}}
|
||||
after
|
||||
timeout ->
|
||||
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, we know the handler was already
|
||||
# removed, so we don't trigger a request for doing so.
|
||||
defp stop(stream, {:removed, {pid, ref, mon_ref} = acc}) do
|
||||
case wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
:ok ->
|
||||
flush_events(ref)
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :stop, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, the handler was not removed yet,
|
||||
# so we trigger a request for doing so.
|
||||
defp stop(stream, {pid, ref, _} = acc) do
|
||||
_ = GenEvent.remove_handler(pid, {pid, ref}, :shutdown)
|
||||
stop(stream, {:removed, acc})
|
||||
end
|
||||
|
||||
defp wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
receive do
|
||||
{:gen_event_EXIT, {^pid, ^ref}, reason}
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 3 and elem(reason, 0) == :swapped ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
:ok
|
||||
{:gen_event_EXIT, {^pid, ^ref}, reason} ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
{:error, reason}
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp flush_events(ref) do
|
||||
receive do
|
||||
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
|
||||
flush_events(ref)
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -349,18 +349,57 @@ defmodule GenServer do
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
:gen_server.call(server, request, timeout)
|
||||
try do
|
||||
:gen.call(server, :"$gen_call", request, timeout)
|
||||
catch
|
||||
:exit, reason ->
|
||||
exit({reason, {__MODULE__, :call, [server, request, timeout]}})
|
||||
else
|
||||
{:ok, res} -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an asynchronous request to the `server`.
|
||||
|
||||
This function returns `:ok` immediately, regardless of whether the
|
||||
destination node or server does exists. `handle_cast/2` will be called on the
|
||||
server to handle the request.
|
||||
This function returns `:ok` immediately, regardless of
|
||||
whether the destination node or server does exists, unless
|
||||
the server is specified as an atom.
|
||||
|
||||
`handle_cast/2` will be called on the server to handle
|
||||
the request. In case the server is a node which is not
|
||||
yet connected to the caller one, the call is going to
|
||||
block until a connection happens. This is different than
|
||||
the behaviour in OTP's `:gen_server` where the message
|
||||
would be sent by another process, which could cause
|
||||
messages to arrive out of order.
|
||||
"""
|
||||
@spec cast(server, term) :: :ok
|
||||
defdelegate cast(server, request), to: :gen_server
|
||||
def cast(server, request)
|
||||
|
||||
def cast({:global, name}, request) do
|
||||
try do
|
||||
:global.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({:via, mod, name}, request) do
|
||||
try do
|
||||
mod.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({name, node}, request) when is_atom(name) and is_atom(node),
|
||||
do: do_send({name, node}, cast_msg(request))
|
||||
|
||||
def cast(dest, request) when is_atom(dest) or is_pid(dest),
|
||||
do: do_send(dest, cast_msg(request))
|
||||
|
||||
@doc """
|
||||
Casts all servers locally registered as `name` at the specified nodes.
|
||||
@@ -371,8 +410,19 @@ defmodule GenServer do
|
||||
See `multi_call/4` for more information.
|
||||
"""
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ nodes(), name, request) do
|
||||
:gen_server.abcast(nodes, name, request)
|
||||
def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
|
||||
msg = cast_msg(request)
|
||||
_ = for node <- nodes, do: do_send({name, node}, msg)
|
||||
:abcast
|
||||
end
|
||||
|
||||
defp cast_msg(req) do
|
||||
{:"$gen_cast", req}
|
||||
end
|
||||
|
||||
defp do_send(dest, msg) do
|
||||
send(dest, msg)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -185,6 +185,8 @@ defmodule Inspect.Algebra do
|
||||
Inspect.inspect(map, opts)
|
||||
rescue
|
||||
e ->
|
||||
stacktrace = System.stacktrace
|
||||
|
||||
# Because we try to raise a nice error message in case
|
||||
# we can't inspect a struct, there is a chance the error
|
||||
# message itself relies on the struct being printed, so
|
||||
@@ -198,9 +200,10 @@ defmodule Inspect.Algebra do
|
||||
Process.put(:inspect_trap, true)
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
formatted = IO.iodata_to_binary(format(res, :infinity))
|
||||
raise ArgumentError,
|
||||
reraise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{formatted}"
|
||||
"\"#{Exception.message(e)}\" while inspecting #{formatted}",
|
||||
stacktrace
|
||||
after
|
||||
Process.delete(:inspect_trap)
|
||||
end
|
||||
@@ -435,16 +438,16 @@ defmodule Inspect.Algebra do
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
|
||||
"[1! 2! 3! ...]"
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
|
||||
@spec surround_many(binary, [any], binary, Inspect.Opts.t, (term, Inspect.Opts.t -> t), binary) :: t
|
||||
def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
|
||||
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
|
||||
end
|
||||
|
||||
def do_surround_many(left, [], right, _, _opts, _fun, _) do
|
||||
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def do_surround_many(left, docs, right, limit, _opts, fun, sep) do
|
||||
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
|
||||
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
|
||||
end
|
||||
|
||||
@@ -482,7 +485,7 @@ defmodule Inspect.Algebra do
|
||||
and returns an IO data representation of the best layout for the
|
||||
document to fit in the given width.
|
||||
"""
|
||||
@spec format(t, non_neg_integer | :infinity) :: binary
|
||||
@spec format(t, non_neg_integer | :infinity) :: iodata
|
||||
def format(d, w) do
|
||||
format(w, 0, [{0, default_mode(w), doc_group(d)}])
|
||||
end
|
||||
|
||||
@@ -9,9 +9,10 @@ defmodule Integer do
|
||||
Determines if an integer is odd.
|
||||
|
||||
Returns `true` if `n` is an odd number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro odd?(n) do
|
||||
defmacro is_odd(n) do
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
end
|
||||
|
||||
@@ -19,9 +20,10 @@ defmodule Integer do
|
||||
Determines if an integer is even.
|
||||
|
||||
Returns `true` if `n` is an even number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro even?(n) do
|
||||
defmacro is_even(n) do
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
end
|
||||
|
||||
@@ -99,7 +101,7 @@ defmodule Integer do
|
||||
"64"
|
||||
|
||||
"""
|
||||
@spec to_string(integer, pos_integer) :: String.t
|
||||
@spec to_string(integer, 2..36) :: String.t
|
||||
def to_string(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
@@ -132,7 +134,7 @@ defmodule Integer do
|
||||
'3FF'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer, pos_integer) :: char_list
|
||||
@spec to_char_list(integer, 2..36) :: char_list
|
||||
def to_char_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
|
||||
@@ -123,8 +123,9 @@ defmodule IO do
|
||||
end
|
||||
end
|
||||
|
||||
@read_all_size 4096
|
||||
defp do_binread_all(mapped_dev, acc) do
|
||||
case :file.read_line(mapped_dev) do
|
||||
case :file.read(mapped_dev, @read_all_size) do
|
||||
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
|
||||
:eof -> acc
|
||||
other -> other
|
||||
@@ -180,7 +181,7 @@ defmodule IO do
|
||||
Inspects and writes the given argument to the device.
|
||||
|
||||
It enables pretty printing by default with width of
|
||||
80 characters. Th width can be changed by explicitly
|
||||
80 characters. The width can be changed by explicitly
|
||||
passing the `:width` option.
|
||||
|
||||
## Examples
|
||||
@@ -394,11 +395,11 @@ defmodule IO do
|
||||
def each_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -406,11 +407,11 @@ defmodule IO do
|
||||
def each_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -25,10 +25,10 @@ defmodule IO.ANSI.Docs do
|
||||
[enabled: true,
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan, :bright],
|
||||
doc_headings: [:yellow, :bright],
|
||||
doc_headings: [:yellow],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow, :bright],
|
||||
doc_title: [:reverse, :yellow],
|
||||
doc_underline: [:underline],
|
||||
width: 80]
|
||||
end
|
||||
@@ -194,7 +194,7 @@ defmodule IO.ANSI.Docs do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> handle_inline(options)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
|
||||
|
||||
@@ -262,7 +262,7 @@ defmodule IO.ANSI.Docs do
|
||||
col = col
|
||||
|> String.replace(~r/\\ \|/x, "|")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> handle_inline(options)
|
||||
{col, length_without_escape(col, 0)}
|
||||
end
|
||||
|
||||
@@ -392,61 +392,104 @@ defmodule IO.ANSI.Docs do
|
||||
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
# Single inline quotes.
|
||||
@single [?`, ?_, ?*]
|
||||
|
||||
# ` does not require space in between
|
||||
@spaced [?_, ?*]
|
||||
# We have four entries: **, *, _ and `.
|
||||
#
|
||||
# The first three behave the same while the last one is simpler
|
||||
# when it comes to delimiters. But, since the first has two
|
||||
# characters, we need to handle 3 cases:
|
||||
#
|
||||
# 1. **
|
||||
# 2. _ and *
|
||||
# 3. `
|
||||
#
|
||||
# Where the first two should have the same code but match differently.
|
||||
@single [?_, ?*]
|
||||
|
||||
# Clauses for handling spaces
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
|
||||
# Characters that can mark the beginning or the end of a word.
|
||||
# Only support the most common ones at this moment.
|
||||
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
|
||||
|
||||
# Inline start
|
||||
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, options) do
|
||||
handle_inline(rest, ?d, ["**"], [], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
|
||||
defp handle_inline(<<mark, rest :: binary>>, options) when mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
|
||||
defp handle_inline(rest, options) do
|
||||
handle_inline(rest, nil, [], [], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
|
||||
# Inline delimiters
|
||||
|
||||
defp handle_inline(<<delimiter, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and delimiter in @delimiters do
|
||||
handle_inline(rest, ?d, ["**"], [delimiter, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<delimiter, mark, rest :: binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and delimiter in @delimiters and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [delimiter, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?`, rest :: binary>>, nil, buffer, acc, options)
|
||||
when rest != "" do
|
||||
handle_inline(rest, ?`, ["`"], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
# Clauses for handling escape
|
||||
|
||||
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
|
||||
when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [?\\, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?\\, mark, rest :: binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [?\\, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
|
||||
end
|
||||
|
||||
# A escape is not valid inside `
|
||||
# An escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
|
||||
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
|
||||
when not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark|buffer], acc, options)
|
||||
end
|
||||
|
||||
# Inline start
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, options) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, options) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
|
||||
|
||||
defp handle_inline(<<?*, ?*, delimiter, rest :: binary>>, ?d, buffer, acc, options)
|
||||
when delimiter in @delimiters do
|
||||
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, delimiter, rest :: binary>>, mark, buffer, acc, options)
|
||||
when delimiter in @delimiters and mark in @single do
|
||||
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?*, ?*, rest:: binary>>, ?d, buffer, acc, options)
|
||||
when rest == "" do
|
||||
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options)
|
||||
when rest == "" and mark in @single do
|
||||
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?`, rest :: binary>>, ?`, buffer, acc, options) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
# Catch all
|
||||
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, options)
|
||||
|
||||
@@ -57,7 +57,7 @@ defmodule IO.Stream do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
|
||||
+20
-11
@@ -1386,7 +1386,14 @@ defmodule Kernel do
|
||||
iex> "abcd" =~ "ad"
|
||||
false
|
||||
|
||||
iex> "abcd" =~ ""
|
||||
true
|
||||
|
||||
"""
|
||||
|
||||
@spec (String.t =~ (String.t | Regex.t)) :: boolean
|
||||
def left =~ "" when is_binary(left), do: true
|
||||
|
||||
def left =~ right when is_binary(left) and is_binary(right) do
|
||||
:binary.match(left, right) != :nomatch
|
||||
end
|
||||
@@ -1643,7 +1650,7 @@ defmodule Kernel do
|
||||
|
||||
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
|
||||
iex> all = fn :get_and_update, data, next ->
|
||||
...> Enum.map(data, next) |> List.unzip() |> List.to_tuple()
|
||||
...> Enum.map(data, next) |> :lists.unzip
|
||||
...> end
|
||||
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
|
||||
{[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]}
|
||||
@@ -1813,7 +1820,9 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defp unnest({{:., _, [expr, key]}, _, []}, acc, kind)
|
||||
when is_tuple(expr) and elem(expr, 0) != :__aliases__ and elem(expr, 0) != :__MODULE__ do
|
||||
when is_tuple(expr) and
|
||||
:erlang.element(1, expr) != :__aliases__ and
|
||||
:erlang.element(1, expr) != :__MODULE__ do
|
||||
unnest(expr, [{:map, key}|acc], kind)
|
||||
end
|
||||
|
||||
@@ -1834,8 +1843,8 @@ defmodule Kernel do
|
||||
|
||||
defp proper_start?({{:., _, [expr, _]}, _, _args})
|
||||
when is_atom(expr)
|
||||
when elem(expr, 0) == :__aliases__
|
||||
when elem(expr, 0) == :__MODULE__, do: true
|
||||
when :erlang.element(1, expr) == :__aliases__
|
||||
when :erlang.element(1, expr) == :__MODULE__, do: true
|
||||
|
||||
defp proper_start?({atom, _, _args})
|
||||
when is_atom(atom), do: true
|
||||
@@ -1881,20 +1890,20 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> nil?(1)
|
||||
iex> is_nil(1)
|
||||
false
|
||||
|
||||
iex> nil?(nil)
|
||||
iex> is_nil(nil)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro nil?(x) do
|
||||
defmacro is_nil(x) do
|
||||
quote do: unquote(x) == nil
|
||||
end
|
||||
|
||||
@doc """
|
||||
A convenient macro that checks if the right side matches
|
||||
the left side. The left side is allowed to be a match pattern.
|
||||
A convenience macro that checks if the right side (an expression)
|
||||
matches the left side (a pattern).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2057,7 +2066,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, tuple_size(entry) - 1)
|
||||
opts = :erlang.element(tuple_size(entry), entry)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
@@ -2379,7 +2388,7 @@ defmodule Kernel do
|
||||
it is not loaded. Check `Code.ensure_loaded/1` for more
|
||||
information.
|
||||
"""
|
||||
@spec function_exported?(atom | tuple, atom, integer) :: boolean
|
||||
@spec function_exported?(atom | tuple, atom, arity) :: boolean
|
||||
def function_exported?(module, function, arity) do
|
||||
:erlang.function_exported(module, function, arity)
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ defmodule Kernel.CLI do
|
||||
## Helpers
|
||||
|
||||
defp at_exit(res) do
|
||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||
hooks = :elixir_config.get_and_put(:at_exit, [])
|
||||
res = Enum.reduce(hooks, res, &exec_fun/2)
|
||||
if hooks == [], do: res, else: at_exit(res)
|
||||
end
|
||||
@@ -113,7 +113,8 @@ defmodule Kernel.CLI do
|
||||
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
|
||||
:elixir_expand, :elixir_lexical]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
@@ -408,7 +409,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp wrapper(fun) do
|
||||
fun.()
|
||||
_ = fun.()
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule Kernel.LexicalTracker do
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
|
||||
[{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
|
||||
val
|
||||
end
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
is set to `true` and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
This function receives a set of callbacks as options:
|
||||
This function accepts the following options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
@@ -23,32 +23,36 @@ defmodule Kernel.ParallelCompiler do
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
* `:dest` - the destination directory for the beam files. When using `files/2`,
|
||||
this information is only used to properly annotate the beam files before
|
||||
they are loaded into memory. If you want a file to actually be writen to
|
||||
`dest`, use `files_to_path/3` instead.
|
||||
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks \\ [])
|
||||
def files(files, options \\ [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
def files(files, options) when is_list(options) do
|
||||
spawn_compilers(files, nil, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callbacks \\ [])
|
||||
def files_to_path(files, path, options \\ [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
|
||||
spawn_compilers(files, path, options)
|
||||
end
|
||||
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
defp spawn_compilers(files, path, options) do
|
||||
true = Code.ensure_loaded?(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
|
||||
result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error and we fail with CompileError
|
||||
@@ -59,21 +63,21 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
|
||||
send h, {ref, :ready}
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(t, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} =
|
||||
@@ -90,40 +94,40 @@ defmodule Kernel.ParallelCompiler do
|
||||
_ = if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
:elixir_compiler.file(h, Keyword.get(options, :dest))
|
||||
end
|
||||
{:compiled, h}
|
||||
{:shutdown, h}
|
||||
catch
|
||||
kind, reason ->
|
||||
{:failure, kind, reason, System.stacktrace}
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
spawn_compilers(t, original, output, options, waiting,
|
||||
[{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
defp spawn_compilers([], _original, _output, _options, [], [], _schedulers, result) do
|
||||
for {:module, mod} <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
|
||||
send child, {ref, :release}
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, but queue and waiting are not full or do not match
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{:struct_available, module} ->
|
||||
available = for {:struct, pid, _, waiting_module} <- waiting,
|
||||
@@ -131,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:struct, module}|result])
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
if callback = Keyword.get(options, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
@@ -147,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:module, module}|result])
|
||||
|
||||
{:waiting, kind, child, ref, on} ->
|
||||
@@ -160,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
waiting = [{kind, child, ref, on}|waiting]
|
||||
end
|
||||
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
|
||||
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
{:DOWN, _down_ref, :process, down_pid, {:shutdown, file}} ->
|
||||
if callback = Keyword.get(options, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
@@ -172,20 +176,26 @@ defmodule Kernel.ParallelCompiler do
|
||||
new_entries = List.delete(entries, down_pid)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
spawn_compilers(new_entries, original, output, options, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
handle_failure(down_ref, reason, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, reason, entries, waiting, queued) do
|
||||
if file = find_failure(ref, queued) do
|
||||
print_failure(file, reason)
|
||||
|
||||
if all_missing?(entries, waiting, queued) do
|
||||
collect_failures(queued, length(queued) - 1)
|
||||
end
|
||||
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
Process.exit(child, :kill)
|
||||
end
|
||||
|
||||
exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
@@ -197,7 +207,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp print_failure(_file, {:compiled, _}) do
|
||||
defp print_failure(_file, {:shutdown, _}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -211,7 +221,8 @@ defmodule Kernel.ParallelCompiler do
|
||||
IO.puts Exception.format(:exit, reason, [])
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
|
||||
:elixir_expand, :elixir_lexical]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
|
||||
@@ -172,10 +172,22 @@ defmodule Kernel.SpecialForms do
|
||||
iex> << 1, 2, 3 >>
|
||||
<< 1, 2, 3 >>
|
||||
|
||||
## Bitstring types
|
||||
## Types
|
||||
|
||||
A bitstring is made of many segments. Each segment has a
|
||||
type, which defaults to integer:
|
||||
A bitstring is made of many segments and each segment has a
|
||||
type. There are 9 types used in bitstrings:
|
||||
|
||||
- `integer`
|
||||
- `float`
|
||||
- `bits` (alias for bitstring)
|
||||
- `bitstring`
|
||||
- `binary`
|
||||
- `bytes` (alias for binary)
|
||||
- `utf8`
|
||||
- `utf16`
|
||||
- `utf32`
|
||||
|
||||
When no type is specified, the default is `integer`:
|
||||
|
||||
iex> <<1, 2, 3>>
|
||||
<<1, 2, 3>>
|
||||
@@ -186,14 +198,7 @@ defmodule Kernel.SpecialForms do
|
||||
iex> <<0, "foo">>
|
||||
<<0, 102, 111, 111>>
|
||||
|
||||
Any other type needs to be explicitly tagged. For example,
|
||||
in order to store a float type in the binary, one has to do:
|
||||
|
||||
iex> <<3.14 :: float>>
|
||||
<<64, 9, 30, 184, 81, 235, 133, 31>>
|
||||
|
||||
This also means that variables need to be explicitly tagged,
|
||||
otherwise Elixir defaults to integer:
|
||||
Variables or any other type need to be explicitly tagged:
|
||||
|
||||
iex> rest = "oo"
|
||||
iex> <<102, rest>>
|
||||
@@ -201,103 +206,161 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
We can solve this by explicitly tagging it as a binary:
|
||||
|
||||
<<102, rest :: binary>>
|
||||
|
||||
The type can be integer, float, bitstring/bits, binary/bytes,
|
||||
utf8, utf16 or utf32, e.g.:
|
||||
|
||||
<<102 :: float, rest :: binary>>
|
||||
|
||||
An integer can be any arbitrary precision integer. A float is an
|
||||
IEEE 754 binary32 or binary64 floating point number. A bitstring
|
||||
is an arbitrary series of bits. A binary is a special case of
|
||||
bitstring that has a total size divisible by 8.
|
||||
iex> rest = "oo"
|
||||
iex> <<102, rest :: binary>>
|
||||
"foo"
|
||||
|
||||
The utf8, utf16, and utf32 types are for unicode codepoints. They
|
||||
can also be applied to literal strings and char lists:
|
||||
|
||||
iex> <<"foo" :: utf16>>
|
||||
<<0,102,0,111,0,111>>
|
||||
<<0, 102, 0, 111, 0, 111>>
|
||||
iex> <<"foo" :: utf32>>
|
||||
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
|
||||
|
||||
The bits type is an alias for bitstring. The bytes type is an
|
||||
alias for binary.
|
||||
## Options
|
||||
|
||||
The signedness can also be given as signed or unsigned. The
|
||||
signedness only matters for matching and relevant only for
|
||||
integers. If unspecified, it defaults to unsigned. Example:
|
||||
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156,102,111,111>>
|
||||
|
||||
This match would have failed if we did not specify that the
|
||||
value -100 is signed. If we're matching into a variable instead
|
||||
of a value, the signedness won't be checked; rather, the number
|
||||
will simply be interpreted as having the given (or implied)
|
||||
signedness, e.g.:
|
||||
|
||||
iex> <<val, _rest :: binary>> = <<-100, "foo">>
|
||||
iex> val
|
||||
156
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
|
||||
The endianness of a segment can be big, little or native (the
|
||||
latter meaning it will be resolved at VM load time). Many options
|
||||
can be given by using `-` as separator:
|
||||
Many options can be given by using `-` as separator. Order is
|
||||
arbitrary, so the following are all equivalent:
|
||||
|
||||
<<102 :: integer-native, rest :: binary>>
|
||||
|
||||
Or:
|
||||
|
||||
<<102 :: native-integer, rest :: binary>>
|
||||
<<102 :: unsigned-big-integer, rest :: binary>>
|
||||
<<102 :: unsigned-big-integer-size(8), rest :: binary>>
|
||||
<<102 :: unsigned-big-integer-8, rest :: binary>>
|
||||
<<102 :: 8-integer-big-unsigned, rest :: binary>>
|
||||
<<102, rest :: binary>>
|
||||
|
||||
And so on.
|
||||
### Unit and Size
|
||||
|
||||
Endianness only makes sense for integers and some UTF code
|
||||
point types (utf16 and utf32).
|
||||
The length of the match is equal to the `unit` (a number of bits) times the
|
||||
`size` (the number of repeated segnments of length `unit`).
|
||||
|
||||
Finally, we can also specify size and unit for each segment. The
|
||||
unit is multiplied by the size to give the effective size of
|
||||
the segment in bits. The default unit for integers, floats,
|
||||
and bitstrings is 1. For binaries, it is 8.
|
||||
Type | Default Unit
|
||||
--------- | ------------
|
||||
`integer` | 1 bit
|
||||
`float` | 1 bit
|
||||
`binary` | 8 bits
|
||||
|
||||
Since integers are default, the default unit is 1. The example below
|
||||
matches because the string "foo" takes 24 bits and we match it
|
||||
against a segment of 24 bits, 8 of which are taken by the integer
|
||||
102 and the remaining 16 bits are specified on the rest.
|
||||
Sizes for types are a bit more nuanced. The default size for integers is 8.
|
||||
|
||||
iex> <<102, _rest :: size(16)>> = "foo"
|
||||
"foo"
|
||||
For floats, it is 64. For floats, `size * unit` must result in 32 or 64,
|
||||
corresponding to [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)
|
||||
binary32 and binary64, respectively.
|
||||
|
||||
We can also match by specifying size and unit explicitly:
|
||||
For binaries, the default is the size of the binary. Only the last binary in a
|
||||
match can use the default size. All others must have their size specified
|
||||
explicitly, even if the match is unambiguous. For example:
|
||||
|
||||
iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
|
||||
"foo"
|
||||
iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
|
||||
"Frank the Walrus"
|
||||
iex> {name, species}
|
||||
{"Frank", "Walrus"}
|
||||
|
||||
However, if we expect a size of 32, it won't match:
|
||||
Failing to specify the size for the non-last causes compilation to fail:
|
||||
|
||||
iex> <<102, _rest :: size(32)>> = "foo"
|
||||
** (MatchError) no match of right hand side value: "foo"
|
||||
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
|
||||
** (CompileError): a binary field without size is only allowed at the end of a binary pattern
|
||||
|
||||
Size and unit are not applicable to utf8, utf16, and utf32.
|
||||
|
||||
The default size for integers is 8. For floats, it is 64. For
|
||||
binaries, it is the size of the binary. Only the last binary
|
||||
in a binary match can use the default size (all others must
|
||||
have their size specified explicitly).
|
||||
#### Shortcut Syntax
|
||||
|
||||
Size and unit can also be specified using a syntax shortcut
|
||||
when passing integer values:
|
||||
|
||||
<< x :: 8 >> == << x :: size(8) >>
|
||||
<< x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
|
||||
<< x :: _ * 4 >> == << x :: unit(4) >>
|
||||
iex> x = 1
|
||||
iex> << x :: 8 >> == << x :: size(8) >>
|
||||
true
|
||||
iex> << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
|
||||
true
|
||||
|
||||
This syntax reflects the fact the effective size is given by
|
||||
multiplying the size by the unit.
|
||||
|
||||
For floats, `size * unit` must result in 32 or 64, corresponding
|
||||
to binary32 and binary64, respectively.
|
||||
### Modifiers
|
||||
|
||||
Some types have associated modifiers to clear up ambiguity in byte
|
||||
representation.
|
||||
|
||||
Modifier | Relevant Type(s)
|
||||
-------------------- | ----------------
|
||||
`signed` | `integer`
|
||||
`unsigned` (default) | `integer`
|
||||
`little` | `integer`, `utf16`, `utf32`
|
||||
`big` (default) | `integer`, `utf16`, `utf32`
|
||||
`native` | `integer`, `utf16`, `utf32`
|
||||
|
||||
### Sign
|
||||
|
||||
Integers can be `signed` or `unsigned`, defaulting to `unsigned`.
|
||||
|
||||
iex> <<int::integer>> = <<-100>>
|
||||
<<156>>
|
||||
iex> int
|
||||
156
|
||||
iex> <<int::integer-signed>> = <<-100>>
|
||||
<<156>>
|
||||
iex> int
|
||||
-100
|
||||
|
||||
`signed` and `unsigned` are only used for matching binaries (see below) and
|
||||
are only used for integers.
|
||||
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156, 102, 111, 111>>
|
||||
|
||||
### Endianness
|
||||
|
||||
Elixir has three options for endianness: `big`, `little`, and `native`.
|
||||
The default is `big`. `native` is determined by the VM at startup.
|
||||
|
||||
iex> <<number::little-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
256
|
||||
iex> <<number::big-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
1
|
||||
iex> <<number::native-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
256
|
||||
|
||||
## Binary/Bitstring Matching
|
||||
|
||||
Binary matching is a powerful feature in Elixir that is useful for extracting
|
||||
information from binaries as well as pattern matching.
|
||||
|
||||
Binary matching can be used by itself to extract information from binaries:
|
||||
|
||||
iex> <<"Hello, ", place::binary>> = "Hello, World"
|
||||
"Hello, World"
|
||||
iex> place
|
||||
"World"
|
||||
|
||||
Or as a part of function definitions to pattern match:
|
||||
|
||||
defmodule ImageTyper
|
||||
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
|
||||
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
|
||||
@jpg_signature <<255::size(8), 216::size(8)>>
|
||||
|
||||
def type(<<@png_signature, rest::binary>>), do: :png
|
||||
def type(<<@jpg_signature, rest::binary>>), do: :jpg
|
||||
def type(_), do :unknown
|
||||
end
|
||||
|
||||
### Performance & Optimizations
|
||||
|
||||
The Erlang compiler can provide a number of optimizations on binary creation
|
||||
and matching. To see optimization output, set the `bin_opt_info` compiler
|
||||
option:
|
||||
|
||||
ERL_COMPILER_OPTIONS=bin_opt_info mix compile
|
||||
|
||||
To learn more about specific optimizations and performance considerations,
|
||||
check out
|
||||
[Erlang's Efficiency Guide on handling binaries](http://www.erlang.org/doc/efficiency_guide/binaryhandling.html).
|
||||
"""
|
||||
defmacro unquote(:<<>>)(args)
|
||||
|
||||
@@ -343,28 +406,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
|
||||
This choice was done so every time single- or double-quotes are used, we have
|
||||
a remote call irregardless of the quote contents. This decision is also reflected
|
||||
a remote call regardless of the quote contents. This decision is also reflected
|
||||
in the quoted expressions discussed below.
|
||||
|
||||
## Runtime (dynamic) behaviour
|
||||
|
||||
The result returned by `.` is always specified by the right-side:
|
||||
|
||||
iex> x = String
|
||||
iex> x.downcase("FOO")
|
||||
"foo"
|
||||
iex> x.Sample
|
||||
String.Sample
|
||||
|
||||
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
|
||||
at runtime via `apply/3` and `Module.concat/2`:
|
||||
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
## Quoted expression
|
||||
|
||||
When `.` is used, the quoted expression may take two distinct
|
||||
@@ -1112,7 +1156,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine the situation you have a variable `name` and
|
||||
Imagine the situation you have a variable `value` and
|
||||
you want to inject it inside some quote. The first attempt
|
||||
would be:
|
||||
|
||||
@@ -1327,22 +1371,13 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
1. The head element of aliases can be any term.
|
||||
1. The head element of aliases can be any term that must expand to
|
||||
an atom at compilation time.
|
||||
|
||||
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__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
Module.concat [some_var, Foo]
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
|
||||
|
||||
@@ -644,6 +644,10 @@ defmodule Kernel.Typespec do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:user_type, line, name, args}) do
|
||||
typespec_to_ast({:type, line, name, args})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :tuple, :any}) do
|
||||
{:tuple, [line: line], []}
|
||||
end
|
||||
@@ -677,8 +681,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :map, fields}) do
|
||||
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
fields = Enum.map fields, fn
|
||||
# OTP 18
|
||||
{:type, _, :map_field_assoc, [k, v]} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
# OTP 17
|
||||
{:type, _, :map_field_assoc, k, v} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
end
|
||||
|
||||
{struct, fields} = Keyword.pop(fields, :__struct__)
|
||||
@@ -821,9 +830,17 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = :lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end, fields)
|
||||
fields =
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
:lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
|
||||
end, fields)
|
||||
else
|
||||
:lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end, fields)
|
||||
end
|
||||
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
@@ -884,7 +901,8 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:.., meta, args}, vars, caller) do
|
||||
typespec({:range, meta, args}, vars, caller)
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :range, args}
|
||||
end
|
||||
|
||||
# Handle special forms
|
||||
@@ -968,9 +986,21 @@ defmodule Kernel.Typespec do
|
||||
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:fun, meta, args}, vars, caller) do
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :fun, args}
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), name, arguments}
|
||||
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
arity = length(arguments)
|
||||
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
|
||||
{type, line(meta), name, arguments}
|
||||
else
|
||||
{:type, line(meta), name, arguments}
|
||||
end
|
||||
end
|
||||
|
||||
# Handle literals
|
||||
@@ -998,7 +1028,7 @@ defmodule Kernel.Typespec do
|
||||
typespec({:list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
defp typespec(list, vars, caller) when is_list(list) do
|
||||
[h|t] = :lists.reverse(list)
|
||||
union = :lists.foldl(fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
@@ -1006,6 +1036,10 @@ defmodule Kernel.Typespec do
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(other, _vars, caller) do
|
||||
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
|
||||
@@ -136,7 +136,7 @@ defmodule Keyword do
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: {:ok, value}
|
||||
@spec fetch(t, key) :: {:ok, value} | :error
|
||||
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{^key, value} -> {:ok, value}
|
||||
|
||||
+2
-22
@@ -247,7 +247,7 @@ defmodule List do
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
end
|
||||
@@ -324,26 +324,6 @@ defmodule List do
|
||||
do_zip(list_of_lists, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unzips the given list of tuples into a list of lists.
|
||||
|
||||
The number of unzipped elements is equal to the size
|
||||
of the smallest tuple in the list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.unzip([{1, 2}, {3, 4}])
|
||||
[[1, 3], [2, 4]]
|
||||
|
||||
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
@spec unzip([tuple]) :: [list]
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map &Tuple.to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with `value` inserted at the specified `index`.
|
||||
Note that `index` is capped at the list length. Negative indices
|
||||
@@ -532,7 +512,7 @@ defmodule List do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(char_list, non_neg_integer) :: integer
|
||||
@spec to_integer(char_list, 2..36) :: integer
|
||||
def to_integer(char_list, base) do
|
||||
:erlang.list_to_integer(char_list, base)
|
||||
end
|
||||
|
||||
@@ -17,6 +17,7 @@ defmodule Macro do
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :when, :in,
|
||||
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
@@ -39,7 +40,8 @@ defmodule Macro do
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
|
||||
o when o in [:|>, :<<<, :>>>] -> {:left, 170}
|
||||
o when o in [:|>, :<<<, :>>>, :<~, :~>,
|
||||
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
|
||||
:in -> {:left, 180}
|
||||
o when o in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
@@ -831,7 +833,7 @@ defmodule Macro do
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{receiver, true}
|
||||
aliases ->
|
||||
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
aliases = :lists.map(&elem(do_expand_once(&1, env), 0), aliases)
|
||||
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
@@ -937,7 +939,7 @@ defmodule Macro do
|
||||
be expanded.
|
||||
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
`expand_once/2` for more information and exmaples.
|
||||
`expand_once/2` for more information and examples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({tree, true}, env)
|
||||
|
||||
@@ -4,7 +4,22 @@ defmodule Macro.Env do
|
||||
|
||||
The current environment can be accessed at any time as
|
||||
`__ENV__`. Inside macros, the caller environment can be
|
||||
accessed as `__CALLER__`. It contains the following fields:
|
||||
accessed as `__CALLER__`.
|
||||
|
||||
An instance of `Macro.Env` must not be modified by hand. If you need to
|
||||
create a custom environment to pass to `Code.eval_quoted/3`, use the
|
||||
following trick:
|
||||
|
||||
def make_custom_env do
|
||||
import SomeModule, only: [some_function: 2]
|
||||
alias A.B.C
|
||||
__ENV__
|
||||
end
|
||||
|
||||
You may then call `make_custom_env()` to get a struct with the desired
|
||||
imports and aliases included.
|
||||
|
||||
It contains the following fields:
|
||||
|
||||
* `module` - the current module name
|
||||
* `file` - the current file name as a binary
|
||||
@@ -105,9 +120,9 @@ defmodule Macro.Env do
|
||||
"""
|
||||
def stacktrace(%{__struct__: Macro.Env} = env) do
|
||||
cond do
|
||||
nil?(env.module) ->
|
||||
is_nil(env.module) ->
|
||||
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
|
||||
nil?(env.function) ->
|
||||
is_nil(env.function) ->
|
||||
[{env.module, :__MODULE__, 0, relative_location(env)}]
|
||||
true ->
|
||||
{name, arity} = env.function
|
||||
|
||||
+33
-42
@@ -85,6 +85,9 @@ defmodule Module do
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
Several uses of `@compile` will accumulate instead of overriding
|
||||
previous ones.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@@ -476,11 +479,6 @@ defmodule Module do
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def function(mod, fun, arity) do
|
||||
:erlang.make_fun(mod, fun, arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
@@ -506,23 +504,23 @@ defmodule Module do
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
table = data_table_for(module)
|
||||
|
||||
{signature, _} = :lists.mapfoldl fn(x, acc) ->
|
||||
{simplify_signature(x, acc), acc + 1}
|
||||
end, 1, signature
|
||||
|
||||
case :ets.lookup(table, tuple) do
|
||||
case :ets.lookup(table, {:doc, tuple}) do
|
||||
[] ->
|
||||
:ets.insert(table, {tuple, line, kind, signature, doc})
|
||||
:ets.insert(table, {{:doc, tuple}, line, kind, signature, doc})
|
||||
:ok
|
||||
[{tuple, line, _old_kind, old_sign, old_doc}] ->
|
||||
[{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
|
||||
:ets.insert(table, {
|
||||
tuple,
|
||||
doc_tuple,
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
if(is_nil(doc), do: old_doc, else: doc)
|
||||
})
|
||||
:ok
|
||||
end
|
||||
@@ -614,7 +612,7 @@ defmodule Module do
|
||||
"""
|
||||
def defines?(module, tuple) when is_tuple(tuple) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
table = defs_table_for(module)
|
||||
:ets.lookup(table, tuple) != []
|
||||
end
|
||||
|
||||
@@ -634,7 +632,7 @@ defmodule Module do
|
||||
"""
|
||||
def defines?(module, tuple, kind) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
table = defs_table_for(module)
|
||||
case :ets.lookup(table, tuple) do
|
||||
[{_, ^kind, _, _, _, _, _}] -> true
|
||||
_ -> false
|
||||
@@ -654,8 +652,8 @@ defmodule Module do
|
||||
"""
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
|
||||
table = defs_table_for(module)
|
||||
:lists.concat :ets.match(table, {:'$1', :_, :_, :_, :_, :_, :_})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -673,8 +671,8 @@ defmodule Module do
|
||||
"""
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
table = defs_table_for(module)
|
||||
:lists.concat :ets.match(table, {:'$1', kind, :_, :_, :_, :_, :_})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -686,7 +684,7 @@ defmodule Module do
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
|
||||
for tuple <- tuples do
|
||||
:lists.foreach(fn tuple ->
|
||||
case :elixir_def.lookup_definition(module, tuple) do
|
||||
false ->
|
||||
{name, arity} = tuple
|
||||
@@ -694,27 +692,26 @@ defmodule Module do
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
|
||||
neighbours = if loaded?(Module.LocalsTracker) do
|
||||
Module.LocalsTracker.yank(module, tuple)
|
||||
else
|
||||
neighbours = if :elixir_compiler.get_opt(:internal) do
|
||||
[]
|
||||
else
|
||||
Module.LocalsTracker.yank(module, tuple)
|
||||
end
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
old = :elixir_def_overridable.overridable(module)
|
||||
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
|
||||
{count + 1, clause, neighbours, false}
|
||||
end, {1, clause, neighbours, false}, old)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
:elixir_def_overridable.overridable(module, merged)
|
||||
end
|
||||
end
|
||||
end, tuples)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `tuple` in `module` is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -733,7 +730,7 @@ defmodule Module do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = normalize_attribute(key, value)
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
|
||||
new =
|
||||
if :lists.member(key, acc) do
|
||||
@@ -834,13 +831,13 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
|
||||
if Keyword.get(opts, :persist) do
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, {:__persisted_attributes, [new|old]})
|
||||
old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
|
||||
:ets.insert(table, {{:elixir, :persisted_attributes}, [new|old]})
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate) do
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, {:__acc_attributes, [new|old]})
|
||||
old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
:ets.insert(table, {{:elixir, :acc_attributes}, [new|old]})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -896,14 +893,14 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val}] -> val
|
||||
[] ->
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
@@ -958,15 +955,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp data_table_for(module) do
|
||||
module
|
||||
:elixir_module.data_table(module)
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
:elixir_def.table(module)
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
:elixir_module.docs_table(module)
|
||||
defp defs_table_for(module) do
|
||||
:elixir_module.defs_table(module)
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
@@ -974,6 +967,4 @@ defmodule Module do
|
||||
raise ArgumentError,
|
||||
"could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
|
||||
defp loaded?(module), do: is_tuple :code.is_loaded(module)
|
||||
end
|
||||
|
||||
@@ -83,7 +83,7 @@ defmodule Module.LocalsTracker do
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{_, val}] = :ets.lookup(table, :__locals_tracker)
|
||||
[{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
|
||||
val
|
||||
end
|
||||
|
||||
@@ -360,7 +360,7 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
defp replace_edge!(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
||||
end
|
||||
:ok
|
||||
|
||||
@@ -30,7 +30,7 @@ defmodule Node do
|
||||
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
|
||||
local node is not alive.
|
||||
"""
|
||||
@spec stop() :: :ok | {:error, term}
|
||||
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
|
||||
def stop() do
|
||||
:net_kernel.stop()
|
||||
end
|
||||
|
||||
@@ -345,7 +345,7 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
|
||||
"""
|
||||
@spec flag(pid, process_flag, term) :: term
|
||||
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
|
||||
def flag(pid, flag, value) do
|
||||
:erlang.process_flag(pid, flag, value)
|
||||
end
|
||||
|
||||
+14
-12
@@ -19,11 +19,12 @@ defmodule Protocol do
|
||||
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = :lists.map(fn _ -> quote(do: term) end,
|
||||
:lists.seq(2, arity))
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
call_args = for i <- :lists.seq(2, arity),
|
||||
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
|
||||
call_args = :lists.map(fn i -> {String.to_atom(<<?x, i + 64>>), [], __MODULE__} end,
|
||||
:lists.seq(2, arity))
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
quote do
|
||||
@@ -440,7 +441,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- builtin do
|
||||
:lists.foreach(fn {guard, mod} ->
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
@@ -449,7 +450,7 @@ defmodule Protocol do
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
end, builtin)
|
||||
|
||||
@spec impl_for!(term) :: atom() | no_return()
|
||||
Kernel.def impl_for!(data) do
|
||||
@@ -609,16 +610,17 @@ defmodule Protocol do
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
signature = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
found =
|
||||
for {:spec, expr, caller} <- specs,
|
||||
Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Kernel.Typespec.define_spec(:callback, expr, caller)
|
||||
true
|
||||
end
|
||||
:lists.map(fn {:spec, expr, caller} ->
|
||||
if Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Kernel.Typespec.define_spec(:callback, expr, caller)
|
||||
true
|
||||
end
|
||||
end, specs)
|
||||
|
||||
found != []
|
||||
:lists.any(& &1 == true, found)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Record do
|
||||
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
iex> Record.record? {User, "john", 27}
|
||||
iex> Record.is_record {User, "john", 27}
|
||||
true
|
||||
|
||||
This module provides conveniences for working with records at
|
||||
@@ -64,11 +64,11 @@ defmodule Record do
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.record?(record, User)
|
||||
iex> Record.is_record(record, User)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro record?(data, kind) do
|
||||
defmacro is_record(data, kind) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -92,14 +92,14 @@ defmodule Record do
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.record?(record)
|
||||
iex> Record.is_record(record)
|
||||
true
|
||||
iex> tuple = {}
|
||||
iex> Record.record?(tuple)
|
||||
iex> Record.is_record(tuple)
|
||||
false
|
||||
|
||||
"""
|
||||
defmacro record?(data) do
|
||||
defmacro is_record(data) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -319,7 +319,7 @@ defmodule Record do
|
||||
# Returns a keyword list of the record
|
||||
@doc false
|
||||
def __keyword__(atom, fields, record) do
|
||||
if record?(record, atom) do
|
||||
if is_record(record, atom) do
|
||||
[_tag|values] = Tuple.to_list(record)
|
||||
join_keyword(fields, values, [])
|
||||
else
|
||||
|
||||
@@ -45,10 +45,11 @@ defmodule Record.Extractor do
|
||||
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.
|
||||
# includes record but with macros and other attributes expanded,
|
||||
# such as `-include(...)` and `-include_lib(...)`. This is done
|
||||
# by using Erlang's epp.
|
||||
defp read_file(file) do
|
||||
case :epp_dodger.quick_parse_file(file) do
|
||||
case :epp.parse_file(file, []) do
|
||||
{:ok, form} ->
|
||||
form
|
||||
other ->
|
||||
@@ -80,11 +81,11 @@ defmodule Record.Extractor do
|
||||
|
||||
defp eval_record(cons, form) do
|
||||
form = form ++
|
||||
[ {:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ cons ]} ]} ]
|
||||
[{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [cons]}]}]
|
||||
|
||||
{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
|
||||
{:clause, 0, [], [], [record_ast]}]} = :erl_expand_records.module(form, []) |> List.last
|
||||
|
||||
{:value, record, _} = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
|
||||
+22
-5
@@ -78,7 +78,7 @@ defmodule Regex do
|
||||
|
||||
defstruct re_pattern: nil, source: "", opts: ""
|
||||
|
||||
@opaque t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
|
||||
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
|
||||
|
||||
defmodule CompileError do
|
||||
defexception message: "regex could not be compiled"
|
||||
@@ -133,6 +133,7 @@ defmodule Regex do
|
||||
Compiles the regular expression according to the given options.
|
||||
Fails with `Regex.CompileError` if the regex cannot be compiled.
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: t
|
||||
def compile!(source, options \\ "") do
|
||||
case compile(source, options) do
|
||||
{:ok, regex} -> regex
|
||||
@@ -152,6 +153,7 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, String.t) :: boolean
|
||||
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{:capture, :none}]) == :match
|
||||
end
|
||||
@@ -168,6 +170,8 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec regex?(t) :: true
|
||||
@spec regex?(any) :: false
|
||||
def regex?(%Regex{}), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@@ -193,6 +197,7 @@ defmodule Regex do
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
@@ -223,6 +228,7 @@ defmodule Regex do
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec named_captures(t, String.t, [term]) :: map | nil
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
names = names(regex)
|
||||
options = Keyword.put(options, :capture, names)
|
||||
@@ -233,6 +239,7 @@ defmodule Regex do
|
||||
@doc """
|
||||
Returns the underlying `re_pattern` in the regular expression.
|
||||
"""
|
||||
@spec re_pattern(t) :: term
|
||||
def re_pattern(%Regex{re_pattern: compiled}) do
|
||||
compiled
|
||||
end
|
||||
@@ -246,6 +253,7 @@ defmodule Regex do
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@spec source(t) :: String.t
|
||||
def source(%Regex{source: source}) do
|
||||
source
|
||||
end
|
||||
@@ -259,6 +267,7 @@ defmodule Regex do
|
||||
"m"
|
||||
|
||||
"""
|
||||
@spec opts(t) :: String.t
|
||||
def opts(%Regex{opts: opts}) do
|
||||
opts
|
||||
end
|
||||
@@ -272,6 +281,7 @@ defmodule Regex do
|
||||
["foo"]
|
||||
|
||||
"""
|
||||
@spec names(t) :: [String.t]
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
names
|
||||
@@ -301,6 +311,7 @@ defmodule Regex do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec scan(t, String.t, [term]) :: [[String.t]]
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
@@ -353,7 +364,7 @@ defmodule Regex do
|
||||
["a", "c"]
|
||||
|
||||
"""
|
||||
|
||||
@spec split(t, String.t, [term]) :: [String.t]
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(%Regex{}, "", _opts), do: [""]
|
||||
@@ -408,8 +419,8 @@ defmodule Regex do
|
||||
|
||||
The replacement can be either a string or a function. The string
|
||||
is used as a replacement for every match and it allows specific
|
||||
captures to be accessed via `\N`, where `N` is the capture. In
|
||||
case `\0` is used, the whole match is inserted.
|
||||
captures to be accessed via `\N` or `\g{N}`, where `N` is the
|
||||
capture. In case `\0` is used, the whole match is inserted.
|
||||
|
||||
When the replacement is a function, the function may have arity
|
||||
N where each argument maps to a capture, with the first argument
|
||||
@@ -439,6 +450,7 @@ defmodule Regex do
|
||||
"[b][d]"
|
||||
|
||||
"""
|
||||
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex, string, replacement, options) when is_binary(replacement) do
|
||||
@@ -467,6 +479,11 @@ defmodule Regex do
|
||||
defp precompile_replacement(""),
|
||||
do: []
|
||||
|
||||
defp precompile_replacement(<<?\\, ?g, ?{, rest :: binary>>) when byte_size(rest) > 0 do
|
||||
{ns, <<?}, rest :: binary>>} = pick_int(rest)
|
||||
[List.to_integer(ns) | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
|
||||
case precompile_replacement(rest) do
|
||||
[head | t] when is_binary(head) ->
|
||||
@@ -538,7 +555,7 @@ defmodule Regex do
|
||||
cond do
|
||||
is_binary(part) ->
|
||||
part
|
||||
part > tuple_size(indexes) ->
|
||||
part >= tuple_size(indexes) ->
|
||||
""
|
||||
true ->
|
||||
get_index(string, elem(indexes, part))
|
||||
|
||||
@@ -198,7 +198,7 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
|
||||
target1.reduce(set1, {:cont, target1.new}, fn v, acc ->
|
||||
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
+149
-44
@@ -156,18 +156,22 @@ defmodule Stream do
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
|
||||
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
lazy enum, {[], 0},
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
fn(f1) -> &do_chunk(&1, n, pad, f1) end
|
||||
if is_nil(pad) do
|
||||
lazy enum, {[], 0}, fn(f1) -> R.chunk(n, step, limit, f1) end
|
||||
else
|
||||
lazy enum, {[], 0},
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
&do_chunk(&1, n, pad, &2)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
|
||||
if nil?(pad) || count == 0 do
|
||||
{:cont, acc}
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
cont_with_acc(f1, buffer, h, old, t)
|
||||
end
|
||||
defp do_chunk(acc(_, {_, 0}, _) = acc, _, _, _) do
|
||||
{:cont, acc}
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t), n, pad, f1) do
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
cont_with_acc(f1, buffer, h, old, t)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -186,7 +190,7 @@ defmodule Stream do
|
||||
def chunk_by(enum, fun) do
|
||||
lazy enum, nil,
|
||||
fn(f1) -> R.chunk_by(fun, f1) end,
|
||||
fn(f1) -> &do_chunk_by(&1, f1) end
|
||||
&do_chunk_by(&1, &2)
|
||||
end
|
||||
|
||||
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
|
||||
@@ -338,6 +342,25 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a value every `n` milliseconds.
|
||||
|
||||
The values emitted are an increasing counter starting at `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.interval(10) |> Enum.take(10)
|
||||
[0,1,2,3,4,5,6,7,8,9]
|
||||
|
||||
"""
|
||||
@spec interval(non_neg_integer) :: Enumerable.t
|
||||
def interval(n) do
|
||||
unfold 0, fn (count) ->
|
||||
:timer.sleep(n)
|
||||
{count, count + 1}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Injects the stream values into the given collectable as a side-effect.
|
||||
|
||||
@@ -495,11 +518,11 @@ defmodule Stream do
|
||||
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
|
||||
def take(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
def take(enum, n) when n > 0 do
|
||||
def take(enum, n) when n > 0 and is_integer(n) do
|
||||
lazy enum, n, fn(f1) -> R.take(f1) end
|
||||
end
|
||||
|
||||
def take(enum, n) when n < 0 do
|
||||
def take(enum, n) when n < 0 and is_integer(n) do
|
||||
&do_take(enum, abs(n), &1, &2)
|
||||
end
|
||||
|
||||
@@ -563,6 +586,22 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a single value after `n` milliseconds.
|
||||
|
||||
The value emitted is `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.timer(10) |> Enum.to_list
|
||||
[0]
|
||||
|
||||
"""
|
||||
@spec timer(non_neg_integer) :: Enumerable.t
|
||||
def timer(n) do
|
||||
take(interval(n), 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
@@ -577,7 +616,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
`Stream.transform/3` is a useful as it can be used as basis to implement
|
||||
`Stream.transform/3` is useful as it can be used as the basis to implement
|
||||
many of the functions defined in this module. For example, we can implement
|
||||
`Stream.take(enum, n)` as follows:
|
||||
|
||||
@@ -660,9 +699,11 @@ defmodule Stream do
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:halted, [:outer|acc]} ->
|
||||
# Only take into account outer halts when the op is not halt itself.
|
||||
# Otherwise, we were the ones wishing to halt, so we should just stop.
|
||||
{:halted, [:outer|acc]} when op != :halt ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:halted, [:inner|acc]} ->
|
||||
{:halted, [_|acc]} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, acc}
|
||||
{:done, [_|acc]} ->
|
||||
@@ -847,6 +888,15 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: Enumerable.t
|
||||
def cycle(enumerable)
|
||||
|
||||
def cycle(enumerable) when is_list(enumerable) do
|
||||
unfold {enumerable, enumerable}, fn
|
||||
{source, [h | t]} -> {h, {source, t}}
|
||||
{source = [h | t], []} -> {h, {source, t}}
|
||||
end
|
||||
end
|
||||
|
||||
def cycle(enumerable) do
|
||||
fn acc, fun ->
|
||||
inner = &do_cycle_each(&1, &2, fun)
|
||||
@@ -934,14 +984,15 @@ defmodule Stream do
|
||||
@doc """
|
||||
Emits a sequence of values for the given resource.
|
||||
|
||||
Similar to `unfold/2` but the initial value is computed lazily via
|
||||
`start_fun` and executes an `after_fun` at the end of enumeration
|
||||
(both in cases of success and failure).
|
||||
Similar to `transform/2` but the initial accumulated value is
|
||||
computed lazily via `start_fun` and executes an `after_fun` at
|
||||
the end of enumeration (both in cases of success and failure).
|
||||
|
||||
Successive values are generated by calling `next_fun` with the
|
||||
previous accumulator (the initial value being the result returned
|
||||
by `start_fun`) and it must return a tuple with the current and
|
||||
next accumulator. The enumeration finishes if it returns `nil`.
|
||||
by `start_fun`) and it must return a tuple containing a list
|
||||
of items to be emitted and the next accumulator. The enumeration
|
||||
finishes if it returns `{:halt, acc}`.
|
||||
|
||||
As the name says, this function is useful to stream values from
|
||||
resources.
|
||||
@@ -951,8 +1002,8 @@ defmodule Stream do
|
||||
Stream.resource(fn -> File.open!("sample") end,
|
||||
fn file ->
|
||||
case IO.read(file, :line) do
|
||||
data when is_binary(data) -> {data, file}
|
||||
_ -> nil
|
||||
data when is_binary(data) -> {[data], file}
|
||||
_ -> {:halt, file}
|
||||
end
|
||||
end,
|
||||
fn file -> File.close(file) end)
|
||||
@@ -974,9 +1025,11 @@ defmodule Stream do
|
||||
|
||||
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
|
||||
try do
|
||||
# Optimize the most common cases
|
||||
case next_fun.(next_acc) do
|
||||
nil -> nil
|
||||
{v, next_acc} -> {fun.(v, acc), next_acc}
|
||||
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
|
||||
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
|
||||
{_, _} = other -> other
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -984,11 +1037,62 @@ defmodule Stream do
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
{:done, acc}
|
||||
{acc, next_acc} ->
|
||||
{:opt, acc, next_acc} ->
|
||||
do_resource(next_acc, next_fun, acc, fun, after_fun)
|
||||
{:halt, next_acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{list, next_acc} when is_list(list) ->
|
||||
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.List.reduce(list, &1, fun))
|
||||
{enum, next_acc} ->
|
||||
inner = &do_resource_each(&1, &2, fun)
|
||||
do_enum_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.reduce(enum, &1, inner))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_list_resource(next_acc, next_fun, acc, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.(acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_list_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_enum_resource(next_acc, next_fun, {op, acc}, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.({op, [:outer|acc]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:halted, [:outer|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, [:inner|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:done, [_|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:suspended, [_|acc], c} ->
|
||||
{:suspended, acc, &do_enum_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_resource_each(x, [:outer|acc], f) do
|
||||
case f.(x, acc) do
|
||||
{:halt, res} -> {:halt, [:inner|res]}
|
||||
{op, res} -> {op, [:outer|res]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1029,12 +1133,12 @@ defmodule Stream do
|
||||
|
||||
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
|
||||
|
||||
defp lazy(%Stream{funs: funs} = lazy, fun),
|
||||
defp lazy(%Stream{done: nil, funs: funs} = lazy, fun),
|
||||
do: %{lazy | funs: [fun|funs] }
|
||||
defp lazy(enum, fun),
|
||||
do: %Stream{enum: enum, funs: [fun]}
|
||||
|
||||
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
|
||||
defp lazy(enum, acc, fun),
|
||||
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
|
||||
@@ -1073,19 +1177,20 @@ defimpl Enumerable, for: Stream do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
{:halted, accs} ->
|
||||
do_done {:halted, accs}, done
|
||||
{:done, accs} ->
|
||||
do_done {:done, accs}, done
|
||||
end
|
||||
end
|
||||
|
||||
defp do_done({reason, [acc|_]}, nil), do: {reason, acc}
|
||||
defp do_done({reason, [acc|t]}, {done, fun}) do
|
||||
[h|_] = Enum.reverse(t)
|
||||
case done.([acc, h], fun) do
|
||||
{:cont, [acc|_]} -> {reason, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,10 +132,16 @@ defmodule Stream.Reducers do
|
||||
defmacro take(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, n, t) = orig) ->
|
||||
if n >= 1 do
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
else
|
||||
{:halt, orig}
|
||||
case n do
|
||||
0 ->
|
||||
{:halt, orig}
|
||||
1 ->
|
||||
case cont_with_acc(unquote(f), entry, h, n-1, t) do
|
||||
{:cont, acc} -> {:halt, acc}
|
||||
reason -> reason
|
||||
end
|
||||
_ ->
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+87
-26
@@ -4,28 +4,12 @@ defmodule String do
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
The functions in this module act according to the
|
||||
Unicode Standard, version 6.3.0. For example,
|
||||
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
|
||||
module.
|
||||
|
||||
In addition to this module, Elixir provides more low-level
|
||||
operations that work directly with binaries. Some
|
||||
of those can be found in the `Kernel` module, as:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
As per the Unicode Standard, a codepoint is an Unicode
|
||||
Character, which may be represented by one or more bytes.
|
||||
For example, the character "é" is represented with two
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 6.3.0.As per the standard, a codepoint is
|
||||
an Unicode Character, which may be represented by one or more
|
||||
bytes. For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> byte_size("é")
|
||||
@@ -66,6 +50,82 @@ defmodule String do
|
||||
This current Elixir version implements Extended Grapheme Cluster
|
||||
algorithm.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
To act accordingly to the Unicode Standard, many functions
|
||||
in this module runs in linear time, as it needs to traverse
|
||||
the whole string considering the proper Unicode codepoints.
|
||||
|
||||
For example, `String.length/1` is going to take longer as
|
||||
the input grows. On the other hand, `byte_size/1` always runs
|
||||
in constant time (i.e. regardless of the input size).
|
||||
|
||||
This means often there are performance costs in using the
|
||||
functions in this module, compared to the more low-level
|
||||
operations that work directly with binaries:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
There are many situations where using the `String` module can
|
||||
be avoided in favor of binary functions or pattern matching.
|
||||
For example, imagine you have a string `prefix` and you want to
|
||||
remove this prefix from another string named `full`.
|
||||
|
||||
One may be tempted to write:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base, String.length(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Although the function above works, it performs poorly. To
|
||||
calculate the length of the string, we need to traverse it
|
||||
fully, so we traverse both `prefix` and `full` strings, then
|
||||
slice the `full` one, traversing it again.
|
||||
|
||||
A first attempting at improving it could be with ranges:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base..-1)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
While this is much better (we don't traverse `full` twice),
|
||||
it could still be improved. In this case, since we want to
|
||||
extract a substring from a string, we can use `byte_size/1`
|
||||
and `binary_part/3` as there is no chance we will slice in
|
||||
the middle of a codepoint made of more than one byte:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> binary_part(full, base, byte_size(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Or simply used pattern matching:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> <<_ :: binary-size(base), rest :: binary>> = full
|
||||
...> rest
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
On the other hand, if you want to dynamically slice a string
|
||||
based on an integer value, then using `String.slice/3` is the
|
||||
best option as it guarantees we won't incorrectly split a valid
|
||||
codepoint in multiple bytes.
|
||||
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
@@ -590,13 +650,14 @@ defmodule String do
|
||||
iex> String.replace("a,b,c", ",", "-", global: false)
|
||||
"a-b,c"
|
||||
|
||||
The pattern can also be a regex. In those cases, one can give `\N`
|
||||
in the `replacement` string to access a specific capture in the regex:
|
||||
The pattern can also be a regex. In those cases, one can give `\N` or
|
||||
`\g{N}` in the `replacement` string to access a specific capture in the
|
||||
regex:
|
||||
|
||||
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
|
||||
"a,bb,cc"
|
||||
|
||||
Notice we had to escape the escape character `\`. By giving `&`,
|
||||
Notice we had to escape the escape character `\`. By giving `\0`,
|
||||
one can inject the whole matched pattern in the replacement string.
|
||||
|
||||
When strings are used as a pattern, a developer can also use the
|
||||
@@ -612,8 +673,8 @@ defmodule String do
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
@spec replace(t, t | Regex.t, t) :: t
|
||||
@spec replace(t, t | Regex.t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
|
||||
if Regex.regex?(pattern) do
|
||||
@@ -1363,7 +1424,7 @@ defmodule String do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t, pos_integer) :: integer
|
||||
@spec to_integer(String.t, 2..36) :: integer
|
||||
def to_integer(string, base) do
|
||||
:erlang.binary_to_integer(string, base)
|
||||
end
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: {:ok, pid}
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, {string, options}, [])
|
||||
GenServer.start_link(__MODULE__, {string, options}, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -52,7 +52,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec contents(pid) :: {binary, binary}
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
GenServer.call(pid, :contents)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -68,7 +68,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec close(pid) :: {:ok, {binary, binary}}
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
GenServer.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
|
||||
@@ -20,8 +20,8 @@ defmodule Supervisor do
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
def start_link(state) do
|
||||
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
|
||||
def start_link(state, opts) do
|
||||
GenServer.start_link(__MODULE__, state, opts)
|
||||
end
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
@@ -38,16 +38,17 @@ defmodule Supervisor do
|
||||
# Import helpers for defining supervisors
|
||||
import Supervisor.Spec
|
||||
|
||||
# We are going to supervise the Stack server which will
|
||||
# be started with a single argument [:hello]
|
||||
# We are going to supervise the Stack server which
|
||||
# will be started with a single argument [:hello]
|
||||
# and the default name of :sup_stack.
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
worker(Stack, [[:hello], [name: :sup_stack]])
|
||||
]
|
||||
|
||||
# Start the supervisor with our one child
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
Notice that when starting the GenServer, we have registered it
|
||||
Notice that when starting the GenServer, we are registering it
|
||||
with name `:sup_stack`, which allows us to call it directly and
|
||||
get what is on the stack:
|
||||
|
||||
@@ -65,7 +66,7 @@ defmodule Supervisor do
|
||||
Let's try it:
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
=ERROR REPORT====
|
||||
** (exit) exited in: GenServer.call(:sup_stack, :pop, 5000)
|
||||
|
||||
Luckily, since the server is being supervised by a supervisor, the
|
||||
supervisor will automatically start a new one, with the default stack
|
||||
@@ -132,10 +133,74 @@ defmodule Supervisor do
|
||||
|
||||
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one children. Many functions
|
||||
supervisor specification to contain only one child. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used.
|
||||
|
||||
## Simple one for one
|
||||
|
||||
The simple one for one supervisor is useful when you want to dynamically
|
||||
start and stop supervisor children. For example, imagine you want to
|
||||
dynamically create multiple stacks. We can do so by defining a simple one
|
||||
for one supervisor:
|
||||
|
||||
# Import helpers for defining supervisors
|
||||
import Supervisor.Spec
|
||||
|
||||
# This time, we don't pass any argument because
|
||||
# the argument will be given when we start the child
|
||||
children = [
|
||||
worker(Stack, [], restart: :transient)
|
||||
]
|
||||
|
||||
# Start the supervisor with our one child
|
||||
{:ok, sup_pid} = Supervisor.start_link(children, strategy: :simple_one_for_one)
|
||||
|
||||
There are a couple differences here:
|
||||
|
||||
* The simple one for one specification can define only one child which
|
||||
works as a template for when we call `start_child/2`
|
||||
|
||||
* We have define the child to have restart strategy of transient. This
|
||||
means that, if the child process exits due to a `:normal`, `:shutdown`
|
||||
or `{:shutdown, term}` reason, it won't be restarted. This is useful
|
||||
as it allows our workers to politely shutdown and be removed from the
|
||||
simple one for one supervisor, without being restarted. You can find
|
||||
more information about restart strategies on `Supervisor.Spec`
|
||||
|
||||
With the supervisor defined, let's dynamically start stacks:
|
||||
|
||||
{:ok, pid} = Supervisor.start_child(sup_pid, [[:hello, :world], []])
|
||||
GenServer.call(pid, :pop) #=> :hello
|
||||
GenServer.call(pid, :pop) #=> :world
|
||||
|
||||
{:ok, pid} = Supervisor.start_child(sup_pid, [[:something, :else], []])
|
||||
GenServer.call(pid, :pop) #=> :something
|
||||
GenServer.call(pid, :pop) #=> :else
|
||||
|
||||
Supervisor.count_children(sup_pid)
|
||||
#=> %{active: 2, specs: 1, supervisors: 0, workers: 2}
|
||||
|
||||
## Exit reasons
|
||||
|
||||
From the example above, you may have noticed that the transient restart
|
||||
strategy for the worker does not restart the child in case it crashes with
|
||||
reason `:normal`, `:shutdown` or `{:shutdown, term}`.
|
||||
|
||||
So one may ask: which exit reason should I choose when existing my worker?
|
||||
There are three options:
|
||||
|
||||
* `:normal` - on such cases, the exit won't be logged, there is no restart
|
||||
on transient mode and linked processes do not exit
|
||||
|
||||
* `:shutdown` or `{:shutdown, term}` - on such cases, the exit won't be
|
||||
logged, there is no restart on transient mode and linked processes exit
|
||||
with the same reason unless trapping exits
|
||||
|
||||
* any other term - on such cases, the exit will be logged, there are
|
||||
restarts on transient mode and linked processes exit with the same reason
|
||||
unless trapping exits
|
||||
|
||||
## Name Registration
|
||||
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
@@ -268,7 +333,9 @@ defmodule Supervisor do
|
||||
and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
|
||||
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
|
||||
def start_child(supervisor, child_spec_or_args) do
|
||||
call(supervisor, {:start_child, child_spec_or_args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given pid or child id.
|
||||
@@ -290,7 +357,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
|
||||
def terminate_child(supervisor, pid_or_child_id) do
|
||||
call(supervisor, {:terminate_child, pid_or_child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the child specification identified by `child_id`.
|
||||
@@ -306,7 +375,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting
|
||||
defdelegate delete_child(supervisor, child_id), to: :supervisor
|
||||
def delete_child(supervisor, child_id) do
|
||||
call(supervisor, {:delete_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Restarts a child process identified by `child_id`.
|
||||
@@ -335,7 +406,9 @@ defmodule Supervisor do
|
||||
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
|
||||
{:ok, child} | {:ok, child, term} | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting | term
|
||||
defdelegate restart_child(supervisor, child_id), to: :supervisor
|
||||
def restart_child(supervisor, child_id) do
|
||||
call(supervisor, {:restart_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children.
|
||||
@@ -361,7 +434,9 @@ defmodule Supervisor do
|
||||
child | :restarting,
|
||||
Supervisor.Spec.worker,
|
||||
Supervisor.Spec.modules}]
|
||||
defdelegate which_children(supervisor), to: :supervisor
|
||||
def which_children(supervisor) do
|
||||
call(supervisor, :which_children)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a map containing count values for the supervisor.
|
||||
@@ -381,9 +456,15 @@ defmodule Supervisor do
|
||||
|
||||
"""
|
||||
@spec count_children(supervisor) ::
|
||||
[specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer]
|
||||
%{specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer}
|
||||
def count_children(supervisor) do
|
||||
:supervisor.count_children(supervisor) |> :maps.from_list
|
||||
call(supervisor, :count_children) |> :maps.from_list
|
||||
end
|
||||
|
||||
@compile {:inline, call: 2}
|
||||
|
||||
defp call(supervisor, req) do
|
||||
GenServer.call(supervisor, req, :infinity)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -145,12 +145,12 @@ defmodule Supervisor.Spec do
|
||||
in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5.
|
||||
a time frame. Defaults to 3.
|
||||
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
Defaults to 5.
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
The `:strategy` option is required and by default maximum 3 restarts
|
||||
are allowed within 5 seconds. Please check the `Supervisor` module for
|
||||
a complete description of the available strategies.
|
||||
"""
|
||||
@@ -162,7 +162,7 @@ defmodule Supervisor.Spec do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxR = Keyword.get(options, :max_restarts, 3)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
|
||||
|
||||
+23
-11
@@ -72,7 +72,7 @@ defmodule System do
|
||||
"""
|
||||
@spec argv() :: [String.t]
|
||||
def argv do
|
||||
:elixir_code_server.call :argv
|
||||
:elixir_config.get(:argv)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -83,7 +83,7 @@ defmodule System do
|
||||
"""
|
||||
@spec argv([String.t]) :: :ok
|
||||
def argv(args) do
|
||||
:elixir_code_server.cast({:argv, args})
|
||||
:elixir_config.put(:argv, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -94,11 +94,20 @@ defmodule System do
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> IO.chardata_to_string(base)
|
||||
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
|
||||
case :os.type() do
|
||||
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
|
||||
_ -> original
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter(original), do: original
|
||||
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
@@ -215,7 +224,7 @@ defmodule System do
|
||||
The function must receive the exit status code as an argument.
|
||||
"""
|
||||
def at_exit(fun) when is_function(fun, 1) do
|
||||
:elixir_code_server.cast {:at_exit, fun}
|
||||
:elixir_config.update :at_exit, &[fun|&1]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -422,18 +431,18 @@ defmodule System do
|
||||
@spec cmd(binary, [binary], Keyword.t) ::
|
||||
{Collectable.t, exit_status :: non_neg_integer}
|
||||
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
|
||||
command = String.to_char_list(command)
|
||||
cmd = String.to_char_list(command)
|
||||
|
||||
command =
|
||||
if Path.type(command) == :absolute do
|
||||
command
|
||||
cmd =
|
||||
if Path.type(cmd) == :absolute do
|
||||
cmd
|
||||
else
|
||||
:os.find_executable(command) || command
|
||||
:os.find_executable(cmd) || :erlang.error(:enoent, [command, args, opts])
|
||||
end
|
||||
|
||||
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
|
||||
{initial, fun} = Collectable.into(into)
|
||||
do_cmd Port.open({:spawn_executable, command}, opts), initial, fun
|
||||
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
|
||||
end
|
||||
|
||||
defp do_cmd(port, acc, fun) do
|
||||
@@ -454,9 +463,12 @@ defmodule System do
|
||||
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
|
||||
do: cmd_opts(t, [{:arg0, bin}|opts], into)
|
||||
|
||||
defp cmd_opts([{:stderr_to_stdout, bool}|t], opts, into) when is_boolean(bool),
|
||||
defp cmd_opts([{:stderr_to_stdout, true}|t], opts, into),
|
||||
do: cmd_opts(t, [:stderr_to_stdout|opts], into)
|
||||
|
||||
defp cmd_opts([{:stderr_to_stdout, false}|t], opts, into),
|
||||
do: cmd_opts(t, opts, into)
|
||||
|
||||
defp cmd_opts([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
|
||||
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
|
||||
|
||||
|
||||
@@ -119,6 +119,30 @@ defmodule Task do
|
||||
Task.Supervised.start_link(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(fun) :: {:ok, pid}
|
||||
def start(fun) do
|
||||
start(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(module, atom, [term]) :: {:ok, pid}
|
||||
def start(mod, fun, args) do
|
||||
Task.Supervised.start(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
def start(info, fun) do
|
||||
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(info, fun) do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
@@ -25,14 +25,23 @@ defmodule Task.Supervisor do
|
||||
described under the `Name Registration` section in the `GenServer` module
|
||||
docs;
|
||||
|
||||
* `:restart` - the restart strategy, may be `:temporary` (the default),
|
||||
`:transient` or `:permanent`. Check `Supervisor.Spec` for more info.
|
||||
Defaults to temporary as most tasks can't be effectively restarted after
|
||||
a crash;
|
||||
|
||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||
or an integer indicating the timeout value, defaults to 5000 milliseconds;
|
||||
|
||||
* `:max_restarts` and `:max_seconds` - as specified in `Supervisor.Spec.supervise/2`;
|
||||
|
||||
"""
|
||||
@spec start_link(Supervisor.options) :: Supervisor.on_start
|
||||
def start_link(opts \\ []) do
|
||||
import Supervisor.Spec
|
||||
{restart, opts} = Keyword.pop(opts, :restart, :temporary)
|
||||
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
|
||||
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
|
||||
children = [worker(Task.Supervised, [], restart: restart, shutdown: shutdown)]
|
||||
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
|
||||
end
|
||||
|
||||
|
||||
+56
-37
@@ -11,18 +11,17 @@ defmodule URI do
|
||||
|
||||
import Bitwise
|
||||
|
||||
@ports %{
|
||||
"ftp" => 21,
|
||||
"http" => 80,
|
||||
"https" => 443,
|
||||
"ldap" => 389,
|
||||
"sftp" => 22,
|
||||
"tftp" => 69,
|
||||
}
|
||||
@schemes [
|
||||
"ftp",
|
||||
"http",
|
||||
"https",
|
||||
"ldap",
|
||||
"sftp",
|
||||
"tftp",
|
||||
]
|
||||
|
||||
Enum.each @ports, fn {scheme, port} ->
|
||||
Enum.each @schemes, fn scheme ->
|
||||
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
|
||||
def default_port(unquote(scheme)), do: unquote(port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -47,8 +46,7 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def default_port(scheme) when is_binary(scheme) do
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Map.get(dict, scheme)
|
||||
:elixir_config.get({:uri, scheme})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -59,8 +57,7 @@ defmodule URI do
|
||||
new URIs.
|
||||
"""
|
||||
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
|
||||
:elixir_config.put({:uri, scheme}, port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -98,7 +95,10 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode_query(q, dict \\ %{}) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
|
||||
case do_decode_query(q) do
|
||||
nil -> dict
|
||||
{{k, v}, q} -> decode_query(q, Dict.put(dict, k, v))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -107,19 +107,19 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map(&(&1))
|
||||
[{"foo", "1"}, {"bar", "2"}]
|
||||
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
Stream.unfold(q, &do_decoder/1)
|
||||
Stream.unfold(q, &do_decode_query/1)
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
defp do_decode_query("") do
|
||||
nil
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
defp do_decode_query(q) do
|
||||
{first, next} =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> {first, rest}
|
||||
@@ -234,7 +234,7 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode(uri) do
|
||||
unpercent(uri)
|
||||
unpercent(uri, "", false)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
@@ -250,23 +250,25 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode_www_form(str) do
|
||||
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
|
||||
unpercent(str, "", true)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect str}"
|
||||
end
|
||||
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
|
||||
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
|
||||
end
|
||||
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
|
||||
defp unpercent(<<?%>>), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail :: binary>>) do
|
||||
<<head>> <> unpercent(tail)
|
||||
defp unpercent(<<?+, tail::binary>>, acc, spaces = true) do
|
||||
unpercent(tail, <<acc::binary, ?\s>>, spaces)
|
||||
end
|
||||
|
||||
defp unpercent(<<>>), do: <<>>
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail::binary>>, acc, spaces) do
|
||||
unpercent(tail, <<acc::binary, bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>>, spaces)
|
||||
end
|
||||
defp unpercent(<<?%, _::binary>>, _acc, _spaces), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail::binary>>, acc, spaces) do
|
||||
unpercent(tail, <<acc::binary, head>>, spaces)
|
||||
end
|
||||
defp unpercent(<<>>, acc, _spaces), do: acc
|
||||
|
||||
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
@@ -274,12 +276,17 @@ defmodule URI do
|
||||
defp hex_to_dec(_n), do: throw(:malformed_uri)
|
||||
|
||||
@doc """
|
||||
Parses a URI into components.
|
||||
Parses a well-formed URI reference into its components.
|
||||
|
||||
URIs have portions that are handled specially for the particular
|
||||
scheme of the URI. For example, http and https have different
|
||||
default ports. Such values can be accessed and registered via
|
||||
`URI.default_port/1` and `URI.default_port/2`.
|
||||
Note this function expects a well-formed URI and does not perform
|
||||
any validation. See the examples section below of how `URI.parse/1`
|
||||
can be used to parse a wide range of relative URIs.
|
||||
|
||||
This function uses the parsing regular expression as defined
|
||||
in the Appendix B of RFC3986.
|
||||
|
||||
When a URI is given without a port, the values registered via
|
||||
`URI.default_port/1` and `URI.default_port/2` are used.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -288,12 +295,24 @@ defmodule URI do
|
||||
authority: "elixir-lang.org", userinfo: nil,
|
||||
host: "elixir-lang.org", port: 80}
|
||||
|
||||
iex> URI.parse("//elixir-lang.org/")
|
||||
%URI{authority: "elixir-lang.org", fragment: nil, host: "elixir-lang.org",
|
||||
path: "/", port: nil, query: nil, scheme: nil, userinfo: nil}
|
||||
|
||||
iex> URI.parse("/foo/bar")
|
||||
%URI{authority: nil, fragment: nil, host: nil, path: "/foo/bar",
|
||||
port: nil, query: nil, scheme: nil, userinfo: nil}
|
||||
|
||||
iex> URI.parse("foo/bar")
|
||||
%URI{authority: nil, fragment: nil, host: nil, path: "foo/bar",
|
||||
port: nil, query: nil, scheme: nil, userinfo: nil}
|
||||
|
||||
"""
|
||||
def parse(%URI{} = uri), do: uri
|
||||
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
regex = ~r/^(([a-z][a-z0-9\+\-\.]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i
|
||||
parts = nillify(Regex.run(regex, s))
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
@@ -309,7 +328,7 @@ defmodule URI do
|
||||
|
||||
scheme = normalize_scheme(scheme)
|
||||
|
||||
if nil?(port) and not nil?(scheme) do
|
||||
if is_nil(port) and not is_nil(scheme) do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
|
||||
@@ -51,6 +51,19 @@ defmodule Version do
|
||||
|
||||
"~> 2.0.0"
|
||||
|
||||
`~>` will never include pre-release versions of its upper bound.
|
||||
It can also be used to set an upper bound on only the major
|
||||
version part. See the table below for `~>` requirements and
|
||||
their corresponding translation.
|
||||
|
||||
`~>` | Translation
|
||||
:------------- | :---------------------
|
||||
`~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
|
||||
`~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
|
||||
`~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
|
||||
`~> 2.0` | `>= 2.0.0 and < 3.0.0`
|
||||
`~> 2.1` | `>= 2.1.0 and < 3.0.0`
|
||||
|
||||
"""
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
@@ -61,7 +74,7 @@ defmodule Version do
|
||||
@type major :: String.t | non_neg_integer
|
||||
@type minor :: non_neg_integer | nil
|
||||
@type patch :: non_neg_integer | nil
|
||||
@type pre :: [String.t]
|
||||
@type pre :: [String.t | non_neg_integer]
|
||||
@type build :: String.t | nil
|
||||
@type matchable :: {major :: major,
|
||||
minor :: minor,
|
||||
@@ -324,7 +337,7 @@ defmodule Version do
|
||||
patch = nillify(patch)
|
||||
pre = nillify(pre)
|
||||
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
if is_nil(minor) or (is_nil(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
major = String.to_integer(major)
|
||||
@@ -506,10 +519,22 @@ end
|
||||
|
||||
defimpl String.Chars, for: Version do
|
||||
def to_string(version) do
|
||||
pre = unless Enum.empty?(pre = version.pre), do: "-#{pre}"
|
||||
pre = pre(version.pre)
|
||||
build = if build = version.build, do: "+#{build}"
|
||||
"#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
|
||||
end
|
||||
|
||||
defp pre([]) do
|
||||
""
|
||||
end
|
||||
|
||||
defp pre(pre) do
|
||||
"-" <>
|
||||
Enum.map_join(pre, ".", fn
|
||||
int when is_integer(int) -> Integer.to_string(int)
|
||||
string when is_binary(string) -> string
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version do
|
||||
|
||||
@@ -29,9 +29,12 @@ start(_Type, _Args) ->
|
||||
end,
|
||||
|
||||
ok = io:setopts(standard_io, Opts),
|
||||
%% Must use undocument {unicode, true} to set unicode on standard_error, more
|
||||
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
|
||||
ok = io:setopts(standard_error, [{unicode,true}]),
|
||||
|
||||
ok = case io:setopts(standard_error, [{encoding,utf8}]) of
|
||||
ok -> ok;
|
||||
{error, _} -> io:setopts(standard_error, [{unicode,true}]) %% R17.3 and earlier
|
||||
end,
|
||||
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
@@ -42,10 +45,29 @@ start(_Type, _Args) ->
|
||||
ok
|
||||
end,
|
||||
|
||||
elixir_sup:start_link().
|
||||
URIs = [{<<"ftp">>, 21},
|
||||
{<<"sftp">>, 22},
|
||||
{<<"tftp">>, 69},
|
||||
{<<"http">>, 80},
|
||||
{<<"https">>, 443},
|
||||
{<<"ldap">>, 389}],
|
||||
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
|
||||
CompilerOpts = [{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
Config = [{at_exit, []},
|
||||
{compiler_options, orddict:from_list(CompilerOpts)}
|
||||
| URIConfig],
|
||||
Tab = elixir_config:new(Config),
|
||||
case elixir_sup:start_link() of
|
||||
{ok, Sup} ->
|
||||
{ok, Sup, Tab};
|
||||
{error, _Reason} = Error ->
|
||||
elixir_config:delete(Tab),
|
||||
Error
|
||||
end.
|
||||
|
||||
stop(Tab) ->
|
||||
elixir_config:delete(Tab).
|
||||
|
||||
stop(_S) ->
|
||||
ok.
|
||||
|
||||
config_change(_Changed, _New, _Remove) ->
|
||||
ok.
|
||||
|
||||
@@ -41,28 +41,29 @@
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
%% Numbers
|
||||
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
|
||||
-define(is_bin(S), S >= $0 andalso S =< $1).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
|
||||
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
|
||||
-define(is_bin(S), (S >= $0 andalso S =< $1)).
|
||||
-define(is_octal(S), (S >= $0 andalso S =< $7)).
|
||||
-define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
|
||||
|
||||
%% Digits and letters
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
-define(is_upcase(S), S >= $A andalso S =< $Z).
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
-define(is_digit(S), (S >= $0 andalso S =< $9)).
|
||||
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
|
||||
-define(is_downcase(S), (S >= $a andalso S =< $z)).
|
||||
|
||||
%% Atoms
|
||||
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
-define(is_atom_start(S), (?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_atom(S), (?is_identifier(S) orelse (S == $@))).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
|
||||
-define(is_identifier(S), (?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_sigil(S), ((S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
-define(is_quote(S), (S == $" orelse S == $')).
|
||||
|
||||
%% Spaces
|
||||
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
|
||||
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
|
||||
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
|
||||
-define(is_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
|
||||
-define(is_vertical_space(S), ((S == $\r) orelse (S == $\n))).
|
||||
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
|
||||
-define(is_invalid_space(S), (S == 16#A0)).
|
||||
@@ -44,7 +44,7 @@ define({Line,E}, Kind, Call, Expr) ->
|
||||
unless_loaded(Fun, Args, Callback) ->
|
||||
case code:is_loaded(?kernel) of
|
||||
{_, _} -> apply(?kernel, Fun, Args);
|
||||
false -> Callback()
|
||||
false -> Callback()
|
||||
end.
|
||||
|
||||
env() ->
|
||||
|
||||
@@ -6,14 +6,11 @@
|
||||
|
||||
-define(timeout, 30000).
|
||||
-record(elixir_code_server, {
|
||||
compilation_status=[],
|
||||
argv=[],
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool={[],0},
|
||||
paths={[],[]},
|
||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
erl_compiler_options=nil
|
||||
mod_pool={[],0},
|
||||
mod_ets=dict:new(),
|
||||
compilation_status=[]
|
||||
}).
|
||||
|
||||
call(Args) ->
|
||||
@@ -29,14 +26,21 @@ start_link() ->
|
||||
|
||||
init(ok) ->
|
||||
%% We attempt to load those modules here so throughout
|
||||
%% the codebase we can use a code:is_loaded/1 check on
|
||||
%% bootstrap instead of using the more expensive
|
||||
%% code:ensure_loaded/1 multiple times.
|
||||
%% the codebase we can avoid code:is_loaded/1 checks.
|
||||
_ = code:ensure_loaded('Elixir.Macro.Env'),
|
||||
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
|
||||
%% The table where we store module definitions
|
||||
_ = ets:new(elixir_modules, [set, protected, named_table, {read_concurrency, true}]),
|
||||
|
||||
{ok, #elixir_code_server{}}.
|
||||
|
||||
handle_call({defmodule, Pid, Tuple}, _From, Config) ->
|
||||
{Ref, New} = defmodule(Pid, Tuple, Config),
|
||||
{reply, Ref, New};
|
||||
|
||||
handle_call({undefmodule, Ref}, _From, Config) ->
|
||||
{reply, ok, undefmodule(Ref, Config)};
|
||||
|
||||
handle_call({acquire, Path}, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
@@ -53,18 +57,6 @@ handle_call({acquire, Path}, From, Config) ->
|
||||
handle_call(loaded, _From, Config) ->
|
||||
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
|
||||
|
||||
handle_call(at_exit, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.at_exit, Config};
|
||||
|
||||
handle_call(flush_at_exit, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
|
||||
|
||||
handle_call(argv, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.argv, Config};
|
||||
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.compiler_options, Config};
|
||||
|
||||
handle_call({compilation_status, CompilerPid}, _From, Config) ->
|
||||
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
|
||||
@@ -73,20 +65,11 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
|
||||
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
case Config#elixir_code_server.mod_pool of
|
||||
{[H|T], Counter} ->
|
||||
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
|
||||
{reply, module_tuple(H), Config#elixir_code_server{mod_pool={T,Counter}}};
|
||||
{[], Counter} ->
|
||||
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
|
||||
end;
|
||||
|
||||
handle_call(erl_compiler_options, _From, Config) ->
|
||||
case Config#elixir_code_server.erl_compiler_options of
|
||||
nil ->
|
||||
Opts = erl_compiler_options(),
|
||||
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
|
||||
Opts ->
|
||||
{reply, Opts, Config}
|
||||
{reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
|
||||
end;
|
||||
|
||||
handle_call(paths, _From, Config) ->
|
||||
@@ -95,20 +78,11 @@ handle_call(paths, _From, Config) ->
|
||||
handle_call(Request, _From, Config) ->
|
||||
{stop, {badcall, Request}, Config}.
|
||||
|
||||
handle_cast({at_exit, AtExit}, Config) ->
|
||||
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
|
||||
|
||||
handle_cast({argv, Argv}, Config) ->
|
||||
{noreply, Config#elixir_code_server{argv=Argv}};
|
||||
|
||||
handle_cast({compiler_options, Options}, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{noreply, Config#elixir_code_server{compiler_options=Final}};
|
||||
|
||||
handle_cast({register_warning, CompilerPid}, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
|
||||
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
|
||||
CompilerOptions = elixir_config:get(compiler_options),
|
||||
case orddict:find(warnings_as_errors, CompilerOptions) of
|
||||
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
|
||||
_ -> {noreply, Config}
|
||||
end;
|
||||
@@ -137,8 +111,8 @@ handle_cast({unload_files, Files}, Config) ->
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
{noreply, Config#elixir_code_server{loaded=Unloaded}};
|
||||
|
||||
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
|
||||
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
|
||||
{noreply, Config#elixir_code_server{mod_pool={[H|T],Counter}}};
|
||||
|
||||
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
|
||||
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
|
||||
@@ -146,7 +120,10 @@ handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
|
||||
handle_cast(Request, Config) ->
|
||||
{stop, {badcast, Request}, Config}.
|
||||
|
||||
handle_info(_Request, Config) ->
|
||||
handle_info({'DOWN', Ref, process, _Pid, _Reason}, Config) ->
|
||||
{noreply, undefmodule(Ref, Config)};
|
||||
|
||||
handle_info(_Msg, Config) ->
|
||||
{noreply, Config}.
|
||||
|
||||
terminate(_Reason, _Config) ->
|
||||
@@ -158,22 +135,17 @@ code_change(_Old, Config, _Extra) ->
|
||||
module_tuple(I) ->
|
||||
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
|
||||
|
||||
erl_compiler_options() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
case os:getenv(Key) of
|
||||
false -> [];
|
||||
Str when is_list(Str) ->
|
||||
case erl_scan:string(Str) of
|
||||
{ok,Tokens,_} ->
|
||||
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
|
||||
ets:insert(elixir_modules, Tuple),
|
||||
Ref = erlang:monitor(process, Pid),
|
||||
Mod = erlang:element(1, Tuple),
|
||||
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
|
||||
|
||||
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
|
||||
case dict:find(Ref, ModEts) of
|
||||
{ok, Mod} ->
|
||||
ets:delete(elixir_modules, Mod),
|
||||
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
|
||||
error ->
|
||||
Config
|
||||
end.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file/2, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
|
||||
get_opt(Key) ->
|
||||
Dict = elixir_code_server:call(compiler_options),
|
||||
Dict = elixir_config:get(compiler_options),
|
||||
case lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{Key, Value} -> Value
|
||||
@@ -42,12 +42,15 @@ file(Relative) when is_binary(Relative) ->
|
||||
file(Relative, Dest) ->
|
||||
File = filename:absname(Relative),
|
||||
{ok, Bin} = file:read_file(File),
|
||||
string(elixir_utils:characters_to_list(Bin), File, Dest).
|
||||
string(elixir_utils:characters_to_list(Bin), File, case Dest of
|
||||
nil -> Dest;
|
||||
_ -> filename:absname(Dest)
|
||||
end).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
Dest = filename:absname(Path),
|
||||
file_to_path(File, Dest) when is_binary(File), is_binary(Dest) ->
|
||||
Comp = file(File, Dest),
|
||||
_ = [binary_to_path(X, Dest) || X <- Comp],
|
||||
Abs = filename:absname(Dest),
|
||||
_ = [binary_to_path(X, Abs) || X <- Comp],
|
||||
Comp.
|
||||
|
||||
%% Evaluation
|
||||
@@ -75,7 +78,7 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
|
||||
%% Pass {native, false} to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
AllOpts = elixir_code_server:call(erl_compiler_options),
|
||||
AllOpts = options(),
|
||||
FinalOpts = AllOpts -- [native, warn_missing_spec],
|
||||
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
@@ -89,6 +92,36 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
|
||||
end).
|
||||
|
||||
options() ->
|
||||
case elixir_config:get(erl_compiler_options) of
|
||||
nil ->
|
||||
elixir_config:update(erl_compiler_options, fun options/1);
|
||||
Opts ->
|
||||
Opts
|
||||
end.
|
||||
|
||||
options(nil) ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
case os:getenv(Key) of
|
||||
false -> [];
|
||||
Str when is_list(Str) ->
|
||||
case erl_scan:string(Str) of
|
||||
{ok,Tokens,_} ->
|
||||
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
end;
|
||||
options(Opts) ->
|
||||
Opts.
|
||||
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
|
||||
Res = Module:Fun(Args),
|
||||
code:delete(Module),
|
||||
@@ -139,8 +172,8 @@ module(Forms, Opts, E, Callback) ->
|
||||
Final =
|
||||
case (get_opt(debug_info) == true) orelse
|
||||
lists:member(debug_info, Opts) of
|
||||
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
|
||||
false -> elixir_code_server:call(erl_compiler_options)
|
||||
true -> [debug_info] ++ options();
|
||||
false -> options()
|
||||
end,
|
||||
inner_module(Forms, Final, false, E, Callback).
|
||||
|
||||
@@ -174,7 +207,10 @@ no_auto_import() ->
|
||||
|
||||
core() ->
|
||||
{ok, _} = application:ensure_all_started(elixir),
|
||||
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
|
||||
New = orddict:from_list([{docs,false},{internal,true}]),
|
||||
Merge = fun(_, _, Value) -> Value end,
|
||||
Update = fun(Old) -> orddict:merge(Merge, Old, New) end,
|
||||
_ = elixir_config:update(compiler_options, Update),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
core_file(File) ->
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
-module(elixir_config).
|
||||
-compile({no_auto_import, [get/1]}).
|
||||
-export([new/1, delete/1, put/2, get/1, update/2, get_and_put/2]).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, code_change/3, terminate/2]).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% public api
|
||||
|
||||
new(Opts) ->
|
||||
Tab = ets:new(?MODULE, [named_table, public, {read_concurrency, true}]),
|
||||
true = ets:insert_new(?MODULE, Opts),
|
||||
Tab.
|
||||
|
||||
delete(?MODULE) ->
|
||||
ets:delete(?MODULE).
|
||||
|
||||
put(Key, Value) ->
|
||||
gen_server:call(?MODULE, {put, Key, Value}).
|
||||
|
||||
get(Key) ->
|
||||
case ets:lookup(?MODULE, Key) of
|
||||
[{_, Value}] -> Value;
|
||||
[] -> nil
|
||||
end.
|
||||
|
||||
update(Key, Fun) ->
|
||||
gen_server:call(?MODULE, {update, Key, Fun}).
|
||||
|
||||
get_and_put(Key, Value) ->
|
||||
gen_server:call(?MODULE, {get_and_put, Key, Value}).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, ?MODULE, []).
|
||||
|
||||
%% gen_server api
|
||||
|
||||
init(Tab) ->
|
||||
%% Ets table must be writable
|
||||
public = ets:info(Tab, protection),
|
||||
{ok, Tab}.
|
||||
|
||||
handle_call({put, Key, Value}, _From, Tab) ->
|
||||
ets:insert(Tab, {Key, Value}),
|
||||
{reply, ok, Tab};
|
||||
handle_call({update, Key, Fun}, _From, Tab) ->
|
||||
Value = Fun(get(Key)),
|
||||
ets:insert(Tab, {Key, Value}),
|
||||
{reply, Value, Tab};
|
||||
handle_call({get_and_put, Key, Value}, _From, Tab) ->
|
||||
OldValue = get(Key),
|
||||
ets:insert(Tab, {Key, Value}),
|
||||
{reply, OldValue, Tab}.
|
||||
|
||||
handle_cast(Cast, Tab) ->
|
||||
{stop, {bad_cast, Cast}, Tab}.
|
||||
|
||||
handle_info(_Msg, Tab) ->
|
||||
{noreply, Tab}.
|
||||
|
||||
code_change(_OldVsn, Tab, _Extra) ->
|
||||
{ok, Tab}.
|
||||
|
||||
terminate(_Reason, _Tab) ->
|
||||
ok.
|
||||
@@ -3,8 +3,8 @@
|
||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 1000). %% 1 second
|
||||
-define(limit, 4294967296). %% 2^32
|
||||
-define(timeout, 30000). %% 30 seconds
|
||||
-define(limit, 4294967295). %% 2^32 - 1
|
||||
|
||||
next() ->
|
||||
gen_server:call(?MODULE, next, ?timeout).
|
||||
|
||||
@@ -1,49 +1,35 @@
|
||||
% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
|
||||
-module(elixir_def).
|
||||
-export([table/1, clauses_table/1, setup/1,
|
||||
cleanup/1, reset_last/1, lookup_definition/2,
|
||||
-export([setup/1, reset_last/1, lookup_definition/2,
|
||||
delete_definition/2, store_definition/6, unwrap_definitions/2,
|
||||
store_each/8, format_error/1]).
|
||||
store_each/7, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(attr, '__def_table').
|
||||
-define(clauses_attr, '__clauses_table').
|
||||
-define(last_def, {elixir, last_def}).
|
||||
-define(attr, {elixir, def_table}).
|
||||
-define(clauses_attr, {elixir, clauses_table}).
|
||||
|
||||
%% Table management functions. Called internally.
|
||||
|
||||
table(Module) ->
|
||||
ets:lookup_element(Module, ?attr, 2).
|
||||
|
||||
clauses_table(Module) ->
|
||||
ets:lookup_element(Module, ?clauses_attr, 2).
|
||||
|
||||
setup(Module) ->
|
||||
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
|
||||
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
|
||||
reset_last(Module),
|
||||
ok.
|
||||
|
||||
cleanup(Module) ->
|
||||
ets:delete(table(Module)),
|
||||
ets:delete(clauses_table(Module)).
|
||||
|
||||
%% Reset the last item. Useful when evaling code.
|
||||
reset_last(Module) ->
|
||||
ets:insert(table(Module), {last, []}).
|
||||
ets:insert(elixir_module:data_table(Module), {?last_def, []}).
|
||||
|
||||
%% Looks up a definition from the database.
|
||||
lookup_definition(Module, Tuple) ->
|
||||
case ets:lookup(table(Module), Tuple) of
|
||||
case ets:lookup(elixir_module:defs_table(Module), Tuple) of
|
||||
[Result] ->
|
||||
CTable = clauses_table(Module),
|
||||
CTable = elixir_module:clas_table(Module),
|
||||
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
delete_definition(Module, Tuple) ->
|
||||
ets:delete(table(Module), Tuple),
|
||||
ets:delete(clauses_table(Module), Tuple).
|
||||
ets:delete(elixir_module:defs_table(Module), Tuple),
|
||||
ets:delete(elixir_module:clas_table(Module), Tuple).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
@@ -82,6 +68,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
LinifyBody = elixir_quote:linify(Line, Key, Body),
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, E),
|
||||
assert_valid_name(Line, Kind, Name, Args, E),
|
||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
|
||||
|
||||
@@ -97,16 +84,12 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocatio
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = ?m(E, file),
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
File = ?m(E, file),
|
||||
compile_super(Module, Super, E),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
|
||||
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
|
||||
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
|
||||
[store_each(false, Kind, File, Location, Module, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
make_struct_available(Kind, Module, Name, Args),
|
||||
@@ -165,7 +148,7 @@ normalize_location(File) ->
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, true, #{function := Function}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
elixir_def_overridable:super(Module, Function);
|
||||
compile_super(_Module, _, _E) -> ok.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
@@ -175,7 +158,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
|
||||
Arity = length(Args),
|
||||
|
||||
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
|
||||
|
||||
@@ -228,9 +211,8 @@ is_macro(_) -> false.
|
||||
% It returns a list of all functions to be exported, plus the macros,
|
||||
% and the body of all functions.
|
||||
unwrap_definitions(File, Module) ->
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
ets:delete(Table, last),
|
||||
Table = elixir_module:defs_table(Module),
|
||||
CTable = elixir_module:clas_table(Module),
|
||||
|
||||
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
|
||||
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
|
||||
@@ -326,26 +308,33 @@ warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
|
||||
%% This function also checks and emit warnings in case
|
||||
%% the kind, of the visibility of the function changes.
|
||||
|
||||
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
Tuple = {Name, Arity},
|
||||
store_each(Check, Kind, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
Data = elixir_module:data_table(Module),
|
||||
Defs = elixir_module:defs_table(Module),
|
||||
Clas = elixir_module:clas_table(Module),
|
||||
|
||||
Tuple = {Name, Arity},
|
||||
HasBody = Clauses =/= [],
|
||||
case ets:lookup(Table, Tuple) of
|
||||
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
|
||||
|
||||
case ets:lookup(Defs, Tuple) of
|
||||
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
|
||||
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
|
||||
FinalLine = StoredLine,
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
(Check and StoredCheck) andalso
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile),
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
|
||||
[] ->
|
||||
FinalLine = Line,
|
||||
FinalLocation = Location,
|
||||
FinalDefaults = {Defaults, HasBody, Defaults}
|
||||
end,
|
||||
Check andalso ets:insert(Table, {last, {Name, Arity}}),
|
||||
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
|
||||
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
|
||||
|
||||
Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
|
||||
ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
|
||||
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
|
||||
|
||||
%% Validations
|
||||
|
||||
@@ -354,8 +343,8 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE,
|
||||
{changed_kind, {Name, Arity, StoredKind, Kind}}).
|
||||
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
|
||||
case ets:lookup_element(Data, ?last_def, 2) of
|
||||
{Name,Arity} -> [];
|
||||
[] -> [];
|
||||
_ ->
|
||||
@@ -378,8 +367,8 @@ check_valid_defaults(Line, File, Name, Arity, Kind, _, _, _) ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE,
|
||||
{clauses_with_defaults, {Kind, Name, Arity}}).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
|
||||
check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
|
||||
[ begin
|
||||
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
|
||||
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
|
||||
@@ -404,8 +393,12 @@ invalid_arg(_) ->
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
|
||||
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
assert_valid_name(Line, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {is_record, Kind});
|
||||
assert_valid_name(_Line, _Kind, _Name, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
%% Format errors
|
||||
@@ -447,5 +440,9 @@ format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
format_error(invalid_args_for_bodyless_clause) ->
|
||||
"can use only variables and \\\\ as arguments of bodyless clause";
|
||||
|
||||
format_error({is_record, Kind}) ->
|
||||
io_lib:format("cannot define function named ~ts is_record/2 due to compability "
|
||||
"issues with the Erlang compiler (it is a known bug)", [Kind]);
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
expand(Args, E) ->
|
||||
NoContext = E#{context := nil},
|
||||
lists:mapfoldl(fun
|
||||
({'\\\\', Meta, [Left, Right]}, Acc) ->
|
||||
{ELeft, EL} = elixir_exp:expand(Left, Acc),
|
||||
{ERight, _} = elixir_exp:expand(Right, Acc#{context := nil}),
|
||||
{ERight, _} = elixir_exp:expand(Right, NoContext),
|
||||
{{'\\\\', Meta, [ELeft, ERight]}, EL};
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, ensure_defined/4,
|
||||
name/2, store/3, format_error/1]).
|
||||
-export([setup/1, overridable/1, overridable/2, name/2, super/2, store_pending/1,
|
||||
ensure_defined/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-define(attr, {elixir, overridable}).
|
||||
|
||||
setup(Module) ->
|
||||
overridable(Module, []).
|
||||
|
||||
overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
|
||||
ets:lookup_element(elixir_module:data_table(Module), ?attr, 2).
|
||||
|
||||
overridable(Module, Value) ->
|
||||
ets:insert(elixir_module:data_table(Module), {'__overridable', Value}).
|
||||
ets:insert(elixir_module:data_table(Module), {?attr, Value}).
|
||||
|
||||
%% Check if an overridable function is defined.
|
||||
|
||||
@@ -43,19 +47,19 @@ store(Module, Function, GenerateName) ->
|
||||
false -> {Kind, Name}
|
||||
end,
|
||||
|
||||
case code:is_loaded('Elixir.Module.LocalsTracker') of
|
||||
{_, _} ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
false ->
|
||||
'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
|
||||
_ ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
|
||||
Def = {function, Line, FinalName, Arity, Clauses},
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
|
||||
elixir_def:store_each(false, FinalKind, File, Location, Module, Defaults, Def)
|
||||
end.
|
||||
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
super(Module, Function) ->
|
||||
store(Module, Function, true).
|
||||
|
||||
store_pending(Module) ->
|
||||
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
|
||||
@@ -73,4 +77,4 @@ format_error({no_super, Module, {Name, Arity}}) ->
|
||||
format_fa({Name, Arity}) ->
|
||||
A = atom_to_binary(Name, utf8),
|
||||
B = integer_to_binary(Arity),
|
||||
<< A/binary, $/, B/binary >>.
|
||||
<<A/binary, $/, B/binary>>.
|
||||
|
||||
@@ -9,20 +9,7 @@
|
||||
find_import/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-import(ordsets, [is_element/2]).
|
||||
|
||||
-define(atom, 'Elixir.Atom').
|
||||
-define(float, 'Elixir.Float').
|
||||
-define(io, 'Elixir.IO').
|
||||
-define(integer, 'Elixir.Integer').
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(list, 'Elixir.List').
|
||||
-define(map, 'Elixir.Map').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(port, 'Elixir.Port').
|
||||
-define(process, 'Elixir.Process').
|
||||
-define(string, 'Elixir.String').
|
||||
-define(system, 'Elixir.System').
|
||||
-define(tuple, 'Elixir.Tuple').
|
||||
|
||||
default_functions() ->
|
||||
[{?kernel, elixir_imported_functions()}].
|
||||
@@ -82,7 +69,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
case inline(Receiver, Name, Arity) of
|
||||
case elixir_rewrite:inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {remote, AR, AN, Arity};
|
||||
false -> {remote, Receiver, Name, Arity}
|
||||
end.
|
||||
@@ -103,9 +90,9 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
|
||||
Arity = length(Args),
|
||||
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, AR, AN, AA} ->
|
||||
Callback(AR, AN, AA);
|
||||
case elixir_rewrite:inline(Receiver, Name, Arity) of
|
||||
{AR, AN} ->
|
||||
Callback(AR, AN, Args);
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, {Name, Arity}, Args, E) of
|
||||
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
@@ -151,11 +138,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
{ok, Receiver, Name, Args};
|
||||
{macro, Receiver} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
@@ -167,8 +150,8 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
error -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case rewrite(Module, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
case elixir_rewrite:inline(Module, Name, Arity) of
|
||||
{AR, AN} -> {ok, AR, AN, Args};
|
||||
false -> error
|
||||
end;
|
||||
false ->
|
||||
@@ -232,7 +215,7 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
||||
end.
|
||||
|
||||
caller(Line, #{module := nil} = E) ->
|
||||
{elixir_compiler, '__FILE__', 2, location(Line, E)};
|
||||
{elixir_compiler_0, '__FILE__', 1, location(Line, E)};
|
||||
caller(Line, #{module := Module, function := nil} = E) ->
|
||||
{Module, '__MODULE__', 0, location(Line, E)};
|
||||
caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
|
||||
@@ -342,163 +325,6 @@ elixir_imported_macros() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
rewrite(?atom, to_string, [Arg], _) ->
|
||||
{ok, erlang, atom_to_binary, [Arg, utf8]};
|
||||
rewrite(?kernel, elem, [Tuple, Index], _) ->
|
||||
{ok, erlang, element, [increment(Index), Tuple]};
|
||||
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
|
||||
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
|
||||
rewrite(?map, 'has_key?', [Map, Key], _) ->
|
||||
{ok, maps, is_key, [Key, Map]};
|
||||
rewrite(?map, fetch, [Map, Key], _) ->
|
||||
{ok, maps, find, [Key, Map]};
|
||||
rewrite(?map, put, [Map, Key, Value], _) ->
|
||||
{ok, maps, put, [Key, Value, Map]};
|
||||
rewrite(?map, delete, [Map, Key], _) ->
|
||||
{ok, maps, remove, [Key, Map]};
|
||||
rewrite(?process, monitor, [Arg], _) ->
|
||||
{ok, erlang, monitor, [process, Arg]};
|
||||
rewrite(?string, to_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_atom, [Arg, utf8]};
|
||||
rewrite(?string, to_existing_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
|
||||
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
|
||||
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
|
||||
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
|
||||
{ok, erlang, delete_element, [increment(Index), Tuple]};
|
||||
rewrite(?tuple, duplicate, [Data, Size], _) ->
|
||||
{ok, erlang, make_tuple, [Size, Data]};
|
||||
|
||||
rewrite(Receiver, Name, Args, Arity) ->
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {ok, AR, AN, Args};
|
||||
false -> false
|
||||
end.
|
||||
|
||||
increment(Number) when is_number(Number) ->
|
||||
Number + 1;
|
||||
increment(Other) ->
|
||||
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
|
||||
|
||||
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
|
||||
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
|
||||
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
|
||||
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
|
||||
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
|
||||
inline(?float, to_string, 1) -> {erlang, float_to_binary};
|
||||
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
|
||||
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
|
||||
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
|
||||
inline(?list, to_float, 1) -> {erlang, list_to_float};
|
||||
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
|
||||
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
|
||||
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
|
||||
|
||||
inline(?kernel, '+', 2) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 2) -> {erlang, '-'};
|
||||
inline(?kernel, '+', 1) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 1) -> {erlang, '-'};
|
||||
inline(?kernel, '*', 2) -> {erlang, '*'};
|
||||
inline(?kernel, '/', 2) -> {erlang, '/'};
|
||||
inline(?kernel, '++', 2) -> {erlang, '++'};
|
||||
inline(?kernel, '--', 2) -> {erlang, '--'};
|
||||
inline(?kernel, 'not', 1) -> {erlang, 'not'};
|
||||
inline(?kernel, '<', 2) -> {erlang, '<'};
|
||||
inline(?kernel, '>', 2) -> {erlang, '>'};
|
||||
inline(?kernel, '<=', 2) -> {erlang, '=<'};
|
||||
inline(?kernel, '>=', 2) -> {erlang, '>='};
|
||||
inline(?kernel, '==', 2) -> {erlang, '=='};
|
||||
inline(?kernel, '!=', 2) -> {erlang, '/='};
|
||||
inline(?kernel, '===', 2) -> {erlang, '=:='};
|
||||
inline(?kernel, '!==', 2) -> {erlang, '=/='};
|
||||
inline(?kernel, abs, 1) -> {erlang, abs};
|
||||
inline(?kernel, apply, 2) -> {erlang, apply};
|
||||
inline(?kernel, apply, 3) -> {erlang, apply};
|
||||
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
|
||||
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
|
||||
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
|
||||
inline(?kernel, 'div', 2) -> {erlang, 'div'};
|
||||
inline(?kernel, exit, 1) -> {erlang, exit};
|
||||
inline(?kernel, hd, 1) -> {erlang, hd};
|
||||
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
|
||||
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
|
||||
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
|
||||
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
|
||||
inline(?kernel, is_float, 1) -> {erlang, is_float};
|
||||
inline(?kernel, is_function, 1) -> {erlang, is_function};
|
||||
inline(?kernel, is_function, 2) -> {erlang, is_function};
|
||||
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
|
||||
inline(?kernel, is_list, 1) -> {erlang, is_list};
|
||||
inline(?kernel, is_map, 1) -> {erlang, is_map};
|
||||
inline(?kernel, is_number, 1) -> {erlang, is_number};
|
||||
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
|
||||
inline(?kernel, is_port, 1) -> {erlang, is_port};
|
||||
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
|
||||
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
|
||||
inline(?kernel, length, 1) -> {erlang, length};
|
||||
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
|
||||
inline(?kernel, map_size, 1) -> {erlang, map_size};
|
||||
inline(?kernel, max, 2) -> {erlang, max};
|
||||
inline(?kernel, min, 2) -> {erlang, min};
|
||||
inline(?kernel, node, 0) -> {erlang, node};
|
||||
inline(?kernel, node, 1) -> {erlang, node};
|
||||
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
|
||||
inline(?kernel, round, 1) -> {erlang, round};
|
||||
inline(?kernel, self, 0) -> {erlang, self};
|
||||
inline(?kernel, send, 2) -> {erlang, send};
|
||||
inline(?kernel, spawn, 1) -> {erlang, spawn};
|
||||
inline(?kernel, spawn, 3) -> {erlang, spawn};
|
||||
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
|
||||
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
|
||||
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
|
||||
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
|
||||
inline(?kernel, throw, 1) -> {erlang, throw};
|
||||
inline(?kernel, tl, 1) -> {erlang, tl};
|
||||
inline(?kernel, trunc, 1) -> {erlang, trunc};
|
||||
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
|
||||
|
||||
inline(?map, keys, 1) -> {maps, keys};
|
||||
inline(?map, merge, 2) -> {maps, merge};
|
||||
inline(?map, size, 1) -> {maps, size};
|
||||
inline(?map, values, 1) -> {maps, values};
|
||||
inline(?map, to_list, 1) -> {maps, to_list};
|
||||
|
||||
inline(?node, spawn, 2) -> {erlang, spawn};
|
||||
inline(?node, spawn, 3) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn, 4) -> {erlang, spawn};
|
||||
inline(?node, spawn, 5) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
|
||||
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
|
||||
|
||||
inline(?process, exit, 2) -> {erlang, exit};
|
||||
inline(?process, spawn, 2) -> {erlang, spawn_opt};
|
||||
inline(?process, spawn, 4) -> {erlang, spawn_opt};
|
||||
inline(?process, demonitor, 1) -> {erlang, demonitor};
|
||||
inline(?process, demonitor, 2) -> {erlang, demonitor};
|
||||
inline(?process, link, 1) -> {erlang, link};
|
||||
inline(?process, unlink, 1) -> {erlang, unlink};
|
||||
|
||||
inline(?port, open, 2) -> {erlang, open_port};
|
||||
inline(?port, call, 3) -> {erlang, port_call};
|
||||
inline(?port, close, 1) -> {erlang, port_close};
|
||||
inline(?port, command, 2) -> {erlang, port_command};
|
||||
inline(?port, command, 3) -> {erlang, port_command};
|
||||
inline(?port, connect, 2) -> {erlang, port_connect};
|
||||
inline(?port, control, 3) -> {erlang, port_control};
|
||||
inline(?port, info, 1) -> {erlang, port_info};
|
||||
inline(?port, info, 2) -> {erlang, port_info};
|
||||
inline(?port, list, 0) -> {erlang, ports};
|
||||
|
||||
inline(?string, to_float, 1) -> {erlang, binary_to_float};
|
||||
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
|
||||
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
|
||||
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
|
||||
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
|
||||
case deprecation(Receiver, Name, Arity) of
|
||||
false -> ok;
|
||||
@@ -522,7 +348,5 @@ deprecation_message(Warning, Message) ->
|
||||
Message -> Warning ++ ", " ++ Message
|
||||
end.
|
||||
|
||||
deprecation('Elixir.Module', 'function', 3) ->
|
||||
"use :erlang.make_fun/3";
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-spec warn(non_neg_integer(), binary(), iolist()) -> ok.
|
||||
-spec warn(non_neg_integer(), binary(), unicode:chardata()) -> ok.
|
||||
|
||||
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
|
||||
warn(file_format(Line, File), Warning).
|
||||
|
||||
-spec warn(iolist(), iolist()) -> ok.
|
||||
-spec warn(unicode:chardata(), unicode:chardata()) -> ok.
|
||||
|
||||
warn(Caller, Warning) ->
|
||||
do_warn([Caller, "warning: ", Warning, $\n]).
|
||||
@@ -31,8 +31,8 @@ form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
|
||||
|
||||
%% Compilation error.
|
||||
|
||||
-spec compile_error(list(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
|
||||
-spec compile_error(list(), binary(), unicode:charlist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), string(), list()) -> no_return().
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
@@ -55,7 +55,23 @@ parse_error(Line, File, Error, <<>>) ->
|
||||
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
|
||||
%% Produce a human-readable message for errors before a sigil
|
||||
parse_error(Line, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) ->
|
||||
{ok, Tokens, _} = erl_scan:string(binary_to_list(Full)),
|
||||
{ok, {sigil, _, Sigil, [Content], _}} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
|
||||
Message = <<"syntax error before: sigil ~", Sigil," with content '", Content/binary, "'">>,
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', Message);
|
||||
|
||||
%% Aliases are wrapped in ['']
|
||||
parse_error(Line, File, Error, <<"['", Token/binary>>) when is_binary(Error) ->
|
||||
Rest =
|
||||
case binary:split(Token, <<"'">>) of
|
||||
[Part, _] -> Part;
|
||||
_ -> <<>>
|
||||
end,
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary>>);
|
||||
|
||||
%% Binaries (and interpolation) are wrapped in [<<...>>]
|
||||
parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
|
||||
Rest =
|
||||
case binary:split(Full, <<"<<">>) of
|
||||
@@ -127,10 +143,13 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||
ok;
|
||||
|
||||
%% Ignore shadowed vars as we guarantee no conflicts ourselves
|
||||
%% Ignore shadowed and exported vars as we guarantee no conflicts ourselves
|
||||
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
||||
ok;
|
||||
|
||||
handle_file_warning(_, _File, {_Line,erl_lint,{exported_var,_Var,_Where}}) ->
|
||||
ok;
|
||||
|
||||
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
|
||||
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
|
||||
warn(Line, File, "this check/guard will always yield the same result");
|
||||
@@ -140,6 +159,11 @@ handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, {unused_var, format_var(Var)}),
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Handle literal eval failures
|
||||
handle_file_warning(_, File, {Line,sys_core_fold,{eval_failure, Error}}) ->
|
||||
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
|
||||
warn(Line, File, ["this expression will fail with ", elixir_aliases:inspect(Struct)]);
|
||||
|
||||
%% Default behaviour
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
@@ -241,4 +265,4 @@ do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_b
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
@@ -66,6 +66,8 @@ expand({'__aliases__', _, _} = Alias, E) ->
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
{Receiver, EA};
|
||||
false ->
|
||||
elixir_errors:warn(?m(E, line), ?m(E, file), io_lib:format("an alias must expand to an atom "
|
||||
"at compilation time but got ~ts", ['Elixir.Macro':to_string(hd(EAliases))])),
|
||||
{{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA}
|
||||
end
|
||||
end;
|
||||
@@ -472,7 +474,8 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
|
||||
true -> ok
|
||||
end,
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{{'.', DotMeta, [Receiver, Right]}, Meta, EArgs}, elixir_env:mergev(EL, EA)}.
|
||||
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs),
|
||||
elixir_env:mergev(EL, EA)}.
|
||||
|
||||
%% Lexical helpers
|
||||
|
||||
@@ -487,8 +490,8 @@ validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
|
||||
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
|
||||
end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
|
||||
|
||||
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
validate_opts(Meta, Kind, _Allowed, _Opts, E) ->
|
||||
compile_error(Meta, ?m(E, file), "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
no_alias_opts(KV) when is_list(KV) ->
|
||||
case lists:keyfind(as, 1, KV) of
|
||||
|
||||
@@ -101,7 +101,9 @@ do_receive(Meta, {Key, _}, _Acc, E) ->
|
||||
%% Try
|
||||
|
||||
'try'(Meta, [], E) ->
|
||||
compile_error(Meta, ?m(E, file), "missing do keywords in try");
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in try");
|
||||
'try'(Meta, [{do, _}], E) ->
|
||||
compile_error(Meta, ?m(E, file), "missing catch/rescue/after/else keyword in try");
|
||||
'try'(Meta, KV, E) when not is_list(KV) ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file), "invalid arguments for try");
|
||||
'try'(Meta, KV, E) ->
|
||||
|
||||
@@ -126,29 +126,6 @@ collect_filters([H|T], Acc) ->
|
||||
collect_filters([], Acc) ->
|
||||
{Acc, []}.
|
||||
|
||||
%% If all we have is one enum generator, we check if it is a list
|
||||
%% for optimization otherwise fallback to the reduce generator.
|
||||
build_inline(Line, [{enum, Meta, Left, Right, Filters}] = Orig, Expr, Into, Var, Acc, S) ->
|
||||
case Right of
|
||||
{cons, _, _, _} ->
|
||||
build_comprehension(Line, Orig, Expr, Into);
|
||||
{Other, _, _} when Other == tuple; Other == map ->
|
||||
build_reduce(Orig, Expr, Into, Acc, S);
|
||||
_ ->
|
||||
Clauses = [{enum, Meta, Left, Var, Filters}],
|
||||
|
||||
{'case', -1, Right, [
|
||||
{clause, -1,
|
||||
[Var],
|
||||
[[elixir_utils:erl_call(Line, erlang, is_list, [Var])]],
|
||||
[build_comprehension(Line, Clauses, Expr, Into)]},
|
||||
{clause, -1,
|
||||
[Var],
|
||||
[],
|
||||
[build_reduce(Clauses, Expr, Into, Acc, S)]}
|
||||
]}
|
||||
end;
|
||||
|
||||
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
|
||||
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
|
||||
true -> build_comprehension(Line, Clauses, Expr, Into);
|
||||
@@ -165,7 +142,7 @@ build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
|
||||
MatchExpr = {match, Line,
|
||||
{tuple, Line, [Acc, Fun]},
|
||||
elixir_utils:erl_call(Line, 'Elixir.Collectable', into, [Into])
|
||||
},
|
||||
},
|
||||
|
||||
TryExpr =
|
||||
{'try', Line,
|
||||
@@ -199,10 +176,8 @@ build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
|
||||
|
||||
build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
|
||||
Line = ?line(Meta),
|
||||
Inner = build_reduce_clause(T, Expr, Acc, Acc, S),
|
||||
|
||||
True = pair(Line, cont, Inner),
|
||||
False = pair(Line, cont, Acc),
|
||||
True = build_reduce_clause(T, Expr, Acc, Acc, S),
|
||||
False = Acc,
|
||||
|
||||
Clauses0 =
|
||||
case is_var(Left) of
|
||||
@@ -218,11 +193,8 @@ build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) -
|
||||
[Left, Acc], [],
|
||||
[join_filters(Line, Filters, True, False)]}|Clauses0],
|
||||
|
||||
Args = [Right, pair(Line, cont, Arg), {'fun', Line, {clauses, Clauses1}}],
|
||||
Tuple = elixir_utils:erl_call(Line, 'Elixir.Enumerable', reduce, Args),
|
||||
|
||||
%% Use -1 because in case of no returns we don't care about the result
|
||||
elixir_utils:erl_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
|
||||
Args = [Right, Arg, {'fun', Line, {clauses, Clauses1}}],
|
||||
elixir_utils:erl_call(Line, 'Elixir.Enum', reduce, Args);
|
||||
|
||||
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
@@ -10,17 +10,19 @@
|
||||
-define(tracker, 'Elixir.Kernel.LexicalTracker').
|
||||
|
||||
run(File, Dest, Callback) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{file, _} ->
|
||||
{ok, Pid} = ?tracker:start_link(Dest),
|
||||
try
|
||||
Callback(Pid)
|
||||
after
|
||||
warn_unused_aliases(File, Pid),
|
||||
warn_unused_imports(File, Pid),
|
||||
unlink(Pid), ?tracker:stop(Pid)
|
||||
end;
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
false ->
|
||||
{ok, Pid} = ?tracker:start_link(Dest),
|
||||
try Callback(Pid) of
|
||||
Res ->
|
||||
warn_unused_aliases(File, Pid),
|
||||
warn_unused_imports(File, Pid),
|
||||
Res
|
||||
after
|
||||
unlink(Pid),
|
||||
?tracker:stop(Pid)
|
||||
end;
|
||||
true ->
|
||||
Callback(nil)
|
||||
end.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
-export([macro_for/3, local_for/3, local_for/4]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(attr, '__locals_tracker').
|
||||
-define(attr, {elixir, locals_tracker}).
|
||||
-define(tracker, 'Elixir.Module.LocalsTracker').
|
||||
|
||||
macro_for(Module, Name, Arity) ->
|
||||
@@ -72,12 +72,12 @@ rewrite_clause(Else, _) -> Else.
|
||||
%% TRACKING
|
||||
|
||||
setup(Module) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{file, _} ->
|
||||
{ok, Pid} = ?tracker:start_link(),
|
||||
ets:insert(Module, {?attr, Pid}),
|
||||
ok;
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
false ->
|
||||
{ok, Pid} = ?tracker:start_link(),
|
||||
ets:insert(elixir_module:data_table(Module), {?attr, Pid}),
|
||||
ok;
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
@@ -108,7 +108,7 @@ if_tracker(Module, Callback) ->
|
||||
if_tracker(Module, ok, Callback).
|
||||
|
||||
if_tracker(Module, Default, Callback) ->
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
|
||||
Pid -> Callback(Pid)
|
||||
catch
|
||||
error:badarg -> Default
|
||||
@@ -118,7 +118,7 @@ if_tracker(Module, Default, Callback) ->
|
||||
|
||||
cache_env(#{module := Module} = RE) ->
|
||||
E = RE#{line := nil,vars := []},
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
|
||||
Pid ->
|
||||
{Pid, ?tracker:cache_env(Pid, E)}
|
||||
catch
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
expand_map(Meta, [{'|', UpdateMeta, [Left, Right]}], E) ->
|
||||
{[ELeft|ERight], EA} = elixir_exp:expand_args([Left|Right], E),
|
||||
validate_kv(Meta, ERight, Right, E),
|
||||
{{'%{}', Meta, [{'|', UpdateMeta, [ELeft, ERight]}]}, EA};
|
||||
expand_map(Meta, Args, E) ->
|
||||
{EArgs, EA} = elixir_exp:expand_args(Args, E),
|
||||
validate_kv(Meta, EArgs, Args, E),
|
||||
{{'%{}', Meta, EArgs}, EA}.
|
||||
|
||||
expand_struct(Meta, Left, Right, E) ->
|
||||
@@ -35,6 +37,15 @@ expand_struct(Meta, Left, Right, E) ->
|
||||
|
||||
{{'%', EMeta, [ELeft, ERight]}, EE}.
|
||||
|
||||
validate_kv(Meta, KV, Original, E) ->
|
||||
lists:foldl(fun
|
||||
({_K, _V}, Acc) -> Acc + 1;
|
||||
(_, Acc) ->
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"expected key-value pairs in a map, got: ~ts",
|
||||
['Elixir.Macro':to_string(lists:nth(Acc, Original))])
|
||||
end, 1, KV).
|
||||
|
||||
translate_map(Meta, Args, S) ->
|
||||
{Assocs, TUpdate, US} = extract_assoc_update(Args, S),
|
||||
translate_map(Meta, Assocs, TUpdate, US).
|
||||
@@ -101,11 +112,11 @@ load_struct(Meta, Name, S) ->
|
||||
error:undef ->
|
||||
Inspected = elixir_aliases:inspect(Name),
|
||||
|
||||
case Context of
|
||||
case Context andalso (S#elixir_scope.function == nil) of
|
||||
true ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot access struct ~ts in the same context that defines it as "
|
||||
"the struct fields are not yet accessible", [Inspected]);
|
||||
"cannot access struct ~ts, the struct was not yet defined or the struct is being "
|
||||
"accessed in the same context that defines it", [Inspected]);
|
||||
false ->
|
||||
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
|
||||
"cannot expand struct ~ts", [Inspected, Inspected])
|
||||
@@ -139,14 +150,10 @@ translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
|
||||
|
||||
Line = ?line(Meta),
|
||||
|
||||
{TArgs, SA} = lists:mapfoldl(fun
|
||||
({Key, Value}, Acc) ->
|
||||
{TKey, Acc1} = KeyFun(Key, Acc),
|
||||
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
|
||||
{{Op, ?line(Meta), TKey, TValue}, Acc2};
|
||||
(Other, _Acc) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "expected key-value pairs in map, got: ~ts",
|
||||
['Elixir.Macro':to_string(Other)])
|
||||
{TArgs, SA} = lists:mapfoldl(fun({Key, Value}, Acc) ->
|
||||
{TKey, Acc1} = KeyFun(Key, Acc),
|
||||
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
|
||||
{{Op, ?line(Meta), TKey, TValue}, Acc2}
|
||||
end, S, Assocs),
|
||||
|
||||
build_map(Line, TUpdate, TArgs, SA).
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
-module(elixir_module).
|
||||
-export([compile/4, data_table/1, docs_table/1, is_open/1,
|
||||
expand_callback/6, add_beam_chunk/3, format_error/1]).
|
||||
-export([data_table/1, defs_table/1, clas_table/1, is_open/1,
|
||||
compile/4, expand_callback/6, add_beam_chunk/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
-define(docs_attr, '__docs_table').
|
||||
-define(lexical_attr, '__lexical_tracker').
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
-define(overridable_attr, '__overridable').
|
||||
-define(location_attr, '__location').
|
||||
-define(acc_attr, {elixir, acc_attributes}).
|
||||
-define(lexical_attr, {elixir, lexical_tracker}).
|
||||
-define(persisted_attr, {elixir, persisted_attributes}).
|
||||
-define(overridable_attr, {elixir, overridable}).
|
||||
-define(location_attr, {elixir, location}).
|
||||
|
||||
%% TABLE METHODS
|
||||
|
||||
data_table(Module) ->
|
||||
Module.
|
||||
ets:lookup_element(elixir_modules, Module, 2).
|
||||
|
||||
docs_table(Module) ->
|
||||
ets:lookup_element(Module, ?docs_attr, 2).
|
||||
defs_table(Module) ->
|
||||
ets:lookup_element(elixir_modules, Module, 3).
|
||||
|
||||
clas_table(Module) ->
|
||||
ets:lookup_element(elixir_modules, Module, 4).
|
||||
|
||||
is_open(Module) ->
|
||||
Module == ets:info(Module, name).
|
||||
ets:lookup(elixir_modules, Module) /= [].
|
||||
|
||||
%% Compilation hook
|
||||
|
||||
@@ -49,12 +51,12 @@ do_compile(Line, Module, Block, Vars, E) ->
|
||||
check_module_availability(Line, File, Module),
|
||||
|
||||
Docs = elixir_compiler:get_opt(docs),
|
||||
build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
|
||||
{Data, Defs, Clas, Ref} = build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
|
||||
|
||||
try
|
||||
{Result, NE} = eval_form(Line, Module, Block, Vars, E),
|
||||
{Result, NE} = eval_form(Line, Module, Data, Block, Vars, E),
|
||||
|
||||
_ = case ets:lookup(data_table(Module), 'on_load') of
|
||||
_ = case ets:lookup(Data, 'on_load') of
|
||||
[] -> ok;
|
||||
[{on_load,OnLoad}] ->
|
||||
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
@@ -65,14 +67,14 @@ do_compile(Line, Module, Block, Vars, E) ->
|
||||
|
||||
{All, Forms0} = functions_form(Line, File, Module, Def, Defp,
|
||||
Defmacro, Defmacrop, Exports, Functions),
|
||||
Forms1 = specs_form(Module, Defmacro, Defmacrop, Forms0),
|
||||
Forms2 = types_form(Line, File, Module, Forms1),
|
||||
Forms3 = attributes_form(Line, File, Module, Forms2),
|
||||
Forms1 = specs_form(Data, Defmacro, Defmacrop, Forms0),
|
||||
Forms2 = types_form(Line, File, Data, Forms1),
|
||||
Forms3 = attributes_form(Line, File, Data, Forms2),
|
||||
|
||||
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
|
||||
|
||||
case Docs of
|
||||
true -> warn_unused_docs(Line, File, Module, doc);
|
||||
true -> warn_unused_docs(Line, File, Data, doc);
|
||||
false -> false
|
||||
end,
|
||||
|
||||
@@ -83,65 +85,68 @@ do_compile(Line, Module, Block, Vars, E) ->
|
||||
{attribute, Line, module, Module} | Forms3
|
||||
],
|
||||
|
||||
Binary = load_form(Line, Final, compile_opts(Module), NE),
|
||||
Binary = load_form(Line, Data, Final, compile_opts(Module), NE),
|
||||
{module, Module, Binary, Result}
|
||||
after
|
||||
elixir_locals:cleanup(Module),
|
||||
elixir_def:cleanup(Module),
|
||||
ets:delete(docs_table(Module)),
|
||||
ets:delete(data_table(Module))
|
||||
ets:delete(Data),
|
||||
ets:delete(Defs),
|
||||
ets:delete(Clas),
|
||||
elixir_code_server:call({undefmodule, Ref})
|
||||
end.
|
||||
|
||||
%% Hook that builds both attribute and functions and set up common hooks.
|
||||
|
||||
build(Line, File, Module, Docs, Lexical) ->
|
||||
%% Table with meta information about the module.
|
||||
DataTable = data_table(Module),
|
||||
|
||||
OldTable = ets:info(DataTable, name),
|
||||
case OldTable == DataTable of
|
||||
true ->
|
||||
[{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2),
|
||||
case ets:lookup(elixir_modules, Module) of
|
||||
[{Module, _, _, _, OldLine, OldFile}] ->
|
||||
Error = {module_in_definition, Module, OldFile, OldLine},
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, Error);
|
||||
false ->
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
|
||||
DataTable = ets:new(DataTable, [set, named_table, public]),
|
||||
ets:insert(DataTable, {before_compile, []}),
|
||||
ets:insert(DataTable, {after_compile, []}),
|
||||
Data = ets:new(Module, [set, public]),
|
||||
Defs = ets:new(Module, [set, public]),
|
||||
Clas = ets:new(Module, [bag, public]),
|
||||
|
||||
Ref = elixir_code_server:call({defmodule, self(),
|
||||
{Module, Data, Defs, Clas, Line, File}}),
|
||||
|
||||
ets:insert(Data, {before_compile, []}),
|
||||
ets:insert(Data, {after_compile, []}),
|
||||
ets:insert(Data, {moduledoc, nil}),
|
||||
|
||||
case Docs of
|
||||
true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]});
|
||||
_ -> ets:insert(DataTable, {on_definition, []})
|
||||
true -> ets:insert(Data, {on_definition, [{'Elixir.Module', compile_doc}]});
|
||||
_ -> ets:insert(Data, {on_definition, []})
|
||||
end,
|
||||
|
||||
Attributes = [behaviour, on_load, spec, type, typep, opaque, callback, compile, external_resource],
|
||||
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition, derive|Attributes]}),
|
||||
ets:insert(DataTable, {?persisted_attr, [vsn|Attributes]}),
|
||||
ets:insert(DataTable, {?docs_attr, ets:new(DataTable, [ordered_set, public])}),
|
||||
ets:insert(DataTable, {?lexical_attr, Lexical}),
|
||||
ets:insert(DataTable, {?overridable_attr, []}),
|
||||
ets:insert(DataTable, {?location_attr, [{File, Line}]}),
|
||||
Attributes = [behaviour, on_load, compile, external_resource],
|
||||
ets:insert(Data, {?acc_attr, [before_compile, after_compile, on_definition, derive,
|
||||
spec, type, typep, opaque, callback|Attributes]}),
|
||||
ets:insert(Data, {?persisted_attr, [vsn|Attributes]}),
|
||||
ets:insert(Data, {?lexical_attr, Lexical}),
|
||||
|
||||
%% Setup other modules
|
||||
%% Setup definition related modules
|
||||
elixir_def:setup(Module),
|
||||
elixir_locals:setup(Module),
|
||||
ok.
|
||||
elixir_def_overridable:setup(Module),
|
||||
|
||||
{Data, Defs, Clas, Ref}.
|
||||
|
||||
%% Receives the module representation and evaluates it.
|
||||
|
||||
eval_form(Line, Module, Block, Vars, E) ->
|
||||
eval_form(Line, Module, Data, Block, Vars, E) ->
|
||||
{Value, EE} = elixir_compiler:eval_forms(Block, Vars, E),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
EV = elixir_env:linify({Line, EE#{vars := [], export_vars := nil}}),
|
||||
EC = eval_callbacks(Line, Module, before_compile, [EV], EV),
|
||||
EC = eval_callbacks(Line, Data, before_compile, [EV], EV),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
{Value, EC}.
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, E) ->
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
eval_callbacks(Line, Data, Name, Args, E) ->
|
||||
Callbacks = lists:reverse(ets:lookup_element(Data, Name, 2)),
|
||||
|
||||
lists:foldl(fun({M,F}, Acc) ->
|
||||
expand_callback(Line, M, F, Args, Acc#{vars := [], export_vars := nil},
|
||||
@@ -160,13 +165,11 @@ functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Exports, Body
|
||||
|
||||
%% Add attributes handling to the form
|
||||
|
||||
attributes_form(Line, File, Module, Current) ->
|
||||
Table = data_table(Module),
|
||||
attributes_form(Line, File, Data, Current) ->
|
||||
AccAttrs = ets:lookup_element(Data, ?acc_attr, 2),
|
||||
PersistedAttrs = ets:lookup_element(Data, ?persisted_attr, 2),
|
||||
|
||||
AccAttrs = ets:lookup_element(Table, '__acc_attributes', 2),
|
||||
PersistedAttrs = ets:lookup_element(Table, '__persisted_attributes', 2),
|
||||
|
||||
Transform = fun({Key, Value}, Acc) ->
|
||||
Transform = fun({Key, Value}, Acc) when is_atom(Key) ->
|
||||
case lists:member(Key, PersistedAttrs) of
|
||||
false -> Acc;
|
||||
true ->
|
||||
@@ -182,7 +185,8 @@ attributes_form(Line, File, Module, Current) ->
|
||||
end
|
||||
end,
|
||||
|
||||
ets:foldl(Transform, Current, Table).
|
||||
Results = ets:select(Data, [{{'$1', '_'}, [{is_atom, '$1'}], ['$_']}]),
|
||||
lists:foldl(Transform, Current, Results).
|
||||
|
||||
process_attribute(Line, File, external_resource, Values) ->
|
||||
lists:usort([process_external_resource(Line, File, Value) || Value <- Values]);
|
||||
@@ -197,27 +201,21 @@ process_external_resource(Line, File, Value) ->
|
||||
|
||||
%% Types
|
||||
|
||||
types_form(Line, File, Module, Forms0) ->
|
||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||
{module, 'Elixir.Kernel.Typespec'} ->
|
||||
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
|
||||
'Elixir.Module':get_attribute(Module, typep) ++
|
||||
'Elixir.Module':get_attribute(Module, opaque),
|
||||
types_form(Line, File, Data, Forms0) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
false ->
|
||||
Types0 = get_typespec(Data, type) ++ get_typespec(Data, typep)
|
||||
++ get_typespec(Data, opaque),
|
||||
|
||||
Types1 = ['Elixir.Kernel.Typespec':translate_type(Kind, Expr, Doc, Caller) ||
|
||||
{Kind, Expr, Doc, Caller} <- Types0],
|
||||
|
||||
'Elixir.Module':delete_attribute(Module, type),
|
||||
'Elixir.Module':delete_attribute(Module, typep),
|
||||
'Elixir.Module':delete_attribute(Module, opaque),
|
||||
|
||||
warn_unused_docs(Line, File, Module, typedoc),
|
||||
|
||||
warn_unused_docs(Line, File, Data, typedoc),
|
||||
Forms1 = types_attributes(Types1, Forms0),
|
||||
Forms2 = export_types_attributes(Types1, Forms1),
|
||||
typedocs_attributes(Types1, Forms2);
|
||||
|
||||
{error, _} ->
|
||||
true ->
|
||||
Forms0
|
||||
end.
|
||||
|
||||
@@ -247,23 +245,17 @@ typedocs_attributes(Types, Forms) ->
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Module, Defmacro, Defmacrop, Forms) ->
|
||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||
{module, 'Elixir.Kernel.Typespec'} ->
|
||||
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
|
||||
'Elixir.Module':get_attribute(Module, callback),
|
||||
|
||||
specs_form(Data, Defmacro, Defmacrop, Forms) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
false ->
|
||||
Specs0 = get_typespec(Data, spec) ++ get_typespec(Data, callback),
|
||||
Specs1 = ['Elixir.Kernel.Typespec':translate_spec(Kind, Expr, Caller) ||
|
||||
{Kind, Expr, Caller} <- Specs0],
|
||||
Specs2 = lists:flatmap(fun(Spec) ->
|
||||
translate_macro_spec(Spec, Defmacro, Defmacrop)
|
||||
end, Specs1),
|
||||
|
||||
'Elixir.Module':delete_attribute(Module, spec),
|
||||
'Elixir.Module':delete_attribute(Module, callback),
|
||||
specs_attributes(Forms, Specs2);
|
||||
|
||||
{error, _} ->
|
||||
true ->
|
||||
Forms
|
||||
end.
|
||||
|
||||
@@ -307,11 +299,11 @@ compile_opts(Module) ->
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
load_form(Line, Forms, Opts, E) ->
|
||||
load_form(Line, Data, Forms, Opts, E) ->
|
||||
elixir_compiler:module(Forms, Opts, E, fun(Module, Binary0) ->
|
||||
Docs = elixir_compiler:get_opt(docs),
|
||||
Binary = add_docs_chunk(Binary0, Module, Line, Docs),
|
||||
eval_callbacks(Line, Module, after_compile, [E, Binary], E),
|
||||
Binary = add_docs_chunk(Binary0, Data, Line, Docs),
|
||||
eval_callbacks(Line, Data, after_compile, [E, Binary], E),
|
||||
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
@@ -331,23 +323,30 @@ load_form(Line, Forms, Opts, E) ->
|
||||
Binary
|
||||
end).
|
||||
|
||||
add_docs_chunk(Bin, Module, Line, true) ->
|
||||
add_docs_chunk(Bin, Data, Line, true) ->
|
||||
ChunkData = term_to_binary({elixir_docs_v1, [
|
||||
{docs, get_docs(Module)},
|
||||
{moduledoc, get_moduledoc(Line, Module)}
|
||||
]}),
|
||||
{docs, get_docs(Data)},
|
||||
{moduledoc, get_moduledoc(Line, Data)}
|
||||
]}),
|
||||
add_beam_chunk(Bin, "ExDc", ChunkData);
|
||||
|
||||
add_docs_chunk(Bin, _, _, _) -> Bin.
|
||||
|
||||
get_docs(Module) ->
|
||||
ordsets:from_list(
|
||||
get_docs(Data) ->
|
||||
Match = ets:match(Data, {{doc, '$1'}, '$2', '$3', '$4', '$5'}),
|
||||
lists:usort(
|
||||
[{Tuple, Line, Kind, Sig, Doc} ||
|
||||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
|
||||
Kind =/= type, Kind =/= opaque]).
|
||||
[Tuple, Line, Kind, Sig, Doc] <- Match,
|
||||
Kind =/= type, Kind =/= opaque]).
|
||||
|
||||
get_moduledoc(Line, Module) ->
|
||||
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
|
||||
get_moduledoc(Line, Data) ->
|
||||
{Line, ets:lookup_element(Data, moduledoc, 2)}.
|
||||
|
||||
get_typespec(Data, Key) ->
|
||||
case ets:lookup(Data, Key) of
|
||||
[{Key, Value}] -> Value;
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
check_module_availability(Line, File, Module) ->
|
||||
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
|
||||
@@ -370,8 +369,8 @@ check_module_availability(Line, File, Module) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
warn_unused_docs(Line, File, Module, Attribute) ->
|
||||
case ets:member(data_table(Module), Attribute) of
|
||||
warn_unused_docs(Line, File, Data, Attribute) ->
|
||||
case ets:member(Data, Attribute) of
|
||||
true ->
|
||||
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_doc, Attribute});
|
||||
_ ->
|
||||
|
||||
@@ -7,7 +7,7 @@ Nonterminals
|
||||
add_op_eol mult_op_eol hat_op_eol two_op_eol pipe_op_eol stab_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
|
||||
type_op_eol rel_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_paren close_paren empty_paren eoe
|
||||
list list_args open_bracket close_bracket
|
||||
tuple open_curly close_curly
|
||||
bit_string open_bit close_bit
|
||||
@@ -16,11 +16,11 @@ Nonterminals
|
||||
container_args_base container_args call_args_parens_base call_args_parens parens_call
|
||||
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
|
||||
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
|
||||
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
|
||||
stab stab_eoe stab_expr stab_maybe_expr stab_parens_many
|
||||
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
|
||||
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_bracket_identifier
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list
|
||||
.
|
||||
|
||||
Terminals
|
||||
@@ -32,13 +32,13 @@ Terminals
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
|
||||
type_op dual_op add_op mult_op hat_op two_op pipe_op stab_op when_op assoc_op
|
||||
capture_op rel_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.'
|
||||
'true' 'false' 'nil' 'do' eol ';' ',' '.'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
|
||||
.
|
||||
|
||||
Rootsymbol grammar.
|
||||
|
||||
%% There are two shift/reduce conflicts coming from call_args_parens.
|
||||
%% Two shift/reduce conflicts coming from call_args_parens.
|
||||
Expect 2.
|
||||
|
||||
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
|
||||
@@ -58,12 +58,12 @@ Left 130 or_op_eol. %% ||, |||, or
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% ==, !=, =~, ===, !==
|
||||
Left 160 rel_op_eol. %% <, >, <=, >=
|
||||
Left 170 arrow_op_eol. %% < (op), (op) > (e.g |>, <<<, >>>)
|
||||
Left 170 arrow_op_eol. %% < (op), (op) > (|>, <<<, >>>, ~>>, <<~, ~>, <~, <~>, <|>)
|
||||
Left 180 in_op_eol. %% in
|
||||
Right 200 two_op_eol. %% ++, --, .., <>
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Left 250 hat_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Left 250 hat_op_eol. %% ^ (op) (^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op. %% .
|
||||
@@ -72,16 +72,16 @@ Nonassoc 330 dot_identifier.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
|
||||
grammar -> eol : nil.
|
||||
grammar -> eoe : nil.
|
||||
grammar -> expr_list : to_block('$1').
|
||||
grammar -> eol expr_list : to_block('$2').
|
||||
grammar -> expr_list eol : to_block('$1').
|
||||
grammar -> eol expr_list eol : to_block('$2').
|
||||
grammar -> eoe expr_list : to_block('$2').
|
||||
grammar -> expr_list eoe : to_block('$1').
|
||||
grammar -> eoe expr_list eoe : to_block('$2').
|
||||
grammar -> '$empty' : nil.
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
expr_list -> expr_list eol expr : ['$3'|'$1'].
|
||||
expr_list -> expr_list eoe expr : ['$3'|'$1'].
|
||||
|
||||
expr -> empty_paren : nil.
|
||||
expr -> matched_expr : '$1'.
|
||||
@@ -202,8 +202,12 @@ access_expr -> bracket_expr : '$1'.
|
||||
access_expr -> at_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> fn_eoe stab end_eoe : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> open_paren stab ';' close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> open_paren ';' stab ';' close_paren : build_stab(reverse('$3')).
|
||||
access_expr -> open_paren ';' stab close_paren : build_stab(reverse('$3')).
|
||||
access_expr -> open_paren ';' close_paren : build_stab([]).
|
||||
access_expr -> number : ?exprs('$1').
|
||||
access_expr -> signed_number : {element(4, '$1'), meta('$1'), ?exprs('$1')}.
|
||||
access_expr -> list : element(1, '$1').
|
||||
@@ -240,28 +244,32 @@ bracket_at_expr -> at_op_eol access_expr bracket_arg :
|
||||
|
||||
%% Blocks
|
||||
|
||||
do_block -> do_eol 'end' : [[{do,nil}]].
|
||||
do_block -> do_eol stab end_eol : [[{do, build_stab(reverse('$2'))}]].
|
||||
do_block -> do_eol block_list 'end' : [[{do, nil}|'$2']].
|
||||
do_block -> do_eol stab_eol block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
|
||||
do_block -> do_eoe 'end' : [[{do,nil}]].
|
||||
do_block -> do_eoe stab end_eoe : [[{do, build_stab(reverse('$2'))}]].
|
||||
do_block -> do_eoe block_list 'end' : [[{do, nil}|'$2']].
|
||||
do_block -> do_eoe stab_eoe block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
|
||||
|
||||
fn_eol -> 'fn' : '$1'.
|
||||
fn_eol -> 'fn' eol : '$1'.
|
||||
eoe -> eol : '$1'.
|
||||
eoe -> ';' : '$1'.
|
||||
eoe -> eol ';' : '$1'.
|
||||
|
||||
do_eol -> 'do' : '$1'.
|
||||
do_eol -> 'do' eol : '$1'.
|
||||
fn_eoe -> 'fn' : '$1'.
|
||||
fn_eoe -> 'fn' eoe : '$1'.
|
||||
|
||||
end_eol -> 'end' : '$1'.
|
||||
end_eol -> eol 'end' : '$2'.
|
||||
do_eoe -> 'do' : '$1'.
|
||||
do_eoe -> 'do' eoe : '$1'.
|
||||
|
||||
block_eol -> block_identifier : '$1'.
|
||||
block_eol -> block_identifier eol : '$1'.
|
||||
end_eoe -> 'end' : '$1'.
|
||||
end_eoe -> eoe 'end' : '$2'.
|
||||
|
||||
block_eoe -> block_identifier : '$1'.
|
||||
block_eoe -> block_identifier eoe : '$1'.
|
||||
|
||||
stab -> stab_expr : ['$1'].
|
||||
stab -> stab eol stab_expr : ['$3'|'$1'].
|
||||
stab -> stab eoe stab_expr : ['$3'|'$1'].
|
||||
|
||||
stab_eol -> stab : '$1'.
|
||||
stab_eol -> stab eol : '$1'.
|
||||
stab_eoe -> stab : '$1'.
|
||||
stab_eoe -> stab eoe : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
|
||||
@@ -275,8 +283,8 @@ stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
|
||||
block_item -> block_eol stab_eol : {?exprs('$1'), build_stab(reverse('$2'))}.
|
||||
block_item -> block_eol : {?exprs('$1'), nil}.
|
||||
block_item -> block_eoe stab_eoe : {?exprs('$1'), build_stab(reverse('$2'))}.
|
||||
block_item -> block_eoe : {?exprs('$1'), nil}.
|
||||
|
||||
block_list -> block_item : ['$1'].
|
||||
block_list -> block_item block_list : ['$1'|'$2'].
|
||||
|
||||
@@ -151,6 +151,10 @@ escape(Expr, Unquote) ->
|
||||
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
quote({unquote_splicing, _, [_]}, _Binding, #elixir_quote{unquote=true}, _) ->
|
||||
argument_error(<<"unquote_splicing only works inside arguments and block contexts, "
|
||||
"wrap it in parens if you want it to work with one-liners">>);
|
||||
|
||||
quote(Expr, nil, Q, E) ->
|
||||
do_quote(Expr, Q, E);
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
-module(elixir_rewrite).
|
||||
-export([rewrite/5, inline/3]).
|
||||
|
||||
%% Convenience variables
|
||||
|
||||
-define(hidden, [{line,-1}]).
|
||||
|
||||
-define(atom, 'Elixir.Atom').
|
||||
-define(enum, 'Elixir.Enum').
|
||||
-define(float, 'Elixir.Float').
|
||||
-define(io, 'Elixir.IO').
|
||||
-define(integer, 'Elixir.Integer').
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(list, 'Elixir.List').
|
||||
-define(map, 'Elixir.Map').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(port, 'Elixir.Port').
|
||||
-define(process, 'Elixir.Process').
|
||||
-define(string, 'Elixir.String').
|
||||
-define(string_chars, 'Elixir.String.Chars').
|
||||
-define(system, 'Elixir.System').
|
||||
-define(tuple, 'Elixir.Tuple').
|
||||
|
||||
%% Inline
|
||||
|
||||
%% Inline rules are straight-forward, they keep the same
|
||||
%% number and order of arguments and show up on captures.
|
||||
|
||||
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
|
||||
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
|
||||
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
|
||||
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
|
||||
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
|
||||
inline(?float, to_string, 1) -> {erlang, float_to_binary};
|
||||
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
|
||||
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
|
||||
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
|
||||
inline(?list, to_float, 1) -> {erlang, list_to_float};
|
||||
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
|
||||
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
|
||||
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
|
||||
|
||||
inline(?kernel, '+', 2) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 2) -> {erlang, '-'};
|
||||
inline(?kernel, '+', 1) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 1) -> {erlang, '-'};
|
||||
inline(?kernel, '*', 2) -> {erlang, '*'};
|
||||
inline(?kernel, '/', 2) -> {erlang, '/'};
|
||||
inline(?kernel, '++', 2) -> {erlang, '++'};
|
||||
inline(?kernel, '--', 2) -> {erlang, '--'};
|
||||
inline(?kernel, 'not', 1) -> {erlang, 'not'};
|
||||
inline(?kernel, '<', 2) -> {erlang, '<'};
|
||||
inline(?kernel, '>', 2) -> {erlang, '>'};
|
||||
inline(?kernel, '<=', 2) -> {erlang, '=<'};
|
||||
inline(?kernel, '>=', 2) -> {erlang, '>='};
|
||||
inline(?kernel, '==', 2) -> {erlang, '=='};
|
||||
inline(?kernel, '!=', 2) -> {erlang, '/='};
|
||||
inline(?kernel, '===', 2) -> {erlang, '=:='};
|
||||
inline(?kernel, '!==', 2) -> {erlang, '=/='};
|
||||
inline(?kernel, abs, 1) -> {erlang, abs};
|
||||
inline(?kernel, apply, 2) -> {erlang, apply};
|
||||
inline(?kernel, apply, 3) -> {erlang, apply};
|
||||
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
|
||||
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
|
||||
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
|
||||
inline(?kernel, 'div', 2) -> {erlang, 'div'};
|
||||
inline(?kernel, exit, 1) -> {erlang, exit};
|
||||
inline(?kernel, hd, 1) -> {erlang, hd};
|
||||
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
|
||||
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
|
||||
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
|
||||
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
|
||||
inline(?kernel, is_float, 1) -> {erlang, is_float};
|
||||
inline(?kernel, is_function, 1) -> {erlang, is_function};
|
||||
inline(?kernel, is_function, 2) -> {erlang, is_function};
|
||||
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
|
||||
inline(?kernel, is_list, 1) -> {erlang, is_list};
|
||||
inline(?kernel, is_map, 1) -> {erlang, is_map};
|
||||
inline(?kernel, is_number, 1) -> {erlang, is_number};
|
||||
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
|
||||
inline(?kernel, is_port, 1) -> {erlang, is_port};
|
||||
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
|
||||
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
|
||||
inline(?kernel, length, 1) -> {erlang, length};
|
||||
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
|
||||
inline(?kernel, map_size, 1) -> {erlang, map_size};
|
||||
inline(?kernel, max, 2) -> {erlang, max};
|
||||
inline(?kernel, min, 2) -> {erlang, min};
|
||||
inline(?kernel, node, 0) -> {erlang, node};
|
||||
inline(?kernel, node, 1) -> {erlang, node};
|
||||
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
|
||||
inline(?kernel, round, 1) -> {erlang, round};
|
||||
inline(?kernel, self, 0) -> {erlang, self};
|
||||
inline(?kernel, send, 2) -> {erlang, send};
|
||||
inline(?kernel, spawn, 1) -> {erlang, spawn};
|
||||
inline(?kernel, spawn, 3) -> {erlang, spawn};
|
||||
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
|
||||
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
|
||||
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
|
||||
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
|
||||
inline(?kernel, throw, 1) -> {erlang, throw};
|
||||
inline(?kernel, tl, 1) -> {erlang, tl};
|
||||
inline(?kernel, trunc, 1) -> {erlang, trunc};
|
||||
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
|
||||
|
||||
inline(?map, keys, 1) -> {maps, keys};
|
||||
inline(?map, merge, 2) -> {maps, merge};
|
||||
inline(?map, size, 1) -> {maps, size};
|
||||
inline(?map, values, 1) -> {maps, values};
|
||||
inline(?map, to_list, 1) -> {maps, to_list};
|
||||
|
||||
inline(?node, spawn, 2) -> {erlang, spawn};
|
||||
inline(?node, spawn, 3) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn, 4) -> {erlang, spawn};
|
||||
inline(?node, spawn, 5) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
|
||||
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
|
||||
|
||||
inline(?process, exit, 2) -> {erlang, exit};
|
||||
inline(?process, spawn, 2) -> {erlang, spawn_opt};
|
||||
inline(?process, spawn, 4) -> {erlang, spawn_opt};
|
||||
inline(?process, demonitor, 1) -> {erlang, demonitor};
|
||||
inline(?process, demonitor, 2) -> {erlang, demonitor};
|
||||
inline(?process, link, 1) -> {erlang, link};
|
||||
inline(?process, unlink, 1) -> {erlang, unlink};
|
||||
|
||||
inline(?port, open, 2) -> {erlang, open_port};
|
||||
inline(?port, call, 3) -> {erlang, port_call};
|
||||
inline(?port, close, 1) -> {erlang, port_close};
|
||||
inline(?port, command, 2) -> {erlang, port_command};
|
||||
inline(?port, command, 3) -> {erlang, port_command};
|
||||
inline(?port, connect, 2) -> {erlang, port_connect};
|
||||
inline(?port, control, 3) -> {erlang, port_control};
|
||||
inline(?port, info, 1) -> {erlang, port_info};
|
||||
inline(?port, info, 2) -> {erlang, port_info};
|
||||
inline(?port, list, 0) -> {erlang, ports};
|
||||
|
||||
inline(?string, to_float, 1) -> {erlang, binary_to_float};
|
||||
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
|
||||
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
|
||||
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
|
||||
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
%% Rewrite rules
|
||||
%% Rewrite rules are more complex than regular inlining code.
|
||||
%% It receives all remote call arguments and return quoted
|
||||
%% expressions with the new environment.
|
||||
|
||||
%% Complex rewrite rules
|
||||
|
||||
rewrite(?string_chars, _DotMeta, 'to_string', _Meta, [String]) when is_binary(String) ->
|
||||
String;
|
||||
rewrite(?string_chars, DotMeta, 'to_string', Meta, [String]) ->
|
||||
Var = {'_rewrite', ?hidden, nil},
|
||||
Guard = {{'.', ?hidden, [erlang, is_binary]}, ?hidden, [Var]},
|
||||
Slow = remote(?string_chars, DotMeta, 'to_string', Meta, [Var]),
|
||||
Fast = Var,
|
||||
|
||||
{'case', ?hidden, [String, [{do,
|
||||
[{'->', ?hidden, [[{'when', ?hidden, [Var, Guard]}], Fast]},
|
||||
{'->', ?hidden, [[Var], Slow]}]
|
||||
}]]};
|
||||
|
||||
rewrite(?enum, DotMeta, 'reverse', Meta, [List]) when is_list(List) ->
|
||||
remote(lists, DotMeta, 'reverse', Meta, [List]);
|
||||
rewrite(?enum, DotMeta, 'reverse', Meta, [List]) ->
|
||||
Var = {'_rewrite', ?hidden, nil},
|
||||
Guard = {{'.', ?hidden, [erlang, is_list]}, ?hidden, [Var]},
|
||||
Slow = remote(?enum, DotMeta, 'reverse', Meta, [Var]),
|
||||
Fast = remote(lists, DotMeta, 'reverse', Meta, [Var]),
|
||||
|
||||
{'case', ?hidden, [List, [{do,
|
||||
[{'->', ?hidden, [[{'when', ?hidden, [Var, Guard]}], Fast]},
|
||||
{'->', ?hidden, [[Var], Slow]}]
|
||||
}]]};
|
||||
|
||||
rewrite(Receiver, DotMeta, Right, Meta, Args) ->
|
||||
{EReceiver, ERight, EArgs} = rewrite(Receiver, Right, Args),
|
||||
remote(EReceiver, DotMeta, ERight, Meta, EArgs).
|
||||
|
||||
%% Simple rewrite rules
|
||||
|
||||
rewrite(?atom, to_string, [Arg]) ->
|
||||
{erlang, atom_to_binary, [Arg, utf8]};
|
||||
rewrite(?kernel, elem, [Tuple, Index]) ->
|
||||
{erlang, element, [increment(Index), Tuple]};
|
||||
rewrite(?kernel, put_elem, [Tuple, Index, Value]) ->
|
||||
{erlang, setelement, [increment(Index), Tuple, Value]};
|
||||
rewrite(?map, 'has_key?', [Map, Key]) ->
|
||||
{maps, is_key, [Key, Map]};
|
||||
rewrite(?map, fetch, [Map, Key]) ->
|
||||
{maps, find, [Key, Map]};
|
||||
rewrite(?map, put, [Map, Key, Value]) ->
|
||||
{maps, put, [Key, Value, Map]};
|
||||
rewrite(?map, delete, [Map, Key]) ->
|
||||
{maps, remove, [Key, Map]};
|
||||
rewrite(?process, monitor, [Arg]) ->
|
||||
{erlang, monitor, [process, Arg]};
|
||||
rewrite(?string, to_atom, [Arg]) ->
|
||||
{erlang, binary_to_atom, [Arg, utf8]};
|
||||
rewrite(?string, to_existing_atom, [Arg]) ->
|
||||
{erlang, binary_to_existing_atom, [Arg, utf8]};
|
||||
rewrite(?tuple, insert_at, [Tuple, Index, Term]) ->
|
||||
{erlang, insert_element, [increment(Index), Tuple, Term]};
|
||||
rewrite(?tuple, delete_at, [Tuple, Index]) ->
|
||||
{erlang, delete_element, [increment(Index), Tuple]};
|
||||
rewrite(?tuple, duplicate, [Data, Size]) ->
|
||||
{erlang, make_tuple, [Size, Data]};
|
||||
rewrite(Receiver, Fun, Args) ->
|
||||
{Receiver, Fun, Args}.
|
||||
|
||||
%% Rewrite helpers
|
||||
|
||||
remote(Receiver, DotMeta, Right, Meta, Args) ->
|
||||
{{'.', DotMeta, [Receiver, Right]}, Meta, Args}.
|
||||
|
||||
increment(Number) when is_number(Number) ->
|
||||
Number + 1;
|
||||
increment(Other) ->
|
||||
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
|
||||
@@ -7,6 +7,16 @@ start_link() ->
|
||||
|
||||
init(ok) ->
|
||||
Workers = [
|
||||
{
|
||||
elixir_config,
|
||||
{elixir_config, start_link, []},
|
||||
|
||||
permanent, % Restart = permanent | transient | temporary
|
||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
||||
worker, % Type = worker | supervisor
|
||||
[elixir_config] % Modules = [Module] | dynamic
|
||||
},
|
||||
|
||||
{
|
||||
elixir_code_server,
|
||||
{elixir_code_server, start_link, []},
|
||||
|
||||
@@ -33,10 +33,16 @@
|
||||
|
||||
-define(arrow_op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>).
|
||||
T1 == $>, T2 == $>, T3 == $>;
|
||||
T1 == $~, T2 == $>, T3 == $>;
|
||||
T1 == $<, T2 == $<, T3 == $~;
|
||||
T1 == $<, T2 == $~, T3 == $>;
|
||||
T1 == $<, T2 == $|, T3 == $>).
|
||||
|
||||
-define(arrow_op(T1, T2),
|
||||
T1 == $|, T2 == $>).
|
||||
T1 == $|, T2 == $>;
|
||||
T1 == $~, T2 == $>;
|
||||
T1 == $<, T2 == $~).
|
||||
|
||||
-define(rel_op(T),
|
||||
T == $<;
|
||||
@@ -299,10 +305,19 @@ tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
% End of line
|
||||
|
||||
tokenize(";" ++ Rest, Line, Scope, []) ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, ';', []));
|
||||
tokenize(Rest, Line, Scope, [{';', Line}]);
|
||||
|
||||
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, ';', [Top|Tokens]));
|
||||
tokenize(";" ++ Rest, Line, Scope, [Top|_] = Tokens) when element(1, Top) /= ';' ->
|
||||
tokenize(Rest, Line, Scope, [{';', Line}|Tokens]);
|
||||
|
||||
tokenize("\\" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\n" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\r\n" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
@@ -311,10 +326,10 @@ tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
|
||||
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
|
||||
|
||||
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
|
||||
|
||||
% Stand-alone tokens
|
||||
|
||||
@@ -458,6 +473,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
handle_space_sensitive_tokens(strip_horizontal_space(Rest), Line, Scope, Tokens);
|
||||
tokenize([T|Rest] = Original, Line, _Scope, Tokens) when ?is_invalid_space(T) ->
|
||||
Message = io_lib:format("invalid space character U+~.16B before: ", [T]),
|
||||
{error, {Line, lists:flatten(Message), until_eol(Rest)}, Original, Tokens};
|
||||
|
||||
tokenize(T, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid token: ", until_eol(T)}, T, Tokens}.
|
||||
|
||||
@@ -598,9 +617,10 @@ handle_space_sensitive_tokens(String, Line, Scope, Tokens) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
|
||||
eol(_Line, [{';',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, [{eol,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Tokens) -> [{eol,Line}|Tokens].
|
||||
|
||||
unsafe_to_atom(Part, Line, #elixir_tokenizer{}) when
|
||||
is_binary(Part) andalso size(Part) > 255;
|
||||
@@ -827,7 +847,7 @@ check_call_identifier(_Kind, Line, Atom, [$(|_]) -> {paren_identifier, Line, Ato
|
||||
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> {bracket_identifier, Line, Atom};
|
||||
check_call_identifier(Kind, Line, Atom, _Rest) -> {Kind, Line, Atom}.
|
||||
|
||||
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, [{eol,_}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, T) -> [Left|T].
|
||||
|
||||
%% Error handling
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
translate({'=', Meta, [Left, Right]}, S) ->
|
||||
Return = case Left of
|
||||
{'_', _, Atom} when is_atom(Atom) -> false;
|
||||
{'_', _, Atom} when is_atom(Atom) -> S#elixir_scope.return;
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
@@ -257,6 +257,9 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
|
||||
%% the is_map/1 guard instead of matching on map. Hopefully
|
||||
%% we can use a match on 17.1.
|
||||
%%
|
||||
%% In the future, we could also use maps:get/2 instead
|
||||
%% of pattern match, reducing the AST footprint.
|
||||
%%
|
||||
%% http://erlang.org/pipermail/erlang-bugs/2014-April/004338.html
|
||||
{{'case', -1, TLeft, [
|
||||
{clause, -1,
|
||||
|
||||
@@ -156,6 +156,16 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.BadStructError') ->
|
||||
erl_tuple_size(Meta, Var, 3),
|
||||
erl_record_compare(Meta, Var, badstruct));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadMapError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_record_compare(Meta, Var, badmap));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.KeyError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_record_compare(Meta, Var, badkey));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
|
||||
erl_or(Meta,
|
||||
{erl(Meta, '=='), Meta, [Var, badarg]},
|
||||
|
||||
@@ -381,8 +381,6 @@ defmodule DictTest.Common do
|
||||
dict = new_dict([{1}, {2}, {3}], fn {x} -> {<<x + 64>>, x} end)
|
||||
assert Dict.size(dict) == 3
|
||||
assert Enum.sort(dict) == [{"A", 1}, {"B", 2}, {"C", 3}]
|
||||
|
||||
assert Collectable.empty(new_dict) == new_dict([])
|
||||
end
|
||||
|
||||
test "is zippable" do
|
||||
|
||||
@@ -363,11 +363,6 @@ defmodule EnumTest.List do
|
||||
assert Enum.to_list(1 .. 3) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :traverse do
|
||||
assert Enum.traverse([1, 2, 3], &(&1 * &1)) == [1, 4, 9]
|
||||
assert Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v*2} end) == %{a: 2, b: 4}
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq([1, 2, 3, 2, 1]) == [1, 2, 3]
|
||||
assert Enum.uniq([1, 2, 3, 2, 1], fn x -> x end) == [1, 2, 3]
|
||||
|
||||
@@ -291,7 +291,7 @@ defmodule Kernel.ExceptionTest do
|
||||
|
||||
test "format_fa" do
|
||||
assert Exception.format_fa(fn -> end, 1) =~
|
||||
~r"#Function<\d\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
|
||||
~r"#Function<\d+\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
|
||||
end
|
||||
|
||||
import Exception, only: [message: 1]
|
||||
@@ -308,8 +308,10 @@ defmodule Kernel.ExceptionTest do
|
||||
|
||||
test "undefined function message" do
|
||||
assert %UndefinedFunctionError{} |> message == "undefined function"
|
||||
assert %UndefinedFunctionError{module: Kernel, function: :bar, arity: 1} |> message ==
|
||||
"undefined function: Kernel.bar/1"
|
||||
assert %UndefinedFunctionError{module: Foo, function: :bar, arity: 1} |> message ==
|
||||
"undefined function: Foo.bar/1"
|
||||
"undefined function: Foo.bar/1 (module Foo is not available)"
|
||||
assert %UndefinedFunctionError{module: nil, function: :bar, arity: 0} |> message ==
|
||||
"undefined function: nil.bar/0"
|
||||
end
|
||||
@@ -325,4 +327,20 @@ defmodule Kernel.ExceptionTest do
|
||||
assert %ErlangError{original: :sample} |> message ==
|
||||
"erlang error: :sample"
|
||||
end
|
||||
|
||||
test "format_stacktrace from file" do
|
||||
assert_raise ArgumentError, fn ->
|
||||
Code.eval_string("def foo do end", [], file: "myfile")
|
||||
end
|
||||
|
||||
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (file)"
|
||||
end
|
||||
|
||||
test "format_stacktrace from module" do
|
||||
assert_raise ArgumentError, fn ->
|
||||
Code.eval_string("defmodule Foo do raise ArgumentError, ~s(oops) end", [], file: "myfile")
|
||||
end
|
||||
|
||||
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (module)"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
LF
|
||||
CR
CRLF
|
||||
LFCR
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenEvent.StreamTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defmodule SlowHandler do
|
||||
use GenEvent
|
||||
|
||||
def handle_event(_event, _state) do
|
||||
:timer.sleep(100)
|
||||
:remove_handler
|
||||
end
|
||||
end
|
||||
|
||||
defp notify(:async, manager, event), do: GenEvent.notify(manager, event)
|
||||
defp notify(:sync, manager, event), do: GenEvent.sync_notify(manager, event)
|
||||
defp notify(:ack, manager, event), do: GenEvent.ack_notify(manager, event)
|
||||
|
||||
@receive_timeout 1000
|
||||
|
||||
test "ack stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.ack_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
spawn_link fn ->
|
||||
GenEvent.ack_notify(pid, i)
|
||||
end
|
||||
end
|
||||
|
||||
# Note the length is 4 compared to 5 n the sync case.
|
||||
wait_for_queue_length(pid, 4)
|
||||
end
|
||||
|
||||
test "sync stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
spawn_link fn ->
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_queue_length(pid, 5)
|
||||
end
|
||||
|
||||
test "async stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 1)
|
||||
end
|
||||
|
||||
test "stream/2 with timeout" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
child = spawn_link fn ->
|
||||
Enum.take(GenEvent.stream(pid, timeout: 50), 5)
|
||||
end
|
||||
|
||||
assert_receive {:EXIT, ^child,
|
||||
{:timeout, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "stream/2 with error/timeout on subscription" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
child = spawn fn -> Enum.to_list(GenEvent.stream(pid)) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.exit(child, :kill)
|
||||
wait_for_handlers(pid, 0)
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "stream/2 with manager killed and trap_exit" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
Process.exit(pid, :kill)
|
||||
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
|
||||
assert_receive ({:EXIT, ^child, {stat, {Enumerable.GenEvent.Stream, :stop, [_,_]}}}
|
||||
when stat in [:killed, :noproc]), @receive_timeout
|
||||
end
|
||||
|
||||
test "stream/2 with manager not alive" do
|
||||
stream = GenEvent.stream(:does_not_exit)
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
assert_receive {:EXIT, ^child,
|
||||
{:noproc, {Enumerable.GenEvent.Stream, :start, [_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
Enum.each [:sync, :async, :ack], fn mode ->
|
||||
@mode mode
|
||||
|
||||
test "#{mode} stream/2 with parallel use (and first finishing first)" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:take3, Enum.take(stream, 3)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
spawn_link fn -> send parent, {:take5, Enum.take(stream, 5)} end
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
# Notify the events for both handlers
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
assert_receive {:take3, [1, 2, 3]}, @receive_timeout
|
||||
|
||||
# Notify the events for to_list stream handler
|
||||
for i <- 4..5 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
assert_receive {:take5, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager stop" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:EXIT, ^child, :normal}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with swap_handler" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref())
|
||||
|
||||
parent = self()
|
||||
spawn_link(fn -> send parent, Enum.take(stream, 5) end)
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
[handler] = GenEvent.which_handlers(pid)
|
||||
GenEvent.swap_handler(pid, handler, :swap_handler, UnknownHandler, [])
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager unregistered" do
|
||||
{:ok, pid} = GenEvent.start_link(name: :unreg)
|
||||
stream = GenEvent.stream(:unreg)
|
||||
|
||||
parent = self()
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(stream, 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
# Unregister the process
|
||||
Process.unregister(:unreg)
|
||||
|
||||
# Notify the remaining events
|
||||
for i <- 4..5 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
# We should have gotten the message and all handlers were removed
|
||||
assert_receive [1, 2, 3, 4, 5], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 flushes events on abort" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
spawn_link fn ->
|
||||
wait_for_handlers(pid, 2)
|
||||
notify(@mode, pid, 1)
|
||||
notify(@mode, pid, 2)
|
||||
notify(@mode, pid, 3)
|
||||
end
|
||||
|
||||
GenEvent.add_handler(pid, SlowHandler, [])
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
try do
|
||||
Enum.each stream, fn _ -> throw :done end
|
||||
catch
|
||||
:done -> :ok
|
||||
end
|
||||
|
||||
# Wait for the slow handler to be removed
|
||||
# so all events have been handled
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# Check no messages leaked.
|
||||
refute_received _any
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_handlers(pid, count) do
|
||||
unless length(GenEvent.which_handlers(pid)) == count do
|
||||
wait_for_handlers(pid, count)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_queue_length(pid, count) do
|
||||
{:message_queue_len, n} = Process.info(pid, :message_queue_len)
|
||||
unless n == count do
|
||||
wait_for_queue_length(pid, count)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,438 +1,515 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenEventTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case
|
||||
|
||||
defmodule LoggerHandler do
|
||||
defmodule ReplyHandler do
|
||||
use GenEvent
|
||||
|
||||
def handle_event({:log, x}, messages) do
|
||||
{:ok, [x|messages]}
|
||||
def init(:raise) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_call(:messages, messages) do
|
||||
{:ok, Enum.reverse(messages), []}
|
||||
def init({:throw, process}) do
|
||||
{:ok, process}
|
||||
end
|
||||
|
||||
def handle_call(call, state) do
|
||||
super(call, state)
|
||||
def init({:raise, _}) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def init({:swap, {:error, :not_found}}) do
|
||||
{:error, :not_found_on_swap}
|
||||
end
|
||||
|
||||
def init({:swap, parent}) when is_pid(parent) do
|
||||
send parent, :swapped
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def init({:custom, return}) do
|
||||
return
|
||||
end
|
||||
|
||||
def init({parent, :hibernate}) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def init({parent, trap}) when is_pid(parent) and is_boolean(trap) do
|
||||
Process.flag(:trap_exit, trap)
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def handle_event(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_event(:hibernate, parent) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_event({:custom, reply}, _parent) do
|
||||
reply
|
||||
end
|
||||
|
||||
def handle_event(event, parent) do
|
||||
send parent, {:event, event}
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def handle_call(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_call(:hibernate, parent) do
|
||||
{:ok, :ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_call({:custom, reply}, _parent) do
|
||||
reply
|
||||
end
|
||||
|
||||
def handle_call(event, parent) do
|
||||
send parent, {:call, event}
|
||||
{:ok, :ok, parent}
|
||||
end
|
||||
|
||||
def handle_info(:hibernate, parent) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_info(event, parent) do
|
||||
send parent, {:info, event}
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def terminate(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def terminate(:swapped, parent) do
|
||||
send parent, {:terminate, :swapped}
|
||||
parent
|
||||
end
|
||||
|
||||
def terminate(arg, parent) do
|
||||
send parent, {:terminate, arg}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule SlowHandler do
|
||||
defmodule DefaultHandler do
|
||||
use GenEvent
|
||||
end
|
||||
|
||||
def handle_event(_event, _state) do
|
||||
:timer.sleep(100)
|
||||
:remove_handler
|
||||
defmodule Via do
|
||||
def register_name(name, pid) do
|
||||
Process.register(pid, name)
|
||||
:yes
|
||||
end
|
||||
|
||||
def whereis_name(name) do
|
||||
Process.whereis(name) || :undefined
|
||||
end
|
||||
end
|
||||
|
||||
@receive_timeout 1000
|
||||
|
||||
test "start_link/2 and handler workflow" do
|
||||
test "start/1" do
|
||||
assert {:ok, pid} = GenEvent.start()
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start(name: :my_gen_event_name)
|
||||
assert GenEvent.which_handlers(:my_gen_event_name) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(:my_gen_event_name) == :ok
|
||||
end
|
||||
|
||||
test "start_link/1" do
|
||||
assert {:ok, pid} = GenEvent.start_link()
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
|
||||
assert GenEvent.which_handlers(:my_gen_event_name) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(:my_gen_event_name) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: {:global, :my_gen_event_name})
|
||||
assert GenEvent.which_handlers({:global, :my_gen_event_name}) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop({:global, :my_gen_event_name}) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: {:via, Via, :my_gen_event_name})
|
||||
assert GenEvent.which_handlers({:via, Via, :my_gen_event_name}) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop({:via, Via, :my_gen_event_name}) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
|
||||
assert GenEvent.start_link(name: :my_gen_event_name) ==
|
||||
{:error, {:already_started, pid}}
|
||||
end
|
||||
|
||||
test "handles exit signals" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
# Terminates on signal from parent when not trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
Process.exit(pid, :shutdown)
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
refute_received {:terminate, _}
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
# Terminates on signal from parent when trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), true})
|
||||
Process.exit(pid, :shutdown)
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
assert_receive {:terminate, :stop}
|
||||
|
||||
assert GenEvent.notify(pid, {:log, 0}) == :ok
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
|
||||
assert GenEvent.notify(pid, {:log, 1}) == :ok
|
||||
assert GenEvent.notify(pid, {:log, 2}) == :ok
|
||||
# Terminates on signal not from parent when not trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
spawn fn -> Process.exit(pid, :shutdown) end
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
refute_received {:terminate, _}
|
||||
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == [1, 2]
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == []
|
||||
|
||||
assert GenEvent.remove_handler(pid, LoggerHandler, []) == :ok
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
# Does not terminate on signal not from parent when trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), true})
|
||||
terminator = spawn fn -> Process.exit(pid, :shutdown) end
|
||||
assert_receive {:info, {:EXIT, ^terminator, :shutdown}}
|
||||
refute_received {:terminate, _}
|
||||
end
|
||||
|
||||
test "start/2 with linked handler" do
|
||||
test "hibernates" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), :hibernate})
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
wake_up(pid)
|
||||
refute hibernating?(pid)
|
||||
|
||||
:ok = GenEvent.call(pid, ReplyHandler, :hibernate)
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
wake_up(pid)
|
||||
:ok = GenEvent.sync_notify(pid, :hibernate)
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "add_handler/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, {:error, :my_error}}) ==
|
||||
{:error, :my_error}
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, :oops}) ==
|
||||
{:error, {:bad_return_value, :oops}}
|
||||
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, [], link: true) == :ok
|
||||
assert {:error, {%RuntimeError{}, _}} =
|
||||
GenEvent.add_handler(pid, ReplyHandler, :raise)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [ReplyHandler]
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == {:error, :already_present}
|
||||
|
||||
assert GenEvent.notify(pid, {:log, 1}) == :ok
|
||||
assert GenEvent.sync_notify(pid, {:log, 2}) == :ok
|
||||
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == [1, 2]
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
|
||||
end
|
||||
|
||||
test "start/2 with linked swap" do
|
||||
test "add_mon_handler/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
parent = self()
|
||||
|
||||
{mon_pid, mon_ref} = spawn_monitor(fn ->
|
||||
assert GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false}) == :ok
|
||||
send parent, :ok
|
||||
receive after: (:infinity -> :ok)
|
||||
end)
|
||||
|
||||
assert_receive :ok
|
||||
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
|
||||
|
||||
# A regular monitor message is passed forward
|
||||
send pid, {:DOWN, make_ref(), :process, self(), :oops}
|
||||
assert_receive {:info, {:DOWN, _, :process, _, :oops}}
|
||||
|
||||
# Killing the monitor though is not passed forward
|
||||
Process.exit(mon_pid, :oops)
|
||||
assert_receive {:DOWN, ^mon_ref, :process, ^mon_pid, :oops}
|
||||
refute_received {:info, {:DOWN, _, :process, _, :oops}}
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
|
||||
end
|
||||
|
||||
test "add_mon_handler/3 with notifications" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
self = self()
|
||||
|
||||
GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
|
||||
GenEvent.remove_handler(pid, ReplyHandler, :ok)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, :normal}
|
||||
|
||||
:ok = GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
|
||||
:ok = GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, nil}}
|
||||
|
||||
:ok = GenEvent.swap_mon_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
|
||||
:ok = GenEvent.swap_mon_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, ^self}}
|
||||
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, :shutdown}
|
||||
end
|
||||
|
||||
test "remove_handler/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
|
||||
GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :ok) ==
|
||||
{:error, :not_found}
|
||||
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
|
||||
{:terminate, :ok}
|
||||
assert_receive {:terminate, :ok}
|
||||
|
||||
assert GenEvent.swap_handler(pid, LoggerHandler, [], LoggerHandler, [], link: true) == :ok
|
||||
GenEvent.add_mon_handler(pid, {ReplyHandler, self()}, {self(), false})
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
|
||||
{:error, :not_found}
|
||||
assert {:error, {%RuntimeError{}, _}} =
|
||||
GenEvent.remove_handler(pid, {ReplyHandler, self()}, :raise)
|
||||
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
end
|
||||
|
||||
test "start/2 with registered name" do
|
||||
{:ok, _} = GenEvent.start(name: :logger)
|
||||
assert GenEvent.stop(:logger) == :ok
|
||||
test "swap_handler/5" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
|
||||
{ReplyHandler, self()}, :swap) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {self(), false}) == :ok
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
|
||||
{ReplyHandler, self()}, :swap) == {:error, :already_present}
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
|
||||
|
||||
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :remove_handler) ==
|
||||
{:terminate, :remove_handler}
|
||||
|
||||
# The handler is initialized even when the module does not exist
|
||||
# on swap. However, in this case, we are returning an error on init.
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap) ==
|
||||
{:error, :not_found_on_swap}
|
||||
end
|
||||
|
||||
test "bad calls" do
|
||||
test "notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.notify(pid, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
assert GenEvent.notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.notify(pid, {:custom, :oops}) == :ok
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.notify(pid, :raise) == :ok
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "notify/2 with bad args" do
|
||||
assert GenEvent.notify({:global, :foo}, :bar) == :ok
|
||||
assert GenEvent.notify({:foo, :bar}, :bar) == :ok
|
||||
assert GenEvent.notify(self, :bar) == :ok
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
GenEvent.notify(:foo, :bar)
|
||||
end
|
||||
end
|
||||
|
||||
test "ack_notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.ack_notify(pid, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
assert GenEvent.ack_notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.ack_notify(pid, {:custom, :oops}) == :ok
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.ack_notify(pid, :raise) == :ok
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "sync_notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.sync_notify(pid, :hello) == :ok
|
||||
assert_received {:event, :hello}
|
||||
|
||||
assert GenEvent.sync_notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_received {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.sync_notify(pid, {:custom, :oops}) == :ok
|
||||
assert_received {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.sync_notify(pid, :raise) == :ok
|
||||
assert_received {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "call/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.call(pid, ReplyHandler, :hello) == :ok
|
||||
assert_receive {:call, :hello}
|
||||
|
||||
assert GenEvent.call(pid, ReplyHandler, {:custom, {:remove_handler, :ok}}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert {:error, {:bad_return_value, :oops}} =
|
||||
GenEvent.call(pid, ReplyHandler, {:custom, :oops})
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert {:error, {%RuntimeError{}, _}} = GenEvent.call(pid, ReplyHandler, :raise)
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "call/2 with bad args" do
|
||||
Logger.remove_backend(:console)
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
|
||||
|
||||
assert GenEvent.add_handler(pid, DefaultHandler, []) == :ok
|
||||
assert GenEvent.call(pid, UnknownHandler, :messages) ==
|
||||
{:error, :bad_module}
|
||||
assert GenEvent.call(pid, LoggerHandler, :whatever) ==
|
||||
{:error, {:EXIT, {:bad_call, :whatever}}}
|
||||
{:error, :not_found}
|
||||
assert GenEvent.call(pid, DefaultHandler, :whatever) ==
|
||||
{:error, {:bad_call, :whatever}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "ack stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
test "add_process_handler/2 with GenEvent" do
|
||||
{:ok, snd} = GenEvent.start_link()
|
||||
GenEvent.add_handler(snd, ReplyHandler, {self(), false})
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: :ack), 3)
|
||||
end
|
||||
{:ok, fst} = GenEvent.start_link()
|
||||
:gen.call(fst, self(), {:add_process_handler, snd, snd})
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
assert GenEvent.notify(fst, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
assert GenEvent.ack_notify(fst, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
# Receive one of the results
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid, mode: :ack), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
GenEvent.notify(pid, i)
|
||||
end
|
||||
|
||||
# The length is 4 instead of 5 because we wait for
|
||||
# message acks and not consumptions acks.
|
||||
wait_for_queue_length(pid, 4)
|
||||
assert GenEvent.sync_notify(fst, :hello) == :ok
|
||||
assert_received {:event, :hello}
|
||||
end
|
||||
|
||||
test "sync stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
test ":sys.get_status/2" do
|
||||
{:ok, pid} = GenEvent.start(name: :my_gen_event_name)
|
||||
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self(), true})
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: :sync), 3)
|
||||
end
|
||||
self = self()
|
||||
status = :sys.get_status(pid)
|
||||
GenEvent.stop(pid)
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Receive one of the results
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid, mode: :sync), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
GenEvent.notify(pid, i)
|
||||
end
|
||||
|
||||
wait_for_queue_length(pid, 5)
|
||||
assert {:status, ^pid, {:module, GenEvent},
|
||||
[pdict, _, ^pid, [], data]} = status
|
||||
assert pdict[:"$ancestors"] == [self()]
|
||||
assert pdict[:"$initial_call"] == {GenEvent, :init_it, 6}
|
||||
assert {'Installed handlers', [
|
||||
{:handler, ReplyHandler, {ReplyHandler, :ok}, ^self, nil, nil}]} = data[:items]
|
||||
end
|
||||
|
||||
test "async stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
test ":sys.get_state/1 and :sys.replace_state/2" do
|
||||
{:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
|
||||
self = self()
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid, mode: :async), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
assert [] = :sys.get_state(pid)
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: :async), 3)
|
||||
end
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self, true})
|
||||
assert [{ReplyHandler, ReplyHandler, ^self}] = :sys.get_state(pid)
|
||||
|
||||
wait_for_handlers(pid, 2)
|
||||
replacer = fn {ReplyHandler, ReplyHandler, _} -> {ReplyHandler, ReplyHandler, :unknown} end
|
||||
:sys.replace_state(pid, replacer)
|
||||
assert [{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
# Fail while replacing does not cause a crash
|
||||
:sys.replace_state(pid, fn _ -> exit(:fail) end)
|
||||
assert [{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
|
||||
|
||||
# Receive one of the results
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self, true})
|
||||
assert [{ReplyHandler, {ReplyHandler, :ok}, ^self},
|
||||
{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
|
||||
|
||||
# One of the subscriptions are gone
|
||||
wait_for_handlers(pid, 1)
|
||||
:ok = :sys.suspend(pid)
|
||||
assert [{ReplyHandler, {ReplyHandler, :ok}, ^self},
|
||||
{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
|
||||
:ok = :sys.resume(pid)
|
||||
end
|
||||
|
||||
Enum.each [:sync, :async], fn mode ->
|
||||
test "#{mode} stream/2 with parallel use (and first finishing first)" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, duration: 200, mode: unquote(mode))
|
||||
defp hibernating?(pid) do
|
||||
Process.info(pid, :current_function) ==
|
||||
{:current_function,{:erlang,:hibernate,3}}
|
||||
end
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:take, Enum.take(stream, 3)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
spawn_link fn -> send parent, {:to_list, Enum.to_list(stream)} end
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
# Notify the events for both handlers
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
assert_receive {:take, [1, 2, 3]}, @receive_timeout
|
||||
|
||||
# Notify the events for to_list stream handler
|
||||
for i <- 4..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive {:to_list, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with timeout" do
|
||||
# Start a manager
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
pid = spawn_link fn ->
|
||||
Enum.take(GenEvent.stream(pid, timeout: 50, mode: unquote(mode)), 5)
|
||||
end
|
||||
|
||||
assert_receive {:EXIT, ^pid,
|
||||
{:timeout, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with error/timeout on subscription" do
|
||||
# Start a manager
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
# Start a subscriber with timeout
|
||||
child = spawn fn -> Enum.to_list(GenEvent.stream(pid, mode: unquote(mode))) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Kill and wait until we have 0 handlers
|
||||
Process.exit(child, :kill)
|
||||
wait_for_handlers(pid, 0)
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager stop" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: unquote(mode)), 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:shutdown, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with cancel streams" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref(), mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, Enum.take(stream, 5) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
GenEvent.cancel_streams(stream)
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with swap_handler" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref(), mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn -> send parent, Enum.take(stream, 5) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
[handler] = GenEvent.which_handlers(pid)
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.swap_handler(pid, handler, :swap_handler, LogHandler, [])
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{{:swapped, LogHandler, _},
|
||||
{Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with duration" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, duration: 200, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:duration, Enum.take(stream, 10)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Wait until the handler is gone
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# The stream is not complete but terminated anyway due to duration
|
||||
assert_receive {:duration, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager killed and trap_exit" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
Process.exit(pid, :kill)
|
||||
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:killed, {Enumerable.GenEvent.Stream, :next, [_,_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager not alive" do
|
||||
# Start a manager and subscribers
|
||||
stream = GenEvent.stream(:does_not_exit, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:noproc, {Enumerable.GenEvent.Stream, :start, [_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager unregistered" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link(name: :unreg)
|
||||
stream = GenEvent.stream(:unreg, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(stream, 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Unregister the process
|
||||
Process.unregister(:unreg)
|
||||
|
||||
# Notify the remaining events
|
||||
for i <- 4..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# We should have gotten the message and all handlers were removed
|
||||
assert_receive [1, 2, 3, 4, 5], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 flushes events on abort" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
spawn_link fn ->
|
||||
wait_for_handlers(pid, 2)
|
||||
GenEvent.notify(pid, 1)
|
||||
GenEvent.notify(pid, 2)
|
||||
GenEvent.notify(pid, 3)
|
||||
end
|
||||
|
||||
GenEvent.add_handler(pid, SlowHandler, [])
|
||||
stream = GenEvent.stream(pid, mode: unquote(mode))
|
||||
|
||||
try do
|
||||
Enum.each stream, fn _ -> throw :done end
|
||||
catch
|
||||
:done -> :ok
|
||||
end
|
||||
|
||||
# Wait for the slow handler to be removed
|
||||
# so all events have been handled
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# Check no messages leaked.
|
||||
refute_received _any
|
||||
defp wait_until(fun, counter \\ 0) do
|
||||
cond do
|
||||
counter > 100 ->
|
||||
flunk "Waited for 1s, but #{inspect fun} never returned true"
|
||||
fun.() ->
|
||||
true
|
||||
true ->
|
||||
receive after: (10 -> wait_until(fun, counter + 1))
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_handlers(pid, count) do
|
||||
unless length(GenEvent.which_handlers(pid)) == count do
|
||||
wait_for_handlers(pid, count)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_queue_length(pid, count) do
|
||||
{:message_queue_len, n} = Process.info(pid, :message_queue_len)
|
||||
unless n == count do
|
||||
wait_for_queue_length(pid, count)
|
||||
end
|
||||
defp wake_up(pid) do
|
||||
send pid, :wake
|
||||
assert_receive {:info, :wake}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,6 +49,12 @@ defmodule GenServerTest do
|
||||
assert GenServer.cast(pid, {:push, :world}) == :ok
|
||||
assert GenServer.call(pid, :pop) == :world
|
||||
assert GenServer.call(pid, :stop) == :ok
|
||||
|
||||
assert GenServer.cast({:global, :foo}, {:push, :world}) == :ok
|
||||
assert GenServer.cast({:via, :foo, :bar}, {:push, :world}) == :ok
|
||||
assert_raise ArgumentError, fn ->
|
||||
GenServer.cast(:foo, {:push, :world})
|
||||
end
|
||||
end
|
||||
|
||||
test "nil name" do
|
||||
|
||||
@@ -41,8 +41,14 @@ defmodule Inspect.AtomTest do
|
||||
|
||||
test :op do
|
||||
assert inspect(:+) == ":+"
|
||||
assert inspect(:<~) == ":<~"
|
||||
assert inspect(:~>) == ":~>"
|
||||
assert inspect(:&&&) == ":&&&"
|
||||
assert inspect(:~~~) == ":~~~"
|
||||
assert inspect(:<<~) == ":<<~"
|
||||
assert inspect(:~>>) == ":~>>"
|
||||
assert inspect(:<~>) == ":<~>"
|
||||
assert inspect(:<|>) == ":<|>"
|
||||
end
|
||||
|
||||
test :... do
|
||||
@@ -293,6 +299,8 @@ defmodule Inspect.MapTest do
|
||||
assert_raise ArgumentError, msg, fn ->
|
||||
inspect(%Failing{})
|
||||
end
|
||||
|
||||
assert [{Inspect.Inspect.MapTest.Failing, :inspect, 2, _}|_] = System.stacktrace
|
||||
end
|
||||
|
||||
test :exception do
|
||||
|
||||
@@ -5,23 +5,23 @@ defmodule IntegerTest do
|
||||
require Integer
|
||||
|
||||
test :odd? do
|
||||
assert Integer.odd?(0) == false
|
||||
assert Integer.odd?(1) == true
|
||||
assert Integer.odd?(2) == false
|
||||
assert Integer.odd?(3) == true
|
||||
assert Integer.odd?(-1) == true
|
||||
assert Integer.odd?(-2) == false
|
||||
assert Integer.odd?(-3) == true
|
||||
assert Integer.is_odd(0) == false
|
||||
assert Integer.is_odd(1) == true
|
||||
assert Integer.is_odd(2) == false
|
||||
assert Integer.is_odd(3) == true
|
||||
assert Integer.is_odd(-1) == true
|
||||
assert Integer.is_odd(-2) == false
|
||||
assert Integer.is_odd(-3) == true
|
||||
end
|
||||
|
||||
test :even? do
|
||||
assert Integer.even?(0) == true
|
||||
assert Integer.even?(1) == false
|
||||
assert Integer.even?(2) == true
|
||||
assert Integer.even?(3) == false
|
||||
assert Integer.even?(-1) == false
|
||||
assert Integer.even?(-2) == true
|
||||
assert Integer.even?(-3) == false
|
||||
assert Integer.is_even(0) == true
|
||||
assert Integer.is_even(1) == false
|
||||
assert Integer.is_even(2) == true
|
||||
assert Integer.is_even(3) == false
|
||||
assert Integer.is_even(-1) == false
|
||||
assert Integer.is_even(-2) == true
|
||||
assert Integer.is_even(-3) == false
|
||||
end
|
||||
|
||||
test :parse do
|
||||
|
||||
@@ -14,24 +14,24 @@ defmodule IO.ANSI.DocsTest do
|
||||
|
||||
test "heading is formatted" do
|
||||
result = format_heading("wibble")
|
||||
assert String.starts_with?(result, "\e[0m\n\e[7m\e[33m\e[1m")
|
||||
assert String.starts_with?(result, "\e[0m\n\e[7m\e[33m")
|
||||
assert String.ends_with?(result, "\e[0m\n\e[0m")
|
||||
assert String.contains?(result, " wibble ")
|
||||
end
|
||||
|
||||
test "first level heading is converted" do
|
||||
result = format("# wibble\n\ntext\n")
|
||||
assert result == "\e[33m\e[1mWIBBLE\e[0m\n\e[0m\ntext\n\e[0m"
|
||||
assert result == "\e[33mWIBBLE\e[0m\n\e[0m\ntext\n\e[0m"
|
||||
end
|
||||
|
||||
test "second level heading is converted" do
|
||||
result = format("## wibble\n\ntext\n")
|
||||
assert result == "\e[33m\e[1mwibble\e[0m\n\e[0m\ntext\n\e[0m"
|
||||
assert result == "\e[33mwibble\e[0m\n\e[0m\ntext\n\e[0m"
|
||||
end
|
||||
|
||||
test "third level heading is converted" do
|
||||
result = format("## wibble\n\ntext\n")
|
||||
assert result == "\e[33m\e[1mwibble\e[0m\n\e[0m\ntext\n\e[0m"
|
||||
assert result == "\e[33mwibble\e[0m\n\e[0m\ntext\n\e[0m"
|
||||
end
|
||||
|
||||
test "code block is converted" do
|
||||
@@ -103,6 +103,9 @@ defmodule IO.ANSI.DocsTest do
|
||||
result = format("*world*")
|
||||
assert result == "\e[1mworld\e[0m\n\e[0m"
|
||||
|
||||
result = format("*world*.")
|
||||
assert result == "\e[1mworld\e[0m.\n\e[0m"
|
||||
|
||||
result = format("**world**")
|
||||
assert result == "\e[1mworld\e[0m\n\e[0m"
|
||||
|
||||
@@ -127,6 +130,28 @@ defmodule IO.ANSI.DocsTest do
|
||||
assert result == "\e[36mhello world\e[0m\n\e[0m"
|
||||
end
|
||||
|
||||
test "multiple stars/underscores/backticks work" do
|
||||
result = format("*hello world* *hello world*")
|
||||
assert result == "\e[1mhello world\e[0m \e[1mhello world\e[0m\n\e[0m"
|
||||
|
||||
result = format("_hello world_ _hello world_")
|
||||
assert result == "\e[4mhello world\e[0m \e[4mhello world\e[0m\n\e[0m"
|
||||
|
||||
result = format("`hello world` `hello world`")
|
||||
assert result == "\e[36mhello world\e[0m \e[36mhello world\e[0m\n\e[0m"
|
||||
end
|
||||
|
||||
test "multiple stars/underscores/backticks work when separated by other words" do
|
||||
result = format("*hello world* unit test *hello world*")
|
||||
assert result == "\e[1mhello world\e[0m unit test \e[1mhello world\e[0m\n\e[0m"
|
||||
|
||||
result = format("_hello world_ unit test _hello world_")
|
||||
assert result == "\e[4mhello world\e[0m unit test \e[4mhello world\e[0m\n\e[0m"
|
||||
|
||||
result = format("`hello world` unit test `hello world`")
|
||||
assert result == "\e[36mhello world\e[0m unit test \e[36mhello world\e[0m\n\e[0m"
|
||||
end
|
||||
|
||||
test "star/underscore preceeded by space doesn't get interpreted" do
|
||||
result = format("_unit _size")
|
||||
assert result == "_unit _size\n\e[0m"
|
||||
@@ -138,6 +163,43 @@ defmodule IO.ANSI.DocsTest do
|
||||
assert result == "*unit *size\n\e[0m"
|
||||
end
|
||||
|
||||
test "star/underscore/backtick preceeded by non-space delimiters gets interpreted" do
|
||||
result = format("(`hello world`)")
|
||||
assert result == "(\e[36mhello world\e[0m)\n\e[0m"
|
||||
result = format("<`hello world`>")
|
||||
assert result == "<\e[36mhello world\e[0m>\n\e[0m"
|
||||
|
||||
result = format("(*hello world*)")
|
||||
assert result == "(\e[1mhello world\e[0m)\n\e[0m"
|
||||
result = format("@*hello world*@")
|
||||
assert result == "@\e[1mhello world\e[0m@\n\e[0m"
|
||||
|
||||
result = format("(_hello world_)")
|
||||
assert result == "(\e[4mhello world\e[0m)\n\e[0m"
|
||||
result = format("'_hello world_'")
|
||||
assert result == "'\e[4mhello world\e[0m'\n\e[0m"
|
||||
end
|
||||
|
||||
test "star/underscore/backtick starts/ends within a word doesn't get interpreted" do
|
||||
result = format("foo_bar, foo_bar_baz!")
|
||||
assert result == "foo_bar, foo_bar_baz!\n\e[0m"
|
||||
|
||||
result = format("_foo_bar")
|
||||
assert result == "_foo_bar\n\e[0m"
|
||||
|
||||
result = format("foo_bar_")
|
||||
assert result == "foo_bar_\n\e[0m"
|
||||
|
||||
result = format("foo*bar, foo*bar*baz!")
|
||||
assert result == "foo*bar, foo*bar*baz!\n\e[0m"
|
||||
|
||||
result = format("*foo*bar")
|
||||
assert result == "*foo*bar\n\e[0m"
|
||||
|
||||
result = format("foo*bar*")
|
||||
assert result == "foo*bar*\n\e[0m"
|
||||
end
|
||||
|
||||
test "backtick preceeded by space gets interpreted" do
|
||||
result = format("`unit `size")
|
||||
assert result == "\e[36munit \e[0msize\n\e[0m"
|
||||
@@ -200,11 +262,6 @@ defmodule IO.ANSI.DocsTest do
|
||||
assert result == "\e[36m__world__\e[0m\n\e[0m"
|
||||
end
|
||||
|
||||
test "backtick works inside parenthesis" do
|
||||
result = format("(`hello world`)")
|
||||
assert result == "(\e[36mhello world\e[0m)\n\e[0m"
|
||||
end
|
||||
|
||||
test "escaping of underlines within links" do
|
||||
result = format("(http://en.wikipedia.org/wiki/ANSI_escape_code)")
|
||||
assert result == "(http://en.wikipedia.org/wiki/ANSI_escape_code)\n\e[0m"
|
||||
|
||||
@@ -22,6 +22,12 @@ defmodule IOTest do
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :binread_all do
|
||||
{:ok, file} = File.open(Path.expand('fixtures/file.bin', __DIR__))
|
||||
assert "LF\nCR\rCRLF\r\nLFCR\n\r" == IO.binread(file, :all)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :getn do
|
||||
{:ok, file} = File.open(Path.expand('fixtures/file.txt', __DIR__))
|
||||
assert "F" == IO.getn(file, "")
|
||||
|
||||
@@ -133,7 +133,7 @@ defmodule Kernel.ComprehensionTest do
|
||||
test "for comprehension with into, generators and filters" do
|
||||
Process.put(:into_cont, [])
|
||||
|
||||
for x <- 1..3, Integer.odd?(x), << y <- "hello" >>, into: %PDict{} do
|
||||
for x <- 1..3, Integer.is_odd(x), << y <- "hello" >>, into: %PDict{} do
|
||||
x + y
|
||||
end
|
||||
|
||||
@@ -151,6 +151,12 @@ defmodule Kernel.ComprehensionTest do
|
||||
assert for({_,x} <- [1, 2, a: 3, b: 4, c: 5], do: x * 2) == [6, 8, 10]
|
||||
end
|
||||
|
||||
test "list for comprehension matched to '_' on last line of block" do
|
||||
assert (if true do
|
||||
_ = for x <- [1, 2, 3], do: x * 2
|
||||
end) == [2, 4, 6]
|
||||
end
|
||||
|
||||
test "list for comprehensions with filters" do
|
||||
assert for(x <- [1, 2, 3], x > 1, x < 3, do: x * 2) == [4]
|
||||
end
|
||||
|
||||
@@ -20,6 +20,10 @@ defmodule Kernel.ErrorsTest do
|
||||
"nofile:1: syntax error before: \"world\"",
|
||||
'"hello" "world"'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: Foobar",
|
||||
'1 Foobar'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: foo",
|
||||
'Foo.:foo'
|
||||
@@ -85,6 +89,12 @@ defmodule Kernel.ErrorsTest do
|
||||
'+.foo'
|
||||
end
|
||||
|
||||
test :syntax_error_before_sigil do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: sigil ~s with content 'bar baz'",
|
||||
'~s(foo) ~s(bar baz)'
|
||||
end
|
||||
|
||||
test :compile_error_on_op_ambiguity do
|
||||
msg = "nofile:1: \"a -1\" looks like a function call but there is a variable named \"a\", " <>
|
||||
"please use explicit parenthesis or even spaces"
|
||||
@@ -150,6 +160,15 @@ defmodule Kernel.ErrorsTest do
|
||||
def hello(arg), do: arg
|
||||
end
|
||||
'''
|
||||
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: function foo/0 undefined",
|
||||
~C'''
|
||||
defmodule ErrorsTest do
|
||||
def hello(foo, bar \\ foo)
|
||||
def hello(foo, bar), do: foo + bar
|
||||
end
|
||||
'''
|
||||
end
|
||||
|
||||
test :invalid_match_pattern do
|
||||
@@ -218,6 +237,10 @@ defmodule Kernel.ErrorsTest do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: '{'",
|
||||
'%{:a, :b}{a: :b}'
|
||||
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: expected key-value pairs in a map, got: put_in(foo.bar().baz(), nil)",
|
||||
'foo = 1; %{put_in(foo.bar.baz, nil), :bar}'
|
||||
end
|
||||
|
||||
test :struct_fields_on_defstruct do
|
||||
@@ -232,8 +255,8 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :struct_access_on_body do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:3: cannot access struct TZ in the same context that defines it " <>
|
||||
"as the struct fields are not yet accessible",
|
||||
"nofile:3: cannot access struct TZ, the struct was not yet defined or the struct " <>
|
||||
"is being accessed in the same context that defines it",
|
||||
'''
|
||||
defmodule TZ do
|
||||
defstruct %{name: "Brasilia"}
|
||||
@@ -290,10 +313,34 @@ defmodule Kernel.ErrorsTest do
|
||||
'unquote 1'
|
||||
end
|
||||
|
||||
test :invalid_unquote_splicing_in_oneliners do
|
||||
assert_compile_fail ArgumentError,
|
||||
"unquote_splicing only works inside arguments and block contexts, " <>
|
||||
"wrap it in parens if you want it to work with one-liners",
|
||||
'''
|
||||
defmodule Foo do
|
||||
defmacro oneliner2 do
|
||||
quote do: unquote_splicing 1
|
||||
end
|
||||
|
||||
def callme do
|
||||
oneliner2
|
||||
end
|
||||
end
|
||||
'''
|
||||
end
|
||||
|
||||
test :invalid_quote_args do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid arguments for quote",
|
||||
'quote 1'
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid options for quote, expected a keyword list",
|
||||
'''
|
||||
quote :foo do
|
||||
foo
|
||||
end
|
||||
'''
|
||||
end
|
||||
|
||||
test :invalid_calls do
|
||||
@@ -340,6 +387,12 @@ defmodule Kernel.ErrorsTest do
|
||||
'fn 1'
|
||||
end
|
||||
|
||||
test :invalid_escape do
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:1: invalid escape \\ at end of file",
|
||||
'1 \\'
|
||||
end
|
||||
|
||||
test :function_local_conflict do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: imported Kernel.&&/2 conflicts with local function",
|
||||
@@ -590,9 +643,47 @@ defmodule Kernel.ErrorsTest do
|
||||
'fn x -> x; x, y -> x + y end'
|
||||
end
|
||||
|
||||
test :end_of_expression do
|
||||
# All valid examples
|
||||
Code.eval_quoted '''
|
||||
1;
|
||||
2;
|
||||
3
|
||||
|
||||
(;)
|
||||
(;1)
|
||||
(1;)
|
||||
(1; 2)
|
||||
|
||||
fn -> 1; 2 end
|
||||
fn -> ; end
|
||||
|
||||
if true do
|
||||
;
|
||||
end
|
||||
|
||||
try do
|
||||
;
|
||||
catch
|
||||
_, _ -> ;
|
||||
after
|
||||
;
|
||||
end
|
||||
'''
|
||||
|
||||
# All invalid examples
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: ';'",
|
||||
'1+;\n2'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: ';'",
|
||||
'max(1,;2)'
|
||||
end
|
||||
|
||||
test :new_line_error do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:3: syntax error before: newline",
|
||||
"nofile:3: syntax error before: eol",
|
||||
'if true do\n foo = [],\n baz\nend'
|
||||
end
|
||||
|
||||
@@ -665,8 +756,13 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
'''
|
||||
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: spec for undefined function ErrorsTest.omg/0",
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
message = "nofile:2: spec for undefined function omg/0"
|
||||
else
|
||||
message = "nofile:2: spec for undefined function ErrorsTest.omg/0"
|
||||
end
|
||||
|
||||
assert_compile_fail CompileError, message,
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
@spec omg :: atom
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user