Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
+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
|
||||
|
||||
@@ -1,5 +1,64 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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,5 @@
|
||||
REBAR := rebar
|
||||
DOCS := v1.0
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -125,16 +126,16 @@ 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
|
||||
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 "$(call SOURCE_REF)" -o doc/$(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."
|
||||
@@ -145,9 +146,18 @@ release_zip: compile
|
||||
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
|
||||
|
||||
|
||||
+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
|
||||
|
||||
11. Create a new branch "vMAJOR.MINOR"
|
||||
|
||||
12. Move docs generation to `docs/vOLD-MAJOR.OLD-MINOR` and copy them to `docs/stable`
|
||||
|
||||
13. Bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
||||
|
||||
14. `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
|
||||
|
||||
@@ -99,10 +99,7 @@ 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)
|
||||
tmp <> String.Chars.to_string(unquote(expr))
|
||||
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
|
||||
|
||||
@@ -260,7 +260,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 +325,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 +364,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 """
|
||||
|
||||
+13
-4
@@ -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)
|
||||
@@ -1729,10 +1738,10 @@ defmodule Enum do
|
||||
def take(collection, count) when count > 0 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)
|
||||
|
||||
+15
-18
@@ -188,7 +188,7 @@ defmodule GenEvent do
|
||||
@type manager :: pid | name | {atom, node}
|
||||
|
||||
@typedoc "Supported values for new handlers"
|
||||
@type handler :: atom | {atom, term} | pid
|
||||
@type handler :: atom | {atom, term} | {pid, reference}
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -601,11 +601,7 @@ defmodule GenEvent do
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
{_from, tag, {:add_handler, handler, args}} ->
|
||||
{hib, reply, handlers} = server_add_handler(handler, args, handlers, nil)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
{_from, tag, {:add_handler, handler, args, notify}} ->
|
||||
{hib, reply, handlers} = server_add_handler(handler, args, handlers, notify)
|
||||
{hib, reply, handlers} = server_add_handler(handler, args, handlers)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
{_from, tag, {:add_mon_handler, handler, args, notify}} ->
|
||||
@@ -739,13 +735,13 @@ defmodule GenEvent do
|
||||
IO.puts dev, "*DBG* #{inspect name}: #{inspect dbg}"
|
||||
end
|
||||
|
||||
defp server_add_handler({module, id}, args, handlers, notify) do
|
||||
handler = handler(module: module, id: {module, id}, pid: notify)
|
||||
defp server_add_handler({module, id}, args, handlers) do
|
||||
handler = handler(module: module, id: {module, id})
|
||||
do_add_handler(module, handler, args, handlers, :ok)
|
||||
end
|
||||
|
||||
defp server_add_handler(module, args, handlers, notify) do
|
||||
handler = handler(module: module, id: module, pid: notify)
|
||||
defp server_add_handler(module, args, handlers) do
|
||||
handler = handler(module: module, id: module)
|
||||
do_add_handler(module, handler, args, handlers, :ok)
|
||||
end
|
||||
|
||||
@@ -763,7 +759,7 @@ defmodule GenEvent do
|
||||
|
||||
defp server_add_process_handler(pid, handlers, notify) do
|
||||
ref = Process.monitor(pid)
|
||||
handler = handler(module: GenEvent.Stream, id: pid,
|
||||
handler = handler(module: GenEvent.Stream, id: {self(), ref},
|
||||
pid: notify, ref: ref)
|
||||
do_add_handler(GenEvent.Stream, handler, {pid, ref}, handlers, {self(), ref})
|
||||
end
|
||||
@@ -779,7 +775,7 @@ defmodule GenEvent do
|
||||
if sup do
|
||||
server_add_mon_handler(module2, {args2, state}, handlers, sup)
|
||||
else
|
||||
server_add_handler(module2, {args2, state}, handlers, nil)
|
||||
server_add_handler(module2, {args2, state}, handlers)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -796,7 +792,7 @@ defmodule GenEvent do
|
||||
|
||||
defp server_split_process_handlers(mode, event, [handler|t], handlers, streams) do
|
||||
case handler(handler, :id) do
|
||||
pid when is_pid(pid) ->
|
||||
{pid, _ref} when is_pid(pid) ->
|
||||
server_process_notify(mode, event, handler)
|
||||
server_split_process_handlers(mode, event, t, handlers, [handler|streams])
|
||||
_ ->
|
||||
@@ -858,16 +854,17 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
defp server_collect_process_handlers(mode, event, [handler|t], handlers, name) when mode in [:sync, :ack] do
|
||||
handler(ref: ref) = handler
|
||||
handler(ref: ref, id: id) = handler
|
||||
|
||||
receive do
|
||||
{^ref, :ok} ->
|
||||
server_collect_process_handlers(mode, event, t, [handler|handlers], name)
|
||||
{^ref, :done} ->
|
||||
do_terminate(handler, :remove_handler, event, name, :normal)
|
||||
{_from, tag, {:delete_handler, ^id, args}} ->
|
||||
do_terminate(handler, args, :remove, name, :normal)
|
||||
reply(tag, :ok)
|
||||
server_collect_process_handlers(mode, event, t, handlers, name)
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
do_terminate(handler, {:stop, reason}, :remove, name, :shutdown)
|
||||
do_terminate(handler, {:stop, reason}, :DOWN, name, :shutdown)
|
||||
server_collect_process_handlers(mode, event, t, handlers, name)
|
||||
end
|
||||
end
|
||||
@@ -939,7 +936,7 @@ defmodule GenEvent do
|
||||
case :lists.keyfind(ref, handler(:ref) + 1, handlers) do
|
||||
false -> :error
|
||||
handler ->
|
||||
do_terminate(handler, {:stop, reason}, :remove, name, :shutdown)
|
||||
do_terminate(handler, {:stop, reason}, :DOWN, name, :shutdown)
|
||||
{:ok, :lists.keydelete(ref, handler(:ref) + 1, handlers)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -80,7 +80,8 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
|
||||
defp start(%{manager: manager} = stream) do
|
||||
try do
|
||||
{:ok, {pid, ref}} = :gen.call(manager, self(), {:add_process_handler, self(), self()}, :infinity)
|
||||
{:ok, {pid, ref}} = :gen.call(manager, self(),
|
||||
{:add_process_handler, self(), self()}, :infinity)
|
||||
mon_ref = Process.monitor(pid)
|
||||
{pid, ref, mon_ref}
|
||||
catch
|
||||
@@ -92,19 +93,6 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
self = self()
|
||||
|
||||
receive do
|
||||
# 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, ^self, _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}}
|
||||
|
||||
# Got an async event.
|
||||
{_from, {^pid, ^ref}, {:notify, event}} ->
|
||||
{[{:async, pid, ref, event}], acc}
|
||||
@@ -116,6 +104,19 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
# 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]}})
|
||||
@@ -135,26 +136,20 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
|
||||
# If we reach this branch, the handler was not removed yet,
|
||||
# so we trigger a request for doing so.
|
||||
defp stop(stream, {pid, _, _} = acc) do
|
||||
parent = self()
|
||||
_ = Task.start(fn -> GenEvent.remove_handler(pid, parent, :shutdown) end)
|
||||
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
|
||||
self = self()
|
||||
|
||||
receive do
|
||||
{_from, {^pid, ^ref}, {notify, _event}} when notify in [:ack_notify, :sync_notify] ->
|
||||
send pid, {ref, :done}
|
||||
wait_for_handler_removal(pid, ref, mon_ref)
|
||||
{:gen_event_EXIT, ^self, reason}
|
||||
{: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, ^self, reason} ->
|
||||
{:gen_event_EXIT, {^pid, ^ref}, reason} ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
{:error, reason}
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
@@ -164,7 +159,7 @@ defimpl Enumerable, for: GenEvent.Stream do
|
||||
|
||||
defp flush_events(ref) do
|
||||
receive do
|
||||
{_from, {_pid, ^ref}, {:notify, _event}} ->
|
||||
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
|
||||
flush_events(ref)
|
||||
after
|
||||
0 -> :ok
|
||||
|
||||
@@ -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,7 +438,7 @@ 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
|
||||
|
||||
@@ -180,7 +180,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1813,7 +1813,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 +1836,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
|
||||
@@ -2057,7 +2059,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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
else
|
||||
:elixir_compiler.file(h, Keyword.get(options, :dest))
|
||||
end
|
||||
{:compiled, h}
|
||||
{:shutdown, h}
|
||||
catch
|
||||
kind, reason ->
|
||||
{:failure, kind, reason, System.stacktrace}
|
||||
@@ -166,7 +166,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
|
||||
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
|
||||
{:DOWN, _down_ref, :process, down_pid, {:shutdown, file}} ->
|
||||
if callback = Keyword.get(options, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
@@ -187,9 +187,15 @@ defmodule Kernel.ParallelCompiler do
|
||||
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
|
||||
@@ -201,7 +207,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp print_failure(_file, {:compiled, _}) do
|
||||
defp print_failure(_file, {:shutdown, _}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -215,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)
|
||||
|
||||
@@ -833,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 ->
|
||||
|
||||
+28
-35
@@ -504,19 +504,19 @@ 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),
|
||||
@@ -612,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
|
||||
|
||||
@@ -632,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
|
||||
@@ -652,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 """
|
||||
@@ -671,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 """
|
||||
@@ -684,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
|
||||
@@ -692,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 """
|
||||
@@ -731,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
|
||||
@@ -832,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
|
||||
|
||||
@@ -901,7 +900,7 @@ defmodule Module do
|
||||
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) ->
|
||||
@@ -956,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
|
||||
@@ -972,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
|
||||
|
||||
|
||||
+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
|
||||
|
||||
@@ -419,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
|
||||
@@ -479,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) ->
|
||||
@@ -550,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))
|
||||
|
||||
+31
-26
@@ -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 is_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
|
||||
@@ -1127,12 +1131,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]}
|
||||
@@ -1171,19 +1175,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
|
||||
|
||||
+12
-11
@@ -74,21 +74,21 @@ defmodule String do
|
||||
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 ->
|
||||
@@ -103,7 +103,7 @@ defmodule String do
|
||||
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)
|
||||
@@ -112,7 +112,7 @@ defmodule String do
|
||||
"John"
|
||||
|
||||
Or simply used pattern matching:
|
||||
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> <<_ :: binary-size(base), rest :: binary>> = full
|
||||
@@ -650,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
|
||||
@@ -672,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
|
||||
|
||||
+13
-10
@@ -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 """
|
||||
@@ -224,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 """
|
||||
@@ -431,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
|
||||
@@ -463,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)
|
||||
|
||||
|
||||
+34
-20
@@ -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 """
|
||||
@@ -274,12 +271,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 +290,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
|
||||
|
||||
@@ -42,10 +42,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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%% 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
|
||||
@@ -78,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
|
||||
@@ -92,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),
|
||||
@@ -142,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).
|
||||
|
||||
@@ -177,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.
|
||||
@@ -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.
|
||||
@@ -98,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),
|
||||
@@ -166,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
|
||||
@@ -229,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),
|
||||
@@ -327,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
|
||||
|
||||
@@ -355,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} -> [];
|
||||
[] -> [];
|
||||
_ ->
|
||||
@@ -379,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}})
|
||||
|
||||
@@ -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 ->
|
||||
@@ -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;
|
||||
|
||||
@@ -55,7 +55,16 @@ 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
|
||||
%% 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 +136,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 +152,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),
|
||||
|
||||
@@ -474,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
|
||||
|
||||
@@ -489,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).
|
||||
@@ -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});
|
||||
_ ->
|
||||
|
||||
@@ -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, []},
|
||||
|
||||
@@ -432,6 +432,23 @@ defmodule GenEventTest do
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "add_process_handler/2 with GenEvent" do
|
||||
{:ok, snd} = GenEvent.start_link()
|
||||
GenEvent.add_handler(snd, ReplyHandler, {self(), false})
|
||||
|
||||
{:ok, fst} = GenEvent.start_link()
|
||||
:gen.call(fst, self(), {:add_process_handler, snd, snd})
|
||||
|
||||
assert GenEvent.notify(fst, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
assert GenEvent.ack_notify(fst, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
assert GenEvent.sync_notify(fst, :hello) == :ok
|
||||
assert_received {:event, :hello}
|
||||
end
|
||||
|
||||
test ":sys.get_status/2" do
|
||||
{:ok, pid} = GenEvent.start(name: :my_gen_event_name)
|
||||
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self(), true})
|
||||
|
||||
@@ -299,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
|
||||
|
||||
@@ -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
|
||||
@@ -127,6 +127,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 +160,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 +259,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"
|
||||
|
||||
@@ -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'
|
||||
@@ -227,6 +231,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
|
||||
@@ -320,6 +328,13 @@ defmodule Kernel.ErrorsTest 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
|
||||
|
||||
@@ -392,11 +392,6 @@ defmodule Kernel.ExpansionTest do
|
||||
|
||||
## Try
|
||||
|
||||
test "try: expands do" do
|
||||
assert expand(quote do: (try do x = y end; x)) ==
|
||||
quote do: (try do x = y() end; x())
|
||||
end
|
||||
|
||||
test "try: expands catch" do
|
||||
assert expand(quote do: (try do x catch x, y -> z = :erlang.+(x, y) end; z)) ==
|
||||
quote do: (try do x() catch x, y -> z = :erlang.+(x, y) end; z())
|
||||
@@ -417,6 +412,12 @@ defmodule Kernel.ExpansionTest do
|
||||
quote do: (try do x() rescue unquote(:in)(x, _) -> x; unquote(:in)(_, [:"Elixir.Error"]) -> x() end; x())
|
||||
end
|
||||
|
||||
test "try: expects more than do" do
|
||||
assert_raise CompileError, ~r"missing catch/rescue/after/else keyword in try", fn ->
|
||||
expand(quote do: (try do x = y end; x))
|
||||
end
|
||||
end
|
||||
|
||||
## Binaries
|
||||
|
||||
test "bitstrings: size * unit" do
|
||||
|
||||
@@ -375,6 +375,20 @@ defmodule Kernel.WarningTest do
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :badarg_warning do
|
||||
assert capture_err(fn ->
|
||||
assert_raise ArgumentError, fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
Atom.to_string "abc"
|
||||
end
|
||||
"""
|
||||
end
|
||||
end) =~ "warning: this expression will fail with ArgumentError"
|
||||
after
|
||||
purge [Sample]
|
||||
end
|
||||
|
||||
test :undefined_function_for_behaviour do
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string """
|
||||
|
||||
@@ -189,6 +189,10 @@ defmodule RegexTest do
|
||||
assert Regex.replace(~r(b), "abc", "d") == "adc"
|
||||
assert Regex.replace(~r(b), "abc", "[\\0]") == "a[b]c"
|
||||
assert Regex.replace(~r[(b)], "abc", "[\\1]") == "a[b]c"
|
||||
assert Regex.replace(~r[(b)], "abc", "[\\2]") == "a[]c"
|
||||
assert Regex.replace(~r[(b)], "abc", "[\\3]") == "a[]c"
|
||||
assert Regex.replace(~r(b), "abc", "[\\g{0}]") == "a[b]c"
|
||||
assert Regex.replace(~r[(b)], "abc", "[\\g{1}]") == "a[b]c"
|
||||
|
||||
assert Regex.replace(~r(b), "abcbe", "d") == "adcde"
|
||||
assert Regex.replace(~r(b), "abcbe", "d", global: false) == "adcbe"
|
||||
|
||||
@@ -67,6 +67,19 @@ defmodule StreamTest do
|
||||
assert Enum.zip(list, list) == Enum.zip(stream, stream)
|
||||
end
|
||||
|
||||
test "chunk/4 is haltable" do
|
||||
assert 1..10 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Enum.to_list ==
|
||||
[[1, 2, 3, 4], [5, 6, 7, 8]]
|
||||
assert 1..10 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Stream.take(3) |> Enum.to_list ==
|
||||
[[1, 2, 3, 4], [5, 6, 7, 8]]
|
||||
assert 1..10 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Stream.take(2) |> Enum.to_list ==
|
||||
[[1, 2, 3, 4], [5, 6, 7, 8]]
|
||||
assert 1..10 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Stream.take(1) |> Enum.to_list ==
|
||||
[[1, 2, 3, 4]]
|
||||
assert 1..6 |> Stream.take(6) |> Stream.chunk(4, 4, [7, 8]) |> Enum.to_list ==
|
||||
[[1, 2, 3, 4], [5, 6, 7, 8]]
|
||||
end
|
||||
|
||||
test "chunk_by/2" do
|
||||
stream = Stream.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1))
|
||||
|
||||
@@ -75,6 +88,8 @@ defmodule StreamTest do
|
||||
[[1], [2, 2], [3], [4, 4, 6], [7, 7]]
|
||||
assert stream |> Stream.take(3) |> Enum.to_list ==
|
||||
[[1], [2, 2], [3]]
|
||||
assert 1..10 |> Stream.chunk(2) |> Enum.take(2) ==
|
||||
[[1, 2], [3, 4]]
|
||||
end
|
||||
|
||||
test "chunk_by/2 is zippable" do
|
||||
|
||||
@@ -192,6 +192,7 @@ defmodule StringTest do
|
||||
assert String.reverse("Hello ∂og") == "go∂ olleH"
|
||||
assert String.reverse("Ā̀stute") == "etutsĀ̀"
|
||||
assert String.reverse(String.reverse("Hello World")) == "Hello World"
|
||||
assert String.reverse(String.reverse("Hello \r\n World")) == "Hello \r\n World"
|
||||
end
|
||||
|
||||
test :replace do
|
||||
@@ -238,7 +239,7 @@ defmodule StringTest do
|
||||
# Extended
|
||||
assert String.graphemes("Ā̀stute") == ["Ā̀", "s", "t", "u", "t", "e"]
|
||||
# CLRF
|
||||
assert String.graphemes("\n\r\f") == ["\n\r", "\f"]
|
||||
assert String.graphemes("\r\n\f") == ["\r\n", "\f"]
|
||||
# Regional indicator
|
||||
assert String.graphemes("\x{1F1E6}\x{1F1E7}\x{1F1E8}") == ["\x{1F1E6}\x{1F1E7}\x{1F1E8}"]
|
||||
# Hangul
|
||||
|
||||
@@ -65,6 +65,16 @@ defmodule SystemTest do
|
||||
opts = [into: [], cd: System.cwd!, env: %{"foo" => "bar"},
|
||||
arg0: "hecho", stderr_to_stdout: true, parallelism: true]
|
||||
assert {["hello\n"], 0} = System.cmd "echo", ["hello"], opts
|
||||
|
||||
with_tmp_dir(fn dir ->
|
||||
new_path = Path.join([dir, "echo2"])
|
||||
File.cp!(System.find_executable("echo"), new_path)
|
||||
assert :enoent = catch_error(System.cmd(new_path, ["hello"]))
|
||||
|
||||
File.cd!(dir)
|
||||
assert :enoent = catch_error(System.cmd("echo2", ["hello"]))
|
||||
assert {"hello\n", 0} = System.cmd(Path.join([System.cwd, "echo2"]), ["hello"])
|
||||
end)
|
||||
end
|
||||
|
||||
test "find_executable/1" do
|
||||
@@ -72,4 +82,17 @@ defmodule SystemTest do
|
||||
assert is_binary System.find_executable("erl")
|
||||
assert !System.find_executable("does-not-really-exist-from-elixir")
|
||||
end
|
||||
|
||||
defp with_tmp_dir(function) do
|
||||
dir = tmp_dir_name()
|
||||
tmp_dir = System.tmp_dir!
|
||||
tmp_path = Path.join [tmp_dir, dir]
|
||||
File.rm_rf! tmp_path
|
||||
File.mkdir_p! tmp_path
|
||||
File.cd! tmp_dir, fn -> function.(dir) end
|
||||
end
|
||||
|
||||
defp tmp_dir_name do
|
||||
:crypto.rand_bytes(4) |> Base.encode16
|
||||
end
|
||||
end
|
||||
|
||||
@@ -142,17 +142,25 @@ defmodule URITest do
|
||||
|
||||
test :default_port do
|
||||
assert URI.default_port("http") == 80
|
||||
assert URI.default_port("unknown") == nil
|
||||
try do
|
||||
URI.default_port("http", 8000)
|
||||
assert URI.default_port("http") == 8000
|
||||
after
|
||||
URI.default_port("http", 80)
|
||||
end
|
||||
|
||||
assert URI.default_port("unknown") == nil
|
||||
URI.default_port("unknown", 13)
|
||||
assert URI.default_port("unknown") == 13
|
||||
end
|
||||
|
||||
test :parse_bad_uris do
|
||||
assert URI.parse("https:??@?F?@#>F//23/")
|
||||
assert URI.parse("")
|
||||
assert URI.parse(":https")
|
||||
assert URI.parse("https")
|
||||
assert URI.parse("https:??@?F?@#>F//23/")
|
||||
|
||||
assert URI.parse(":https").path == ":https"
|
||||
assert URI.parse("https").path == "https"
|
||||
assert URI.parse("ht\0tps://foo.com").path == "ht\0tps://foo.com"
|
||||
end
|
||||
|
||||
test :ipv6_addresses do
|
||||
|
||||
@@ -208,8 +208,8 @@ defmodule String.Graphemes do
|
||||
end
|
||||
|
||||
# Don't break CRLF
|
||||
def next_grapheme(<< ?\n, ?\r, rest :: binary >>) do
|
||||
{"\n\r", rest}
|
||||
def next_grapheme(<< ?\r, ?\n, rest :: binary >>) do
|
||||
{"\r\n", rest}
|
||||
end
|
||||
|
||||
# Break on control
|
||||
|
||||
@@ -220,9 +220,17 @@ defmodule ExUnit.Runner do
|
||||
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
|
||||
after
|
||||
timeout ->
|
||||
stacktrace =
|
||||
try do
|
||||
Process.info(test_pid, :current_stacktrace)
|
||||
catch
|
||||
_, _ -> []
|
||||
else
|
||||
{:current_stacktrace, stacktrace} -> stacktrace
|
||||
end
|
||||
Process.exit(test_pid, :kill)
|
||||
Process.demonitor(test_ref, [:flush])
|
||||
%{test | state: {:failed, {:error, %ExUnit.TimeoutError{timeout: timeout}, []}}}
|
||||
%{test | state: {:failed, {:error, %ExUnit.TimeoutError{timeout: timeout}, stacktrace}}}
|
||||
end
|
||||
|
||||
exec_on_exit(test, test_pid)
|
||||
|
||||
@@ -50,8 +50,9 @@ defmodule ExUnitTest do
|
||||
end
|
||||
end
|
||||
|
||||
assert capture_io(fn -> ExUnit.run end) =~
|
||||
"** (ExUnit.TimeoutError) test timed out after 10ms"
|
||||
output = capture_io(fn -> ExUnit.run end)
|
||||
assert output =~ "** (ExUnit.TimeoutError) test timed out after 10ms"
|
||||
assert output =~ ~r"\(stdlib\) timer\.erl:\d+: :timer\.sleep/1"
|
||||
end
|
||||
|
||||
test "filtering cases with tags" do
|
||||
|
||||
+12
-61
@@ -273,54 +273,35 @@ defmodule IEx do
|
||||
|
||||
"""
|
||||
def configure(options) do
|
||||
Enum.each options, fn {k, v} ->
|
||||
Application.put_env(:iex, k, configure(k, v))
|
||||
end
|
||||
end
|
||||
|
||||
defp configure(k, v) when k in [:colors, :inspect] and is_list(v) do
|
||||
Keyword.merge(Application.get_env(:iex, k), v)
|
||||
end
|
||||
|
||||
defp configure(:history_size, v) when is_integer(v) do
|
||||
v
|
||||
end
|
||||
|
||||
defp configure(k, v) when k in [:default_prompt, :alive_prompt] and is_binary(v) do
|
||||
v
|
||||
end
|
||||
|
||||
defp configure(k, v) do
|
||||
raise ArgumentError, "invalid configuration or value for pair #{inspect k} - #{inspect v}"
|
||||
IEx.Config.configure(options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns IEx configuration.
|
||||
"""
|
||||
def configuration do
|
||||
Application.get_all_env(:iex)
|
||||
IEx.Config.configuration()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers a function to be invoked after the IEx process is spawned.
|
||||
"""
|
||||
def after_spawn(fun) when is_function(fun) do
|
||||
Application.put_env(:iex, :after_spawn, [fun|after_spawn])
|
||||
IEx.Config.after_spawn(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns registered `after_spawn` callbacks.
|
||||
"""
|
||||
def after_spawn do
|
||||
{:ok, list} = Application.fetch_env(:iex, :after_spawn)
|
||||
list
|
||||
IEx.Config.after_spawn()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if IEx was started.
|
||||
"""
|
||||
def started? do
|
||||
Application.get_env(:iex, :started, false)
|
||||
IEx.Config.started?()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -329,37 +310,28 @@ defmodule IEx do
|
||||
ANSI escapes in `string` are not processed in any way.
|
||||
"""
|
||||
def color(color, string) do
|
||||
colors = Application.get_env(:iex, :colors)
|
||||
|
||||
if color_enabled?(colors[:enabled]) do
|
||||
ansi = Keyword.get(colors, color, default_color(color))
|
||||
IO.iodata_to_binary(IO.ANSI.format_fragment(ansi, true)) <> string <> IO.ANSI.reset
|
||||
else
|
||||
string
|
||||
case IEx.Config.color(color) do
|
||||
nil ->
|
||||
string
|
||||
ansi ->
|
||||
IO.iodata_to_binary([IO.ANSI.format_fragment(ansi, true), string | IO.ANSI.reset])
|
||||
end
|
||||
end
|
||||
|
||||
defp color_enabled?(nil), do: IO.ANSI.enabled?
|
||||
defp color_enabled?(bool) when is_boolean(bool), do: bool
|
||||
|
||||
@doc """
|
||||
Gets the IEx width for printing.
|
||||
|
||||
Used by helpers and it has a maximum cap of 80 chars.
|
||||
"""
|
||||
def width do
|
||||
case :io.columns() do
|
||||
{:ok, width} -> min(width, 80)
|
||||
{:error, _} -> 80
|
||||
end
|
||||
IEx.Config.width()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the options used for inspecting.
|
||||
"""
|
||||
def inspect_opts do
|
||||
Application.get_env(:iex, :inspect) ++
|
||||
[width: width(), pretty: true]
|
||||
IEx.Config.inspect_opts()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -467,7 +439,6 @@ defmodule IEx do
|
||||
defp start_iex() do
|
||||
unless started? do
|
||||
{:ok, _} = Application.ensure_all_started(:iex)
|
||||
Application.put_env(:iex, :started, true)
|
||||
end
|
||||
|
||||
:ok
|
||||
@@ -503,24 +474,4 @@ defmodule IEx do
|
||||
_ = for fun <- Enum.reverse(after_spawn), do: fun.()
|
||||
:ok
|
||||
end
|
||||
|
||||
# Used by default on evaluation cycle
|
||||
defp default_color(:eval_interrupt), do: [:yellow]
|
||||
defp default_color(:eval_result), do: [:yellow]
|
||||
defp default_color(:eval_error), do: [:red]
|
||||
defp default_color(:eval_info), do: [:normal]
|
||||
defp default_color(:stack_app), do: [:red, :bright]
|
||||
defp default_color(:stack_info), do: [:red]
|
||||
|
||||
# Used by ls
|
||||
defp default_color(:ls_directory), do: [:blue]
|
||||
defp default_color(:ls_device), do: [:green]
|
||||
|
||||
# Used by ansi docs
|
||||
defp default_color(:doc_bold), do: [:bright]
|
||||
defp default_color(:doc_code), do: [:cyan, :bright]
|
||||
defp default_color(:doc_headings), do: [:yellow, :bright]
|
||||
defp default_color(:doc_inline_code), do: [:cyan]
|
||||
defp default_color(:doc_underline), do: [:underline]
|
||||
defp default_color(:doc_title), do: [:reverse, :yellow, :bright]
|
||||
end
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
defmodule IEx.App do
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [worker(IEx.Config, [])]
|
||||
options = [strategy: :one_for_one, name: IEx.Supervisor]
|
||||
|
||||
tab = IEx.Config.new()
|
||||
case Supervisor.start_link(children, options) do
|
||||
{:ok, pid} ->
|
||||
{:ok, pid, tab}
|
||||
{:error, _} = error ->
|
||||
IEx.Config.delete(tab)
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
def stop(tab) do
|
||||
IEx.Config.delete(tab)
|
||||
end
|
||||
end
|
||||
@@ -10,12 +10,12 @@ defmodule IEx.Autocomplete do
|
||||
h === ?. and t != []->
|
||||
expand_dot(reduce(t))
|
||||
h === ?: ->
|
||||
expand_erlang_modules
|
||||
expand_erlang_modules()
|
||||
identifier?(h) ->
|
||||
expand_expr(reduce(expr))
|
||||
(h == ?/) and t != [] and identifier?(hd(t)) ->
|
||||
expand_expr(reduce(t))
|
||||
h in '(+[' ->
|
||||
h in '([{' ->
|
||||
expand('')
|
||||
true ->
|
||||
no()
|
||||
@@ -40,32 +40,26 @@ defmodule IEx.Autocomplete do
|
||||
defp expand_expr(expr) do
|
||||
case Code.string_to_quoted expr do
|
||||
{:ok, atom} when is_atom(atom) ->
|
||||
expand_erlang_modules Atom.to_string(atom)
|
||||
expand_erlang_modules(Atom.to_string(atom))
|
||||
{:ok, {atom, _, nil}} when is_atom(atom) ->
|
||||
expand_import Atom.to_string(atom)
|
||||
expand_import(Atom.to_string(atom))
|
||||
{:ok, {:__aliases__, _, [root]}} ->
|
||||
expand_elixir_modules [], Atom.to_string(root)
|
||||
expand_elixir_modules([], Atom.to_string(root))
|
||||
{:ok, {:__aliases__, _, [h|_] = list}} when is_atom(h) ->
|
||||
hint = Atom.to_string(List.last(list))
|
||||
list = Enum.take(list, length(list) - 1)
|
||||
expand_elixir_modules list, hint
|
||||
expand_elixir_modules(list, hint)
|
||||
{:ok, {{:., _, [mod, fun]}, _, []}} when is_atom(fun) ->
|
||||
expand_call mod, Atom.to_string(fun)
|
||||
expand_call(mod, Atom.to_string(fun))
|
||||
_ ->
|
||||
no()
|
||||
end
|
||||
end
|
||||
|
||||
defp reduce(expr) do
|
||||
last_token(Enum.reverse(expr), [' ', '(', '[', '+', '-'])
|
||||
end
|
||||
|
||||
defp last_token(s, []) do
|
||||
s
|
||||
end
|
||||
|
||||
defp last_token(s, [h|t]) do
|
||||
last_token(List.last(:string.tokens(s, h)), t)
|
||||
Enum.reverse Enum.reduce [' ', '(', '[', '{'], expr, fn token, acc ->
|
||||
hd(:string.tokens(acc, token))
|
||||
end
|
||||
end
|
||||
|
||||
defp yes(hint, entries) do
|
||||
@@ -93,42 +87,22 @@ defmodule IEx.Autocomplete do
|
||||
length = byte_size(hint)
|
||||
prefix = :binary.longest_common_prefix(binary)
|
||||
if prefix in [0, length] do
|
||||
entries = Enum.reduce(entries, [], fn e, acc -> to_entries(e) ++ acc end)
|
||||
yes("", entries)
|
||||
yes("", Enum.flat_map(entries, &to_entries/1))
|
||||
else
|
||||
yes(:binary.part(first.name, prefix, length-prefix), [])
|
||||
end
|
||||
end
|
||||
|
||||
## Root Modules
|
||||
|
||||
defp root_modules do
|
||||
Enum.reduce :code.all_loaded, [], fn {m, _}, acc ->
|
||||
mod = Atom.to_string(m)
|
||||
case mod do
|
||||
"Elixir" <> _ ->
|
||||
tokens = String.split(mod, ".")
|
||||
if length(tokens) == 2 do
|
||||
[%{kind: :module, name: List.last(tokens), type: :elixir}|acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
_ ->
|
||||
[%{kind: :module, name: mod, type: :erlang}|acc]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## Expand calls
|
||||
|
||||
# :atom.fun
|
||||
defp expand_call(mod, hint) when is_atom(mod) do
|
||||
expand_require mod, hint
|
||||
expand_require(mod, hint)
|
||||
end
|
||||
|
||||
# Elixir.fun
|
||||
defp expand_call({:__aliases__, _, list}, hint) do
|
||||
expand_require Module.concat(list), hint
|
||||
expand_require(Module.concat(list), hint)
|
||||
end
|
||||
|
||||
defp expand_call(_, _) do
|
||||
@@ -136,13 +110,13 @@ defmodule IEx.Autocomplete do
|
||||
end
|
||||
|
||||
defp expand_require(mod, hint) do
|
||||
format_expansion module_funs(mod, hint), hint
|
||||
format_expansion match_module_funs(mod, hint), hint
|
||||
end
|
||||
|
||||
defp expand_import(hint) do
|
||||
funs = module_funs(IEx.Helpers, hint) ++
|
||||
module_funs(Kernel, hint) ++
|
||||
module_funs(Kernel.SpecialForms, hint)
|
||||
funs = match_module_funs(IEx.Helpers, hint) ++
|
||||
match_module_funs(Kernel, hint) ++
|
||||
match_module_funs(Kernel.SpecialForms, hint)
|
||||
format_expansion funs, hint
|
||||
end
|
||||
|
||||
@@ -152,55 +126,76 @@ defmodule IEx.Autocomplete do
|
||||
format_expansion match_erlang_modules(hint), hint
|
||||
end
|
||||
|
||||
defp match_erlang_modules("") do
|
||||
Enum.filter root_modules, fn m -> m.type === :erlang end
|
||||
end
|
||||
|
||||
defp match_erlang_modules(hint) do
|
||||
Enum.filter root_modules, fn m -> String.starts_with?(m.name, hint) end
|
||||
for mod <- match_modules(hint, true) do
|
||||
%{kind: :module, name: mod, type: :erlang}
|
||||
end
|
||||
end
|
||||
|
||||
## Elixir modules
|
||||
|
||||
defp expand_elixir_modules(list, hint) do
|
||||
mod = Module.concat(list)
|
||||
format_expansion elixir_submodules(mod, hint, list == []) ++ module_funs(mod, hint), hint
|
||||
format_expansion match_elixir_modules(mod, hint, list == []) ++
|
||||
match_module_funs(mod, hint), hint
|
||||
end
|
||||
|
||||
defp elixir_submodules(mod, hint, root) do
|
||||
modname = Atom.to_string(mod)
|
||||
depth = length(String.split(modname, ".")) + 1
|
||||
base = modname <> "." <> hint
|
||||
defp match_elixir_modules(module, hint, root) do
|
||||
module = Atom.to_string(module)
|
||||
depth = length(String.split(module, ".")) + 1
|
||||
base = module <> "." <> hint
|
||||
|
||||
Enum.reduce modules_as_lists(root), [], fn(m, acc) ->
|
||||
if String.starts_with?(m, base) do
|
||||
tokens = String.split(m, ".")
|
||||
if length(tokens) == depth do
|
||||
name = List.last(tokens)
|
||||
[%{kind: :module, type: :elixir, name: name}|acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
for mod <- match_modules(base, root),
|
||||
tokens = String.split(mod, "."),
|
||||
length(tokens) == depth do
|
||||
name = List.last(tokens)
|
||||
%{kind: :module, type: :elixir, name: name}
|
||||
end
|
||||
end
|
||||
|
||||
defp modules_as_lists(true) do
|
||||
["Elixir.Elixir"] ++ modules_as_lists(false)
|
||||
end
|
||||
|
||||
defp modules_as_lists(false) do
|
||||
Enum.map(:code.all_loaded, fn({m, _}) -> Atom.to_string(m) end)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp module_funs(mod, hint) do
|
||||
defp match_modules(hint, root) do
|
||||
get_modules(root)
|
||||
|> :lists.usort()
|
||||
|> Enum.drop_while(& not String.starts_with?(&1, hint))
|
||||
|> Enum.take_while(& String.starts_with?(&1, hint))
|
||||
end
|
||||
|
||||
defp get_modules(true) do
|
||||
["Elixir.Elixir"] ++ get_modules(false)
|
||||
end
|
||||
|
||||
defp get_modules(false) do
|
||||
modules = Enum.map(:code.all_loaded, fn({m, _}) -> Atom.to_string(m) end)
|
||||
|
||||
if :code.get_mode() === :interactive do
|
||||
modules ++ get_modules_from_applications()
|
||||
else
|
||||
modules
|
||||
end
|
||||
end
|
||||
|
||||
defp get_modules_from_applications do
|
||||
for {app, _, _} <- which_applications(),
|
||||
{_, modules} = :application.get_key(app, :modules),
|
||||
module <- modules do
|
||||
Atom.to_string(module)
|
||||
end
|
||||
end
|
||||
|
||||
defp which_applications() do
|
||||
try do
|
||||
:application.which_applications(5000)
|
||||
catch
|
||||
:exit, {:timeout, _} -> [:elixir, :iex]
|
||||
end
|
||||
end
|
||||
|
||||
defp match_module_funs(mod, hint) do
|
||||
case ensure_loaded(mod) do
|
||||
{:module, _} ->
|
||||
falist = get_funs(mod)
|
||||
falist = get_module_funs(mod)
|
||||
|
||||
list = Enum.reduce falist, [], fn {f, a}, acc ->
|
||||
case :lists.keyfind(f, 1, acc) do
|
||||
@@ -211,15 +206,15 @@ defmodule IEx.Autocomplete do
|
||||
|
||||
for {fun, arities} <- list,
|
||||
name = Atom.to_string(fun),
|
||||
hint == "" or String.starts_with?(name, hint) do
|
||||
String.starts_with?(name, hint) do
|
||||
%{kind: :function, name: name, arities: arities}
|
||||
end
|
||||
end |> :lists.sort()
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp get_funs(mod) do
|
||||
defp get_module_funs(mod) do
|
||||
if function_exported?(mod, :__info__, 1) do
|
||||
if docs = Code.get_docs(mod, :docs) do
|
||||
for {tuple, _line, _kind, _sign, doc} <- docs, doc != false, do: tuple
|
||||
@@ -241,7 +236,7 @@ defmodule IEx.Autocomplete do
|
||||
end
|
||||
|
||||
defp to_entries(%{kind: :function, name: name, arities: arities}) do
|
||||
for a <- arities, do: "#{name}/#{a}"
|
||||
for a <- :lists.sort(arities), do: "#{name}/#{a}"
|
||||
end
|
||||
|
||||
defp to_uniq_entries(%{kind: :module}) do
|
||||
|
||||
+10
-8
@@ -53,13 +53,15 @@ defmodule IEx.CLI do
|
||||
if tty_works? do
|
||||
:user_drv.start([:"tty_sl -c -e", tty_args])
|
||||
else
|
||||
:user.start
|
||||
unless match? {:win32, _}, :os.type do
|
||||
IO.puts "Warning: could not run smart terminal, falling back to dumb one"
|
||||
end
|
||||
:application.set_env(:stdlib, :shell_prompt_func,
|
||||
{__MODULE__, :prompt})
|
||||
:user.start()
|
||||
local_start()
|
||||
end
|
||||
end
|
||||
def prompt(_n) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Check if tty works. If it does not, we fall back to the
|
||||
# simple/dumb terminal. This is starting the linked in
|
||||
@@ -94,7 +96,7 @@ defmodule IEx.CLI do
|
||||
end
|
||||
|
||||
def local_start do
|
||||
IEx.start(config(), {:elixir, :start_cli, []})
|
||||
IEx.start(options(), {:elixir, :start_cli, []})
|
||||
end
|
||||
|
||||
def remote_start(parent, ref) do
|
||||
@@ -108,7 +110,7 @@ defmodule IEx.CLI do
|
||||
|
||||
defp remote_start_function do
|
||||
ref = make_ref
|
||||
config = config()
|
||||
opts = options()
|
||||
|
||||
parent = spawn_link fn ->
|
||||
receive do
|
||||
@@ -119,11 +121,11 @@ defmodule IEx.CLI do
|
||||
end
|
||||
|
||||
fn ->
|
||||
IEx.start(config, {__MODULE__, :remote_start, [parent, ref]})
|
||||
IEx.start(opts, {__MODULE__, :remote_start, [parent, ref]})
|
||||
end
|
||||
end
|
||||
|
||||
defp config do
|
||||
defp options do
|
||||
[dot_iex_path: find_dot_iex(:init.get_plain_arguments)]
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
defmodule IEx.Config do
|
||||
@moduledoc false
|
||||
|
||||
@table __MODULE__
|
||||
@agent __MODULE__
|
||||
@keys [:colors, :inspect, :history_size, :default_prompt, :alive_prompt]
|
||||
@colors [:eval_interrupt, :eval_result, :eval_error, :eval_info, :stack_app,
|
||||
:stack_info, :ls_directory, :ls_device]
|
||||
|
||||
def new() do
|
||||
tab = :ets.new(@table, [:named_table, :public])
|
||||
true = :ets.insert_new(tab, [after_spawn: []])
|
||||
tab
|
||||
end
|
||||
|
||||
def delete(__MODULE__) do
|
||||
:ets.delete(__MODULE__)
|
||||
end
|
||||
|
||||
def configure(options) do
|
||||
Agent.update(@agent, __MODULE__, :handle_configure, [options])
|
||||
end
|
||||
|
||||
def handle_configure(tab, options) do
|
||||
options = :lists.ukeysort(1, options)
|
||||
get_config()
|
||||
|> Keyword.merge(options, &merge_option/3)
|
||||
|> put_config()
|
||||
tab
|
||||
end
|
||||
|
||||
defp get_config() do
|
||||
Application.get_all_env(:iex)
|
||||
|> Keyword.take(@keys)
|
||||
end
|
||||
|
||||
defp put_config(config) do
|
||||
put = fn({key, value}) when key in @keys ->
|
||||
Application.put_env(:iex, key, value)
|
||||
end
|
||||
Enum.each(config, put)
|
||||
end
|
||||
|
||||
defp merge_option(:colors, old, new) when is_list(new), do: Keyword.merge(old, new)
|
||||
defp merge_option(:inspect, old, new) when is_list(new), do: Keyword.merge(old, new)
|
||||
defp merge_option(:history_size, _old, new) when is_integer(new), do: new
|
||||
defp merge_option(:default_prompt, _old, new) when is_binary(new), do: new
|
||||
defp merge_option(:alive_prompt, _old, new) when is_binary(new), do: new
|
||||
|
||||
defp merge_option(k, _old, new) do
|
||||
raise ArgumentError, "invalid configuration or value for pair #{inspect k} - #{inspect new}"
|
||||
end
|
||||
|
||||
def configuration() do
|
||||
Keyword.merge(default_config(), get_config(), &merge_option/3)
|
||||
end
|
||||
|
||||
defp default_config() do
|
||||
Enum.map(@keys, &{&1, default_option(&1)})
|
||||
end
|
||||
|
||||
defp default_option(:colors), do: [{:enabled, IO.ANSI.enabled?} | default_colors()]
|
||||
defp default_option(:inspect), do: []
|
||||
defp default_option(:history_size), do: 20
|
||||
|
||||
defp default_option(prompt) when prompt in [:default_prompt, :alive_prompt] do
|
||||
"%prefix(%counter)>"
|
||||
end
|
||||
|
||||
defp default_colors() do
|
||||
Enum.map(@colors, &{&1, default_color(&1)}) ++ default_doc_colors()
|
||||
end
|
||||
|
||||
defp default_doc_colors() do
|
||||
Keyword.drop(IO.ANSI.Docs.default_options(), [:enabled, :width])
|
||||
end
|
||||
|
||||
# Used by default on evaluation cycle
|
||||
defp default_color(:eval_interrupt), do: [:yellow]
|
||||
defp default_color(:eval_result), do: [:yellow]
|
||||
defp default_color(:eval_error), do: [:red]
|
||||
defp default_color(:eval_info), do: [:normal]
|
||||
defp default_color(:stack_app), do: [:red, :bright]
|
||||
defp default_color(:stack_info), do: [:red]
|
||||
|
||||
# Used by ls
|
||||
defp default_color(:ls_directory), do: [:blue]
|
||||
defp default_color(:ls_device), do: [:green]
|
||||
|
||||
# Used by ansi docs
|
||||
defp default_color(doc_color) do
|
||||
IO.ANSI.Docs.default_options()
|
||||
|> Keyword.fetch!(doc_color)
|
||||
end
|
||||
|
||||
def color(color) do
|
||||
colors = Application.get_env(:iex, :colors, [])
|
||||
if colors_enabled?(colors) do
|
||||
case Keyword.fetch(colors, color) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
:error ->
|
||||
default_color(color)
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp colors_enabled?(colors \\ Application.get_env(:iex, :colors, [])) do
|
||||
case Keyword.fetch(colors, :enabled) do
|
||||
{:ok, enabled} ->
|
||||
enabled
|
||||
:error ->
|
||||
IO.ANSI.enabled?()
|
||||
end
|
||||
end
|
||||
|
||||
def ansi_docs() do
|
||||
colors = Application.get_env(:iex, :colors, [])
|
||||
if enabled = colors_enabled?(colors) do
|
||||
[width: width(), enabled: enabled] ++ colors
|
||||
end
|
||||
end
|
||||
|
||||
def inspect_opts() do
|
||||
inspect_options = Application.get_env(:iex, :inspect, []) ++ [pretty: true]
|
||||
cond do
|
||||
Keyword.has_key?(inspect_options, :width) ->
|
||||
inspect_options
|
||||
colors_enabled? ->
|
||||
[width: width()] ++ inspect_options
|
||||
true ->
|
||||
inspect_options
|
||||
end
|
||||
end
|
||||
|
||||
def width() do
|
||||
case :io.columns() do
|
||||
{:ok, width} -> min(width, 80)
|
||||
{:error, _} -> 80
|
||||
end
|
||||
end
|
||||
|
||||
def after_spawn(fun) do
|
||||
Agent.update(@agent, __MODULE__, :handle_after_spawn, [fun])
|
||||
end
|
||||
|
||||
def handle_after_spawn(tab, fun) do
|
||||
:ets.update_element(tab, :after_spawn, {2, [fun | after_spawn()]})
|
||||
end
|
||||
|
||||
def after_spawn() do
|
||||
:ets.lookup_element(@table, :after_spawn, 2)
|
||||
end
|
||||
|
||||
def started?() do
|
||||
Process.whereis(@agent) !== nil
|
||||
end
|
||||
|
||||
def history_size(), do: get(:history_size)
|
||||
|
||||
def default_prompt(), do: get(:default_prompt)
|
||||
|
||||
def alive_prompt(), do: get(:alive_prompt)
|
||||
|
||||
defp get(key) do
|
||||
case Application.fetch_env(:iex, key) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
:error ->
|
||||
default_option(key)
|
||||
end
|
||||
end
|
||||
|
||||
def start_link() do
|
||||
Agent.start_link(__MODULE__, :init, [@table], [name: @agent])
|
||||
end
|
||||
|
||||
def init(tab) do
|
||||
:public = :ets.info(tab, :protection)
|
||||
tab
|
||||
end
|
||||
end
|
||||
@@ -25,8 +25,8 @@ defmodule IEx.Evaluator do
|
||||
|
||||
defp loop(server) do
|
||||
receive do
|
||||
{:eval, ^server, code, config} ->
|
||||
send server, {:evaled, self, eval(code, config)}
|
||||
{:eval, ^server, code, state} ->
|
||||
send server, {:evaled, self, eval(code, state)}
|
||||
loop(server)
|
||||
{:done, ^server} ->
|
||||
IEx.History.reset
|
||||
@@ -36,9 +36,9 @@ defmodule IEx.Evaluator do
|
||||
|
||||
@doc """
|
||||
Locates and loads an .iex.exs file from one of predefined locations.
|
||||
Returns the new config.
|
||||
Returns the new state.
|
||||
"""
|
||||
def load_dot_iex(config, path \\ nil) do
|
||||
def load_dot_iex(state, path \\ nil) do
|
||||
candidates = if path do
|
||||
[path]
|
||||
else
|
||||
@@ -48,22 +48,22 @@ defmodule IEx.Evaluator do
|
||||
path = Enum.find candidates, &File.regular?/1
|
||||
|
||||
if is_nil(path) do
|
||||
config
|
||||
state
|
||||
else
|
||||
eval_dot_iex(config, path)
|
||||
eval_dot_iex(state, path)
|
||||
end
|
||||
end
|
||||
|
||||
defp eval_dot_iex(config, path) do
|
||||
defp eval_dot_iex(state, path) do
|
||||
try do
|
||||
code = File.read!(path)
|
||||
env = :elixir.env_for_eval(config.env, file: path, line: 1)
|
||||
env = :elixir.env_for_eval(state.env, file: path, line: 1)
|
||||
|
||||
# Evaluate the contents in the same environment server_loop will run in
|
||||
{_result, binding, env, _scope} =
|
||||
:elixir.eval(String.to_char_list(code), config.binding, env)
|
||||
:elixir.eval(String.to_char_list(code), state.binding, env)
|
||||
|
||||
%{config | binding: binding, env: :elixir.env_for_eval(env, file: "iex", line: 1)}
|
||||
%{state | binding: binding, env: :elixir.env_for_eval(env, file: "iex", line: 1)}
|
||||
catch
|
||||
kind, error ->
|
||||
io_result "Error while evaluating: #{path}"
|
||||
@@ -80,63 +80,62 @@ defmodule IEx.Evaluator do
|
||||
# line in IEx). In case of any other error, we let :elixir_translator
|
||||
# to re-raise it.
|
||||
#
|
||||
# Returns updated config.
|
||||
# Returns updated state.
|
||||
#
|
||||
# The first two clauses provide support for the break-trigger allowing to
|
||||
# break out from a pending incomplete expression. See
|
||||
# https://github.com/elixir-lang/elixir/issues/1089 for discussion.
|
||||
@break_trigger '#iex:break\n'
|
||||
|
||||
defp eval(code, config) do
|
||||
defp eval(code, state) do
|
||||
try do
|
||||
do_eval(String.to_char_list(code), config)
|
||||
do_eval(String.to_char_list(code), state)
|
||||
catch
|
||||
kind, error ->
|
||||
print_error(kind, error, System.stacktrace)
|
||||
%{config | cache: ''}
|
||||
%{state | cache: ''}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_eval(@break_trigger, config=%IEx.Config{cache: ''}) do
|
||||
config
|
||||
defp do_eval(@break_trigger, state=%IEx.State{cache: ''}) do
|
||||
state
|
||||
end
|
||||
|
||||
defp do_eval(@break_trigger, config) do
|
||||
:elixir_errors.parse_error(config.counter, "iex", "incomplete expression", "")
|
||||
defp do_eval(@break_trigger, state) do
|
||||
:elixir_errors.parse_error(state.counter, "iex", "incomplete expression", "")
|
||||
end
|
||||
|
||||
defp do_eval(latest_input, config) do
|
||||
code = config.cache ++ latest_input
|
||||
line = config.counter
|
||||
handle_eval(Code.string_to_quoted(code, [line: line, file: "iex"]), code, line, config)
|
||||
defp do_eval(latest_input, state) do
|
||||
code = state.cache ++ latest_input
|
||||
line = state.counter
|
||||
handle_eval(Code.string_to_quoted(code, [line: line, file: "iex"]), code, line, state)
|
||||
end
|
||||
|
||||
defp handle_eval({:ok, forms}, code, line, config) do
|
||||
defp handle_eval({:ok, forms}, code, line, state) do
|
||||
{result, new_binding, env, scope} =
|
||||
:elixir.eval_forms(forms, config.binding, config.env, config.scope)
|
||||
:elixir.eval_forms(forms, state.binding, state.env, state.scope)
|
||||
unless result == IEx.dont_display_result, do: io_inspect(result)
|
||||
update_history(line, code, result)
|
||||
%{config | env: env,
|
||||
cache: '',
|
||||
scope: scope,
|
||||
binding: new_binding,
|
||||
counter: config.counter + 1}
|
||||
%{state | env: env,
|
||||
cache: '',
|
||||
scope: scope,
|
||||
binding: new_binding,
|
||||
counter: state.counter + 1}
|
||||
end
|
||||
|
||||
defp handle_eval({:error, {_, _, ""}}, code, _line, config) do
|
||||
# Update config.cache so that IEx continues to add new input to
|
||||
defp handle_eval({:error, {_, _, ""}}, code, _line, state) do
|
||||
# Update state.cache so that IEx continues to add new input to
|
||||
# the unfinished expression in `code`
|
||||
%{config | cache: code}
|
||||
%{state | cache: code}
|
||||
end
|
||||
|
||||
defp handle_eval({:error, {line, error, token}}, _code, _line, _config) do
|
||||
defp handle_eval({:error, {line, error, token}}, _code, _line, _state) do
|
||||
# Encountered malformed expression
|
||||
:elixir_errors.parse_error(line, "iex", error, token)
|
||||
end
|
||||
|
||||
defp update_history(counter, cache, result) do
|
||||
IEx.History.append({counter, cache, result}, counter,
|
||||
Application.get_env(:iex, :history_size))
|
||||
IEx.History.append({counter, cache, result}, counter, IEx.Config.history_size)
|
||||
end
|
||||
|
||||
defp io_inspect(result) do
|
||||
|
||||
@@ -42,7 +42,7 @@ defmodule IEx.Helpers do
|
||||
h(Enum.reverse/1)
|
||||
|
||||
To discover all available functions for a module, type the module name
|
||||
follow by a dot, then press tab to trigger autocomplete. For example:
|
||||
followed by a dot, then press tab to trigger autocomplete. For example:
|
||||
|
||||
Enum.
|
||||
|
||||
|
||||
@@ -175,15 +175,9 @@ defmodule IEx.Introspection do
|
||||
end
|
||||
|
||||
defp ansi_docs() do
|
||||
opts = Application.get_env(:iex, :colors)
|
||||
if color_enabled?(opts[:enabled]) do
|
||||
[width: IEx.width] ++ opts
|
||||
end
|
||||
IEx.Config.ansi_docs()
|
||||
end
|
||||
|
||||
defp color_enabled?(nil), do: IO.ANSI.enabled?
|
||||
defp color_enabled?(bool) when is_boolean(bool), do: bool
|
||||
|
||||
@doc """
|
||||
Print types in module.
|
||||
"""
|
||||
|
||||
+20
-20
@@ -1,4 +1,4 @@
|
||||
defmodule IEx.Config do
|
||||
defmodule IEx.State do
|
||||
@moduledoc false
|
||||
defstruct binding: nil, cache: '', counter: 1, prefix: "iex", scope: nil, env: nil
|
||||
@type t :: %__MODULE__{}
|
||||
@@ -115,7 +115,7 @@ defmodule IEx.Server do
|
||||
self_leader = Process.group_leader
|
||||
evaluator = opts[:evaluator] || spawn(fn -> IEx.Evaluator.start(self_pid, self_leader) end)
|
||||
Process.put(:evaluator, evaluator)
|
||||
loop(run_config(opts), evaluator, Process.monitor(evaluator))
|
||||
loop(run_state(opts), evaluator, Process.monitor(evaluator))
|
||||
end
|
||||
|
||||
defp reset_loop(opts, evaluator, evaluator_ref) do
|
||||
@@ -136,23 +136,23 @@ defmodule IEx.Server do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp loop(config, evaluator, evaluator_ref) do
|
||||
defp loop(state, evaluator, evaluator_ref) do
|
||||
self_pid = self()
|
||||
counter = config.counter
|
||||
prefix = if config.cache != [], do: "...", else: config.prefix
|
||||
counter = state.counter
|
||||
prefix = if state.cache != [], do: "...", else: state.prefix
|
||||
|
||||
input = spawn(fn -> io_get(self_pid, prefix, counter) end)
|
||||
wait_input(config, evaluator, evaluator_ref, input)
|
||||
wait_input(state, evaluator, evaluator_ref, input)
|
||||
end
|
||||
|
||||
defp wait_input(config, evaluator, evaluator_ref, input) do
|
||||
defp wait_input(state, evaluator, evaluator_ref, input) do
|
||||
receive do
|
||||
{:input, ^input, code} when is_binary(code) ->
|
||||
send evaluator, {:eval, self, code, config}
|
||||
send evaluator, {:eval, self, code, state}
|
||||
wait_eval(evaluator, evaluator_ref)
|
||||
{:input, ^input, {:error, :interrupted}} ->
|
||||
io_error "** (EXIT) interrupted"
|
||||
loop(%{config | cache: ''}, evaluator, evaluator_ref)
|
||||
loop(%{state | cache: ''}, evaluator, evaluator_ref)
|
||||
{:input, ^input, :eof} ->
|
||||
exit_loop(evaluator, evaluator_ref)
|
||||
{:input, ^input, {:error, :terminated}} ->
|
||||
@@ -160,15 +160,15 @@ defmodule IEx.Server do
|
||||
|
||||
msg ->
|
||||
handle_take_over(msg, evaluator, evaluator_ref, input, fn ->
|
||||
wait_input(config, evaluator, evaluator_ref, input)
|
||||
wait_input(state, evaluator, evaluator_ref, input)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_eval(evaluator, evaluator_ref) do
|
||||
receive do
|
||||
{:evaled, ^evaluator, config} ->
|
||||
loop(config, evaluator, evaluator_ref)
|
||||
{:evaled, ^evaluator, state} ->
|
||||
loop(state, evaluator, evaluator_ref)
|
||||
msg ->
|
||||
handle_take_over(msg, evaluator, evaluator_ref, nil,
|
||||
fn -> wait_eval(evaluator, evaluator_ref) end)
|
||||
@@ -231,9 +231,9 @@ defmodule IEx.Server do
|
||||
IO.gets(:stdio, message) =~ ~r/^(Y(es)?)?$/i
|
||||
end
|
||||
|
||||
## Config
|
||||
## State
|
||||
|
||||
defp run_config(opts) do
|
||||
defp run_state(opts) do
|
||||
locals = Keyword.get(opts, :delegate_locals_to, IEx.Helpers)
|
||||
|
||||
env =
|
||||
@@ -247,11 +247,11 @@ defmodule IEx.Server do
|
||||
|
||||
binding = Keyword.get(opts, :binding, [])
|
||||
prefix = Keyword.get(opts, :prefix, "iex")
|
||||
config = %IEx.Config{binding: binding, scope: scope, prefix: prefix, env: env}
|
||||
state = %IEx.State{binding: binding, scope: scope, prefix: prefix, env: env}
|
||||
|
||||
case opts[:dot_iex_path] do
|
||||
"" -> config
|
||||
path -> IEx.Evaluator.load_dot_iex(config, path)
|
||||
"" -> state
|
||||
path -> IEx.Evaluator.load_dot_iex(state, path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -265,12 +265,12 @@ defmodule IEx.Server do
|
||||
defp prompt(prefix, counter) do
|
||||
{mode, prefix} =
|
||||
if Node.alive? do
|
||||
{:alive, prefix || remote_prefix}
|
||||
{:alive_prompt, prefix || remote_prefix}
|
||||
else
|
||||
{:default, prefix || "iex"}
|
||||
{:default_prompt, prefix || "iex"}
|
||||
end
|
||||
|
||||
prompt = Application.get_env(:iex, :"#{mode}_prompt")
|
||||
prompt = apply(IEx.Config, mode, [])
|
||||
|> String.replace("%counter", to_string(counter))
|
||||
|> String.replace("%prefix", to_string(prefix))
|
||||
|> String.replace("%node", to_string(node))
|
||||
|
||||
+3
-2
@@ -8,8 +8,9 @@ defmodule IEx.Mixfile do
|
||||
end
|
||||
|
||||
def application do
|
||||
[env: [
|
||||
after_spawn: [],
|
||||
[registered: [IEx.Supervisor, IEx.Config],
|
||||
mod: {IEx.App, []},
|
||||
env: [
|
||||
colors: [],
|
||||
inspect: [],
|
||||
history_size: 20,
|
||||
|
||||
@@ -7,40 +7,54 @@ defmodule IEx.AutocompleteTest do
|
||||
IEx.Autocomplete.expand(Enum.reverse expr)
|
||||
end
|
||||
|
||||
test :erlang_module_simple_completion do
|
||||
assert expand(':z') == {:yes, 'lib.', []}
|
||||
test :erlang_module_completion do
|
||||
assert expand(':zl') == {:yes, 'ib.', []}
|
||||
end
|
||||
|
||||
test :erlang_module_no_completion do
|
||||
assert expand(':x') == {:no, '', []}
|
||||
assert expand('x.Foo') == {:no, '', []}
|
||||
end
|
||||
|
||||
test :erlang_module_common_prefix_completion do
|
||||
assert expand(':us') == {:yes, 'er', []}
|
||||
assert expand(':unknown') == {:no, '', []}
|
||||
end
|
||||
|
||||
test :erlang_module_multiple_values_completion do
|
||||
{:yes, '', list} = expand(':user')
|
||||
assert length(list) > 1
|
||||
assert 'user' in list
|
||||
assert 'user_drv' in list
|
||||
end
|
||||
|
||||
test :elixir_simple_completion do
|
||||
test :erlang_root_completion do
|
||||
{:yes, '', list} = expand(':')
|
||||
assert is_list(list)
|
||||
assert 'lists' in list
|
||||
end
|
||||
|
||||
test :elixir_proxy do
|
||||
{:yes, '', list} = expand('E')
|
||||
assert 'Elixir' in list
|
||||
end
|
||||
|
||||
test :elixir_completion do
|
||||
assert expand('En') == {:yes, 'um', []}
|
||||
assert expand('Enumera') == {:yes, 'ble.', []}
|
||||
end
|
||||
|
||||
test :elixir_auto_completion_with_self do
|
||||
test :elixir_completion_with_self do
|
||||
assert expand('Enumerable') == {:yes, '.', []}
|
||||
end
|
||||
|
||||
test :elixir_completion_on_modules_from_load_path do
|
||||
assert expand('Str') == {:yes, [], ['Stream', 'String', 'StringIO']}
|
||||
assert expand('Ma') == {:yes, '', ['Macro', 'Map', 'MatchError']}
|
||||
assert expand('Dic') == {:yes, 't.', []}
|
||||
assert expand('Ex') == {:yes, [], ['ExUnit', 'Exception']}
|
||||
end
|
||||
|
||||
test :elixir_no_completion do
|
||||
assert expand('.') == {:no, '', []}
|
||||
assert expand('Xyz') == {:no, '', []}
|
||||
assert expand('x.Foo') == {:no, '', []}
|
||||
end
|
||||
|
||||
test :elixir_root_submodule_completion do
|
||||
_ = [foo: 1][:foo]
|
||||
assert expand('Elixir.Acce') == {:yes, 'ss.', []}
|
||||
end
|
||||
|
||||
@@ -52,51 +66,43 @@ defmodule IEx.AutocompleteTest do
|
||||
assert expand('IEx.Xyz') == {:no, '', []}
|
||||
end
|
||||
|
||||
test :elixir_function_completion do
|
||||
test :function_completion do
|
||||
assert expand('System.ve') == {:yes, 'rsion', []}
|
||||
assert expand(':ets.fun2') == {:yes, 'ms', []}
|
||||
end
|
||||
|
||||
test :elixir_function_completion_with_arity do
|
||||
test :function_completion_with_arity do
|
||||
assert expand('String.printable?') == {:yes, '', ['printable?/1']}
|
||||
assert expand('String.printable?/') == {:yes, '', ['printable?/1']}
|
||||
end
|
||||
|
||||
test :elixir_macro_completion do
|
||||
test :macro_completion do
|
||||
{:yes, '', list} = expand('Kernel.is_')
|
||||
assert is_list(list)
|
||||
end
|
||||
|
||||
test :elixir_root_completion do
|
||||
test :imports_completion do
|
||||
{:yes, '', list} = expand('')
|
||||
assert is_list(list)
|
||||
assert 'h/1' in list
|
||||
assert 'unquote/1' in list
|
||||
assert 'pwd/0' in list
|
||||
end
|
||||
|
||||
test :elixir_kernel_completion do
|
||||
test :kernel_import_completion do
|
||||
assert expand('defstru') == {:yes, 'ct', []}
|
||||
assert expand('put_') == {:yes, '', ['put_elem/3', 'put_in/2', 'put_in/3']}
|
||||
end
|
||||
|
||||
test :elixir_special_form_completion do
|
||||
test :kernel_special_form_completion do
|
||||
assert expand('unquote_spl') == {:yes, 'icing', []}
|
||||
end
|
||||
|
||||
test :elixir_proxy do
|
||||
{:yes, '', list} = expand('E')
|
||||
assert 'Elixir' in list
|
||||
end
|
||||
|
||||
test :elixir_erlang_module_root_completion do
|
||||
{:yes, '', list} = expand(':')
|
||||
assert is_list(list)
|
||||
assert 'lists' in list
|
||||
end
|
||||
|
||||
test :completion_inside_expression do
|
||||
assert expand('1+En') == {:yes, 'um', []}
|
||||
assert expand('1 En') == {:yes, 'um', []}
|
||||
assert expand('Test(En') == {:yes, 'um', []}
|
||||
assert expand('Test :z') == {:yes, 'lib.', []}
|
||||
assert expand('[:z') == {:yes, 'lib.', []}
|
||||
assert expand('Test :zl') == {:yes, 'ib.', []}
|
||||
assert expand('[:zl') == {:yes, 'ib.', []}
|
||||
assert expand('{:zl') == {:yes, 'ib.', []}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Application.start(:iex)
|
||||
Application.put_env(:iex, :colors, [enabled: false])
|
||||
:ok = Application.start(:iex)
|
||||
IEx.configure([colors: [enabled: false]])
|
||||
ExUnit.start [trace: "--trace" in System.argv]
|
||||
|
||||
defmodule IEx.Case do
|
||||
@@ -31,11 +31,13 @@ defmodule IEx.Case do
|
||||
end
|
||||
end
|
||||
|
||||
@iex_app_env [:default_prompt, :alive_prompt, :inspect, :colors, :history_size]
|
||||
setup do
|
||||
opts = IEx.configuration |>
|
||||
Keyword.take([:default_prompt, :alive_prompt, :inspect, :colors, :history_size])
|
||||
opts = Application.get_all_env(:iex)
|
||||
|> Keyword.take(@iex_app_env)
|
||||
on_exit fn ->
|
||||
Enum.each opts, fn {k, v} -> Application.put_env(:iex, k, v) end
|
||||
Enum.each @iex_app_env, fn k -> Application.delete_env(:iex, k) end
|
||||
IEx.configure(opts)
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -474,8 +474,8 @@ defmodule Logger do
|
||||
defp macro_log(level, chardata, metadata, caller) do
|
||||
min_level = Application.get_env(:logger, :compile_time_purge_level, :debug)
|
||||
if compare_levels(level, min_level) != :lt do
|
||||
%{module: module, function: function, line: line} = caller
|
||||
caller = [module: module, function: function, line: line]
|
||||
%{module: module, function: fun, line: line} = caller
|
||||
caller = [module: module, function: form_fa(fun), line: line]
|
||||
quote do
|
||||
Logger.log(unquote(level), unquote(chardata), unquote(caller) ++ unquote(metadata))
|
||||
end
|
||||
@@ -491,6 +491,12 @@ defmodule Logger do
|
||||
defp truncate(data, n),
|
||||
do: Logger.Utils.truncate(to_string(data), n)
|
||||
|
||||
defp form_fa({name, arity}) do
|
||||
Atom.to_string(name) <> "/" <> Integer.to_string(arity)
|
||||
end
|
||||
|
||||
defp form_fa(nil), do: nil
|
||||
|
||||
defp notify(:sync, msg), do: GenEvent.sync_notify(Logger, msg)
|
||||
defp notify(:async, msg), do: GenEvent.notify(Logger, msg)
|
||||
end
|
||||
|
||||
@@ -21,49 +21,59 @@ defmodule Logger.App do
|
||||
{otp_reports?, sasl_reports?, threshold}, :link],
|
||||
[id: Logger.ErrorHandler, function: :watcher])]
|
||||
|
||||
config = Logger.Config.new()
|
||||
|
||||
case Supervisor.start_link(children, options) do
|
||||
{:ok, _} = ok ->
|
||||
deleted = delete_error_logger_handler(otp_reports?, :error_logger_tty_h, [])
|
||||
deleted = delete_error_logger_handler(sasl_reports?, :sasl_report_tty_h, deleted)
|
||||
store_deleted_handlers(deleted)
|
||||
ok
|
||||
{:ok, sup} ->
|
||||
handlers = [error_logger_tty_h: otp_reports?,
|
||||
sasl_logger_tty_h: sasl_reports?]
|
||||
delete_handlers(handlers)
|
||||
{:ok, sup, config}
|
||||
{:error, _} = error ->
|
||||
Logger.Config.delete(config)
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stop(_) do
|
||||
Application.get_env(:logger, :deleted_handlers)
|
||||
|> Enum.each(&:error_logger.add_report_handler/1)
|
||||
def stop(config) do
|
||||
Logger.Config.deleted_handlers()
|
||||
|> add_handlers()
|
||||
Logger.Config.delete(config)
|
||||
end
|
||||
|
||||
# We need to do this in another process as the Application
|
||||
# Controller is currently blocked shutting down this app.
|
||||
spawn_link(fn -> Logger.Config.clear_data end)
|
||||
|
||||
:ok
|
||||
@doc false
|
||||
def config_change(_changed, _new, _removed) do
|
||||
Logger.Config.configure([])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the application without sending messages to error logger.
|
||||
"""
|
||||
def stop() do
|
||||
set = Application.get_env(:logger, :deleted_handlers)
|
||||
Application.put_env(:logger, :deleted_handlers, HashSet.new)
|
||||
_ = Application.stop(:logger)
|
||||
Enum.each(set, &:error_logger.add_report_handler/1)
|
||||
end
|
||||
|
||||
defp store_deleted_handlers(list) do
|
||||
Application.put_env(:logger, :deleted_handlers, Enum.into(list, HashSet.new))
|
||||
end
|
||||
|
||||
defp delete_error_logger_handler(should_delete?, handler, deleted) do
|
||||
if should_delete? and
|
||||
:error_logger.delete_report_handler(handler) != {:error, :module_not_found} do
|
||||
[handler|deleted]
|
||||
try do
|
||||
Logger.Config.deleted_handlers([])
|
||||
catch
|
||||
:exit, {:noproc, _} ->
|
||||
{:error, {:not_started, :logger}}
|
||||
else
|
||||
deleted
|
||||
deleted_handlers ->
|
||||
result = Application.stop(:logger)
|
||||
add_handlers(deleted_handlers)
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_handlers(handlers) do
|
||||
deleted? = fn({handler, delete?}) ->
|
||||
delete? && :error_logger.delete_report_handler(handler) != {:error, :module_not_found}
|
||||
end
|
||||
[] = Enum.filter_map(handlers, deleted?, fn({handler, _}) -> handler end)
|
||||
|> Logger.Config.deleted_handlers()
|
||||
:ok
|
||||
end
|
||||
|
||||
defp add_handlers(handlers) do
|
||||
Enum.each(handlers, &:error_logger.add_report_handler/1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,9 @@ defmodule Logger.Config do
|
||||
use GenEvent
|
||||
|
||||
@name __MODULE__
|
||||
@table __MODULE__
|
||||
@data :__data__
|
||||
@deleted_handlers :__deleted_handlers__
|
||||
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, :ok, name: @name)
|
||||
@@ -44,22 +46,46 @@ defmodule Logger.Config do
|
||||
def translate_backend(other), do: other
|
||||
|
||||
def __data__() do
|
||||
if data = Application.get_env(:logger, @data) do
|
||||
data
|
||||
try do
|
||||
:ets.lookup_element(@table, @data, 2)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
raise "Cannot use Logger, the :logger application is not running"
|
||||
else
|
||||
raise "Cannot use Logger, the :logger application is not running"
|
||||
nil ->
|
||||
raise "Cannot use Logger, the :logger application is not running"
|
||||
data ->
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
def clear_data() do
|
||||
Application.delete_env(:logger, @data)
|
||||
def deleted_handlers() do
|
||||
try do
|
||||
:ets.lookup_element(@table, @deleted_handlers, 2)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def deleted_handlers(handlers) do
|
||||
GenEvent.call(Logger, @name, {:deleted_handlers, handlers})
|
||||
end
|
||||
|
||||
def new() do
|
||||
tab = :ets.new(@table, [:named_table, :public, {:read_concurrency, true}])
|
||||
true = :ets.insert_new(@table, [{@data, nil}, {@deleted_handlers, []}])
|
||||
tab
|
||||
end
|
||||
|
||||
def delete(@table) do
|
||||
:ets.delete(@table)
|
||||
end
|
||||
## Callbacks
|
||||
|
||||
def init(_) do
|
||||
# Use previous data if available in case this handler crashed.
|
||||
state = Application.get_env(:logger, @data) || compute_state(:async)
|
||||
state = :ets.lookup_element(@table, @data, 2) || compute_state(:async)
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@@ -111,6 +137,12 @@ defmodule Logger.Config do
|
||||
{:ok, :ok, state}
|
||||
end
|
||||
|
||||
def handle_call({:deleted_handlers, new}, state) do
|
||||
old = deleted_handlers()
|
||||
true = :ets.update_element(@table, @deleted_handlers, {2, new})
|
||||
{:ok, old, state}
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compute_mode(state) do
|
||||
@@ -159,7 +191,7 @@ defmodule Logger.Config do
|
||||
end
|
||||
|
||||
defp persist(state) do
|
||||
Application.put_env(:logger, @data, state)
|
||||
:ets.update_element(@table, @data, {2, state})
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,8 +43,19 @@ defmodule Logger.Backends.ConsoleTest do
|
||||
Logger.metadata(user_id: 13)
|
||||
|
||||
assert capture_log(fn ->
|
||||
Logger.debug("user_id=13 hello")
|
||||
end) =~ "hello"
|
||||
Logger.debug("hello")
|
||||
end) =~ "user_id=13 hello"
|
||||
end
|
||||
|
||||
test "metadata defaults" do
|
||||
Logger.configure_backend(:console,
|
||||
format: "$metadata", metadata: [:module, :function, :line])
|
||||
|
||||
%{module: mod, function: {name, arity}, line: line} = __ENV__
|
||||
|
||||
assert capture_log(fn ->
|
||||
Logger.debug("hello")
|
||||
end) =~ "module=#{mod} function=#{name}/#{arity} line=#{line + 3}"
|
||||
end
|
||||
|
||||
test "can configure level" do
|
||||
|
||||
@@ -234,4 +234,15 @@ defmodule LoggerTest do
|
||||
wait_for_handler(Logger, Logger.Config)
|
||||
wait_for_handler(:error_logger, Logger.ErrorHandler)
|
||||
end
|
||||
|
||||
test "Logger.Config updates config on config_change/3" do
|
||||
:ok = Logger.configure([level: :debug])
|
||||
try do
|
||||
Application.put_env(:logger, :level, :error)
|
||||
assert Logger.App.config_change([level: :error], [], []) === :ok
|
||||
assert Logger.level() === :error
|
||||
after
|
||||
Logger.configure([level: :debug])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+19
-9
@@ -161,19 +161,29 @@ defmodule Mix do
|
||||
def start(_type, []) do
|
||||
import Supervisor.Spec
|
||||
|
||||
state = [shell: Mix.Shell.IO,
|
||||
env: String.to_atom(System.get_env("MIX_ENV") || "dev"),
|
||||
scm: [Mix.SCM.Git, Mix.SCM.Path]]
|
||||
tab = Mix.State.new(state)
|
||||
children = [
|
||||
worker(Mix.State, [tab]),
|
||||
worker(Mix.TasksServer, []),
|
||||
worker(Mix.ProjectStack, [])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one, name: Mix.Supervisor]
|
||||
stat = Supervisor.start_link(children, opts)
|
||||
|
||||
if env = System.get_env("MIX_ENV") do
|
||||
env(String.to_atom env)
|
||||
case Supervisor.start_link(children, opts) do
|
||||
{:ok, pid} ->
|
||||
{:ok, pid, tab}
|
||||
{:error, _} = error ->
|
||||
Mix.State.delete(tab)
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
stat
|
||||
@doc false
|
||||
def stop(tab) do
|
||||
Mix.State.delete(tab)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -181,7 +191,7 @@ defmodule Mix do
|
||||
"""
|
||||
def env do
|
||||
# env is not available on bootstrapping, so set a :dev default
|
||||
Application.get_env(:mix, :env, :dev)
|
||||
Mix.State.get(:env, :dev)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -191,7 +201,7 @@ defmodule Mix do
|
||||
configuration won't be reloaded.
|
||||
"""
|
||||
def env(env) when is_atom(env) do
|
||||
Application.put_env(:mix, :env, env)
|
||||
Mix.State.put(:env, env)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -218,14 +228,14 @@ defmodule Mix do
|
||||
messages to the current process.
|
||||
"""
|
||||
def shell do
|
||||
Application.get_env(:mix, :shell, Mix.Shell.IO)
|
||||
Mix.State.get(:shell, Mix.Shell.IO)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the current shell.
|
||||
"""
|
||||
def shell(shell) do
|
||||
Application.put_env(:mix, :shell, shell)
|
||||
Mix.State.put(:shell, shell)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -19,10 +19,10 @@ defmodule Mix.CLI do
|
||||
end
|
||||
|
||||
defp proceed(args) do
|
||||
_ = Mix.Tasks.Local.Hex.ensure_updated?()
|
||||
load_dot_config()
|
||||
args = load_mixfile(args)
|
||||
{task, args} = get_task(args)
|
||||
ensure_hex(task)
|
||||
change_env(task)
|
||||
run_task(task, args)
|
||||
end
|
||||
@@ -69,6 +69,11 @@ defmodule Mix.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_hex("local.hex"),
|
||||
do: :ok
|
||||
defp ensure_hex(_task),
|
||||
do: Mix.Tasks.Local.Hex.ensure_updated?()
|
||||
|
||||
defp change_env(task) do
|
||||
if is_nil(System.get_env("MIX_ENV")) &&
|
||||
(env = preferred_cli_env(task)) do
|
||||
|
||||
@@ -13,12 +13,13 @@ defmodule Mix.Compilers.Elixir do
|
||||
in between modules, which helps it recompile only the modules that
|
||||
have changed at runtime.
|
||||
"""
|
||||
def compile(manifest, srcs, exts, dest, force, on_start) do
|
||||
all = Mix.Utils.extract_files(srcs, exts)
|
||||
all_entries = read_manifest(manifest)
|
||||
def compile(manifest, srcs, skip, exts, dest, force, on_start) do
|
||||
keep = srcs -- skip
|
||||
all = Mix.Utils.extract_files(keep, exts)
|
||||
{all_entries, skip_entries} = read_manifest(manifest, dest, keep)
|
||||
|
||||
removed =
|
||||
for {_b, _m, source, _d, _f} <- all_entries, not(source in all), do: source
|
||||
for {_b, _m, source, _d, _f, _bin} <- all_entries, not(source in all), do: source
|
||||
|
||||
changed =
|
||||
if force do
|
||||
@@ -32,31 +33,45 @@ defmodule Mix.Compilers.Elixir do
|
||||
# Otherwise let's start with the new ones
|
||||
# plus the ones that have changed
|
||||
for(source <- all,
|
||||
not Enum.any?(all_entries, fn {_b, _m, s, _d, _f} -> s == source end),
|
||||
not Enum.any?(all_entries, fn {_b, _m, s, _d, _f, _bin} -> s == source end),
|
||||
do: source)
|
||||
++
|
||||
for({_b, _m, source, _d, files} <- all_entries,
|
||||
for({_b, _m, source, _d, files, _bin} <- all_entries,
|
||||
times = Enum.map([source|files], &HashDict.fetch!(all_mtimes, &1)),
|
||||
Mix.Utils.stale?(times, [modified]),
|
||||
do: source)
|
||||
end
|
||||
|
||||
{entries, changed} = remove_stale_entries(all_entries, removed ++ changed, [], [])
|
||||
{entries, changed} = remove_stale_entries(all_entries, removed ++ changed)
|
||||
stale = changed -- removed
|
||||
|
||||
cond do
|
||||
stale != [] ->
|
||||
do_compile(manifest, entries, stale, dest, on_start)
|
||||
compile_manifest(manifest, entries ++ skip_entries, stale, dest, on_start)
|
||||
:ok
|
||||
removed != [] ->
|
||||
write_manifest(manifest, entries ++ skip_entries)
|
||||
:ok
|
||||
File.exists?(Mix.Project.app_path) ->
|
||||
# If we have an app path but a noop result, we rebuild
|
||||
# the structure because priv/include needs to be recopied when
|
||||
# symlink is not available. We do this in the Elixir compiler
|
||||
# instead of the main compiler one because other compilers may
|
||||
# return :ok, saying they did some work, but they may have not
|
||||
# built a structure at all.
|
||||
#
|
||||
# This implies the Elixir compiler is somewhat required, even
|
||||
# for Erlang projects, which is an ok assumption to make (we
|
||||
# ship with both Elixir and Erlang compilers by default).
|
||||
Mix.Project.build_structure()
|
||||
:noop
|
||||
true ->
|
||||
:noop
|
||||
end
|
||||
end
|
||||
|
||||
defp mtimes(entries) do
|
||||
Enum.reduce(entries, HashDict.new, fn {_b, _m, source, _d, files}, dict ->
|
||||
Enum.reduce(entries, HashDict.new, fn {_b, _m, source, _d, files, _bin}, dict ->
|
||||
Enum.reduce([source|files], dict, fn file, dict ->
|
||||
if HashDict.has_key?(dict, file) do
|
||||
dict
|
||||
@@ -82,16 +97,14 @@ defmodule Mix.Compilers.Elixir do
|
||||
end
|
||||
end
|
||||
|
||||
defp do_compile(manifest, entries, stale, dest, on_start) do
|
||||
defp compile_manifest(manifest, entries, stale, dest, on_start) do
|
||||
Mix.Project.build_structure
|
||||
on_start.()
|
||||
cwd = File.cwd!
|
||||
|
||||
# Starts a server responsible for keeping track which files
|
||||
# were compiled and the dependencies in between them.
|
||||
{:ok, pid} = Agent.start_link(fn ->
|
||||
Enum.map(entries, &Tuple.insert_at(&1, 5, nil))
|
||||
end)
|
||||
{:ok, pid} = Agent.start_link(fn -> entries end)
|
||||
|
||||
try do
|
||||
_ = Kernel.ParallelCompiler.files :lists.usort(stale),
|
||||
@@ -103,7 +116,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
entries
|
||||
end
|
||||
after
|
||||
Agent.stop pid
|
||||
Agent.stop(pid, :infinity)
|
||||
end
|
||||
|
||||
:ok
|
||||
@@ -145,7 +158,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
|
||||
# This function receives the manifest entries and some source
|
||||
# files that have changed. It then, recursively, figures out
|
||||
# all the files that changed (thanks to the dependencies) and
|
||||
# all the files that changed (via the module dependencies) and
|
||||
# return their sources as the remaining entries.
|
||||
defp remove_stale_entries(all, []) do
|
||||
{all, []}
|
||||
@@ -155,9 +168,12 @@ defmodule Mix.Compilers.Elixir do
|
||||
remove_stale_entries(all, :lists.usort(changed), [], [])
|
||||
end
|
||||
|
||||
defp remove_stale_entries([{beam, module, source, _d, _f} = entry|t], changed, removed, acc) do
|
||||
defp remove_stale_entries([{beam, module, source, _d, _f, _bin} = entry|t], changed, removed, acc) do
|
||||
if source in changed do
|
||||
atom = String.to_atom(module)
|
||||
_ = File.rm(beam)
|
||||
_ = :code.purge(atom)
|
||||
_ = :code.delete(atom)
|
||||
remove_stale_entries(t, changed, [module|removed], acc)
|
||||
else
|
||||
remove_stale_entries(t, changed, removed, [entry|acc])
|
||||
@@ -167,7 +183,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
defp remove_stale_entries([], changed, removed, acc) do
|
||||
# If any of the dependencies for the remaining entries
|
||||
# were removed, get its source so we can remove them.
|
||||
next_changed = for {_b, _m, source, deps, _f} <- acc,
|
||||
next_changed = for {_b, _m, source, deps, _f, _bin} <- acc,
|
||||
Enum.any?(deps, &(&1 in removed)),
|
||||
do: source
|
||||
|
||||
@@ -179,27 +195,44 @@ defmodule Mix.Compilers.Elixir do
|
||||
|
||||
# Reads the manifest returning the results as tuples.
|
||||
# The beam files are read, removed and stored in memory.
|
||||
defp read_manifest(manifest) do
|
||||
defp read_manifest(manifest, dest, keep_paths) do
|
||||
initial = {[], []}
|
||||
|
||||
case File.read(manifest) do
|
||||
{:ok, contents} ->
|
||||
Enum.reduce String.split(contents, "\n"), [], fn x, acc ->
|
||||
case String.split(x, "\t") do
|
||||
[beam, module, source|deps] ->
|
||||
{deps, files} =
|
||||
case Enum.split_while(deps, &(&1 != "Elixir")) do
|
||||
{deps, ["Elixir"|files]} -> {deps, files}
|
||||
{deps, _} -> {deps, []}
|
||||
end
|
||||
[{beam, module, source, deps, files}|acc]
|
||||
_ ->
|
||||
acc
|
||||
end
|
||||
keep_paths = Enum.map(keep_paths, &(&1 <> "/"))
|
||||
Enum.reduce String.split(contents, "\n"), initial, fn x, acc ->
|
||||
read_manifest_entry(String.split(x, "\t"), acc, dest, keep_paths)
|
||||
end
|
||||
{:error, _} ->
|
||||
[]
|
||||
initial
|
||||
end
|
||||
end
|
||||
|
||||
defp read_manifest_entry([_beam, module, source|deps], {keep, skip}, dest, keep_paths) do
|
||||
{deps, files} =
|
||||
case Enum.split_while(deps, &(&1 != "Elixir")) do
|
||||
{deps, ["Elixir"|files]} -> {deps, files}
|
||||
{deps, _} -> {deps, []}
|
||||
end
|
||||
|
||||
# TODO: Notice we do not use beam from the file.
|
||||
# Once Elixir v1.1 is out, we can start writing "1"
|
||||
# instead of the beam file in write_manifest/2.
|
||||
entry = {Path.join(dest, module <> ".beam"), module, source,
|
||||
deps, files, nil}
|
||||
|
||||
if String.starts_with?(source, keep_paths) do
|
||||
{[entry|keep], skip}
|
||||
else
|
||||
{keep, [entry|skip]}
|
||||
end
|
||||
end
|
||||
|
||||
defp read_manifest_entry(_, acc, _dest, _skip_paths) do
|
||||
acc
|
||||
end
|
||||
|
||||
# Writes the manifest separating entries by tabs.
|
||||
defp write_manifest(_manifest, []) do
|
||||
:ok
|
||||
@@ -213,6 +246,11 @@ defmodule Mix.Compilers.Elixir do
|
||||
[beam, module, source | tail] |> Enum.join("\t")
|
||||
end)
|
||||
|
||||
# The Mix.Dep.Lock keeps all the project dependencies. Since Elixir
|
||||
# is a dependency itself, we need to touch the lock so the current
|
||||
# Elixir version, used to compile the files above, is properly stored.
|
||||
Mix.Dep.Lock.touch
|
||||
|
||||
File.mkdir_p!(Path.dirname(manifest))
|
||||
File.write!(manifest, Enum.join(lines, "\n"))
|
||||
end
|
||||
|
||||
+14
-30
@@ -40,6 +40,8 @@ defmodule Mix.Config do
|
||||
@doc """
|
||||
Configures the given application.
|
||||
|
||||
Keyword lists are always deep merged.
|
||||
|
||||
## Examples
|
||||
|
||||
The given `opts` are merged into the existing configuration
|
||||
@@ -69,6 +71,8 @@ defmodule Mix.Config do
|
||||
@doc """
|
||||
Configures the given key for the given application.
|
||||
|
||||
Keyword lists are always deep merged.
|
||||
|
||||
## Examples
|
||||
|
||||
The given `opts` are merged into the existing values for `key`
|
||||
@@ -91,8 +95,7 @@ defmodule Mix.Config do
|
||||
quote do
|
||||
var!(config, Mix.Config) =
|
||||
Mix.Config.merge(var!(config, Mix.Config),
|
||||
[{unquote(app), [{unquote(key), unquote(opts)}]}],
|
||||
fn _app, _key, v1, v2 -> Keyword.merge(v1, v2) end)
|
||||
[{unquote(app), [{unquote(key), unquote(opts)}]}])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -116,7 +119,7 @@ defmodule Mix.Config do
|
||||
defmacro import_config(file) do
|
||||
quote do
|
||||
var!(config, Mix.Config) =
|
||||
Mix.Config.read_wildcard!(Path.expand(unquote(file), __DIR__), var!(config, Mix.Config))
|
||||
Mix.Config.read_wildcard!(var!(config, Mix.Config), Path.expand(unquote(file), __DIR__))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -142,7 +145,7 @@ defmodule Mix.Config do
|
||||
@doc """
|
||||
Reads many configuration files given by wildcard into a single config.
|
||||
"""
|
||||
def read_wildcard!(path, config) do
|
||||
def read_wildcard!(config, path) do
|
||||
paths = case Path.wildcard(path) do
|
||||
[] -> [path]
|
||||
o -> o
|
||||
@@ -201,34 +204,15 @@ defmodule Mix.Config do
|
||||
"""
|
||||
def merge(config1, config2) do
|
||||
Keyword.merge(config1, config2, fn _, app1, app2 ->
|
||||
Keyword.merge(app1, app2)
|
||||
Keyword.merge(app1, app2, &deep_merge/3)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two configurations.
|
||||
|
||||
The configuration of each application is merged together
|
||||
and a callback is invoked in case of conflicts receiving
|
||||
the app, the conflicting key and both values. It must return
|
||||
a value that will be used as part of the conflict resolution.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Mix.Config.merge([app: [k: :v1]], [app: [k: :v2]],
|
||||
...> fn app, k, v1, v2 -> {app, k, v1, v2} end)
|
||||
[app: [k: {:app, :k, :v1, :v2}]]
|
||||
|
||||
"""
|
||||
def merge(config1, config2, callback) do
|
||||
Keyword.merge(config1, config2, fn app, app1, app2 ->
|
||||
Keyword.merge(app1, app2, fn k, v1, v2 ->
|
||||
if v1 == v2 do
|
||||
v1
|
||||
else
|
||||
callback.(app, k, v1, v2)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
defp deep_merge(_key, value1, value2) do
|
||||
if Keyword.keyword?(value1) and Keyword.keyword?(value2) do
|
||||
Keyword.merge(value1, value2, &deep_merge/3)
|
||||
else
|
||||
value2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule Mix.Generator do
|
||||
def create_file(path, contents, opts \\ []) when is_binary(path) do
|
||||
Mix.shell.info [:green, "* creating ", :reset, Path.relative_to_cwd(path)]
|
||||
|
||||
if opts[:force] || overwriting?(path) do
|
||||
if opts[:force] || Mix.Utils.overwriting?(path) do
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
File.write!(path, contents)
|
||||
end
|
||||
@@ -31,15 +31,6 @@ defmodule Mix.Generator do
|
||||
File.mkdir_p! path
|
||||
end
|
||||
|
||||
defp overwriting?(path) do
|
||||
if File.exists?(path) do
|
||||
full = Path.expand(path)
|
||||
Mix.shell.yes?(Path.relative_to_cwd(full) <> " already exists, overwrite?")
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro from_file(path) do
|
||||
quote do
|
||||
|
||||
@@ -156,7 +156,7 @@ defmodule Mix.Rebar do
|
||||
defp wrap_cmd(nil), do: nil
|
||||
defp wrap_cmd(rebar) do
|
||||
if match?({:win32, _}, :os.type) and not String.ends_with?(rebar,".cmd") do
|
||||
"escript.exe #{rebar}"
|
||||
"escript.exe \"#{rebar}\""
|
||||
else
|
||||
rebar
|
||||
end
|
||||
|
||||
@@ -30,13 +30,13 @@ defmodule Mix.RemoteConverger do
|
||||
Get registered remote converger.
|
||||
"""
|
||||
def get do
|
||||
Application.get_env(:mix, :remote_converger)
|
||||
Mix.State.get(:remote_converger)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Register a remote converger.
|
||||
"""
|
||||
def register(mod) when is_atom(mod) do
|
||||
Application.put_env(:mix, :remote_converger, mod)
|
||||
Mix.State.put(:remote_converger, mod)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -116,7 +116,7 @@ defmodule Mix.SCM do
|
||||
until a matching one is found.
|
||||
"""
|
||||
def available do
|
||||
{:ok, scm} = Application.fetch_env(:mix, :scm)
|
||||
{:ok, scm} = Mix.State.fetch(:scm)
|
||||
scm
|
||||
end
|
||||
|
||||
@@ -124,15 +124,13 @@ defmodule Mix.SCM do
|
||||
Prepend the given SCM module to the list of available SCMs.
|
||||
"""
|
||||
def prepend(mod) when is_atom(mod) do
|
||||
available = Enum.reject(available(), &(&1 == mod))
|
||||
Application.put_env(:mix, :scm, [mod|available])
|
||||
Mix.State.prepend(:scm, mod)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Append the given SCM module to the list of available SCMs.
|
||||
"""
|
||||
def append(mod) when is_atom(mod) do
|
||||
available = Enum.reject(available(), &(&1 == mod))
|
||||
Application.put_env(:mix, :scm, available ++ [mod])
|
||||
Mix.State.append(:scm, mod)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -163,12 +163,12 @@ defmodule Mix.SCM.Git do
|
||||
end
|
||||
|
||||
defp assert_git do
|
||||
case Application.fetch_env(:mix, :git_available) do
|
||||
case Mix.State.fetch(:git_available) do
|
||||
{:ok, true} ->
|
||||
:ok
|
||||
:error ->
|
||||
if :os.find_executable('git') do
|
||||
Application.put_env(:mix, :git_available, true)
|
||||
Mix.State.put(:git_available, true)
|
||||
else
|
||||
Mix.raise "Error fetching/updating Git repository: the `git` " <>
|
||||
"executable is not available in your PATH. Please install " <>
|
||||
@@ -179,7 +179,7 @@ defmodule Mix.SCM.Git do
|
||||
end
|
||||
|
||||
defp git_version do
|
||||
case Application.fetch_env(:mix, :git_version) do
|
||||
case Mix.State.fetch(:git_version) do
|
||||
{:ok, version} ->
|
||||
version
|
||||
:error ->
|
||||
@@ -189,7 +189,7 @@ defmodule Mix.SCM.Git do
|
||||
|> String.strip
|
||||
|> parse_version
|
||||
|
||||
Application.put_env(:mix, :git_version, version)
|
||||
Mix.State.put(:git_version, version)
|
||||
version
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +42,7 @@ defmodule Mix.Shell.IO do
|
||||
"""
|
||||
def error(message) do
|
||||
print_app
|
||||
IO.puts :stderr, red(IO.ANSI.format(message))
|
||||
IO.puts :stderr, IO.ANSI.format(red(message))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -72,6 +72,6 @@ defmodule Mix.Shell.IO do
|
||||
defp got_yes?(_), do: false
|
||||
|
||||
defp red(message) do
|
||||
[IO.ANSI.red, IO.ANSI.bright, message, IO.ANSI.reset]
|
||||
[:red, :bright, message]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
defmodule Mix.State do
|
||||
@moduledoc false
|
||||
|
||||
@table __MODULE__
|
||||
@agent __MODULE__
|
||||
|
||||
def new(state) do
|
||||
tab = :ets.new(@table, [:named_table, :public])
|
||||
true = :ets.insert_new(@table, state)
|
||||
tab
|
||||
end
|
||||
|
||||
def delete(@table = tab) do
|
||||
:ets.delete(tab)
|
||||
end
|
||||
|
||||
def start_link(tab) do
|
||||
Agent.start_link(__MODULE__, :init, [tab], [name: @agent])
|
||||
end
|
||||
|
||||
def init(@table = tab) do
|
||||
case :ets.info(tab, :protection) do
|
||||
:public ->
|
||||
tab
|
||||
:undefined ->
|
||||
raise "#{tab} does not exist"
|
||||
_ ->
|
||||
raise "#{tab} is not public"
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(key) do
|
||||
case :ets.lookup(@table, key) do
|
||||
[{_, value}] ->
|
||||
{:ok, value}
|
||||
[] ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def get(key, default \\ nil) do
|
||||
case fetch(key) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def put(key, value) do
|
||||
Agent.update(@agent, __MODULE__, :handle_put, [key, value])
|
||||
end
|
||||
|
||||
def handle_put(tab, key, value) do
|
||||
true = :ets.insert(tab, {key, value})
|
||||
tab
|
||||
end
|
||||
|
||||
def prepend(key, value) do
|
||||
Agent.update(@agent, __MODULE__, :handle_prepend, [key, value])
|
||||
end
|
||||
|
||||
def handle_prepend(tab, key, value) do
|
||||
true = :ets.insert(tab, {key, [value | reject(key, value)]})
|
||||
tab
|
||||
end
|
||||
|
||||
def append(key, value) do
|
||||
Agent.update(@agent, __MODULE__, :handle_append, [key, value])
|
||||
end
|
||||
|
||||
def handle_append(tab, key, value) do
|
||||
true = :ets.insert(tab, {key, reject(key, value) ++ [value]})
|
||||
tab
|
||||
end
|
||||
|
||||
defp reject(key, value) do
|
||||
{:ok, list} = fetch(key)
|
||||
Enum.reject(list, &(&1 === value))
|
||||
end
|
||||
end
|
||||
@@ -26,14 +26,13 @@ defmodule Mix.Tasks.App.Start do
|
||||
Mix.Task.run "compile", ["--no-readd"|args]
|
||||
end
|
||||
|
||||
# Stop the Logger after we have used it for compilation.
|
||||
# It is up to the application to decide if it should be
|
||||
# restarted or not.
|
||||
# Mix should not depend directly on Logger so check that
|
||||
# it's loaded.
|
||||
if Code.ensure_loaded?(Logger), do: Logger.App.stop()
|
||||
|
||||
unless "--no-start" in args do
|
||||
# Stop the Logger when starting the application as it is
|
||||
# up to the application to decide if it should be restarted
|
||||
# or not.
|
||||
#
|
||||
# Mix should not depend directly on Logger so check that it's loaded.
|
||||
if Code.ensure_loaded?(Logger), do: Logger.App.stop()
|
||||
start(Mix.Project.config[:app])
|
||||
end
|
||||
|
||||
|
||||
@@ -72,5 +72,8 @@ defmodule Mix.Tasks.Archive.Build do
|
||||
end
|
||||
|
||||
Mix.Archive.create(source, target)
|
||||
|
||||
Mix.shell.info "Generated archive #{target} with MIX_ENV=#{Mix.env}"
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,13 +54,12 @@ defmodule Mix.Tasks.Archive.Install do
|
||||
if opts[:force] || should_install?(src, previous) do
|
||||
remove_previous_versions(previous)
|
||||
|
||||
dest = Mix.Local.archives_path()
|
||||
archive = Path.join(dest, basename(src))
|
||||
check_file_exists(archive)
|
||||
archive = Path.join(Mix.Local.archives_path(), basename(src))
|
||||
check_file_exists(src, archive)
|
||||
|
||||
File.mkdir_p!(dest)
|
||||
File.write!(archive, Mix.Utils.read_path!(src, opts))
|
||||
Mix.shell.info [:green, "* creating ", :reset, Path.relative_to_cwd(archive)]
|
||||
if Mix.Utils.copy_path!(src, archive, opts) do
|
||||
Mix.shell.info [:green, "* creating ", :reset, Path.relative_to_cwd(archive)]
|
||||
end
|
||||
|
||||
true = Code.append_path(Mix.Archive.ebin(archive))
|
||||
else
|
||||
@@ -80,11 +79,11 @@ defmodule Mix.Tasks.Archive.Install do
|
||||
defp should_install?(_src, previous_files) do
|
||||
files = Enum.map_join(previous_files, ", ", &Path.basename/1)
|
||||
|
||||
Mix.shell.yes?("Found existing archives: #{files}.\n" <>
|
||||
Mix.shell.yes?("Found existing archive(s): #{files}.\n" <>
|
||||
"Are you sure you want to replace them?")
|
||||
end
|
||||
|
||||
defp check_file_exists(path) do
|
||||
defp check_file_exists(src, path) do
|
||||
# OTP keeps loaded archives open, this leads to unfortunate behaviour on
|
||||
# Windows when trying overwrite loaded archives. remove_previous_versions
|
||||
# completes successfully even though the file will be first removed after
|
||||
@@ -92,7 +91,10 @@ defmodule Mix.Tasks.Archive.Install do
|
||||
# command, which should complete successfully at that time
|
||||
|
||||
if File.exists?(path) and match?({:win32, _}, :os.type) do
|
||||
Mix.raise "Unable to overwrite open archives on Windows. Run the command again"
|
||||
Mix.raise "Unable to overwrite open archives on Windows. Please manually remove " <>
|
||||
"the existing archive at #{inspect path} and run this command again. In " <>
|
||||
"case re-running the command still does not work, please fetch the archive " <>
|
||||
"at #{inspect src} and manually copy it to #{inspect path}."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -99,7 +99,9 @@ defmodule Mix.Tasks.Compile.App do
|
||||
properties = ensure_correct_properties(app, config, properties)
|
||||
contents = {:application, app, properties}
|
||||
|
||||
Mix.Project.build_structure(config)
|
||||
# There are no modules, we assume no structure was built.
|
||||
# If it was built, it doesn't matter, we just rebuild it.
|
||||
if mods == [], do: Mix.Project.build_structure(config)
|
||||
File.write!(target, :io_lib.format("~p.", [contents]))
|
||||
|
||||
Mix.shell.info "Generated #{app}.app"
|
||||
|
||||
@@ -22,8 +22,8 @@ defmodule Mix.Tasks.Compile.Elixir do
|
||||
* `--debug-info` (`--no-debug-info`) - attach (or not) debug info to compiled modules
|
||||
* `--ignore-module-conflict` - do not emit warnings if a module was previously defined
|
||||
* `--warnings-as-errors` - treat warnings as errors and return a non-zero exit code
|
||||
* `--elixirc-paths` - paths to lookup for Elixir source.
|
||||
Can be given multiple times and, once given, overrides the project elixirc_paths config.
|
||||
* `--elixirc-paths` - restrict the original elixirc paths to
|
||||
a subset of the ones specified. Can be given multiple times.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -51,10 +51,12 @@ defmodule Mix.Tasks.Compile.Elixir do
|
||||
|
||||
project = Mix.Project.config
|
||||
dest = Mix.Project.compile_path(project)
|
||||
srcs = case Keyword.get_values(opts, :elixirc_paths) do
|
||||
[] -> project[:elixirc_paths]
|
||||
ep -> ep
|
||||
end
|
||||
srcs = project[:elixirc_paths]
|
||||
skip =
|
||||
case Keyword.get_values(opts, :elixirc_paths) do
|
||||
[] -> []
|
||||
ep -> srcs -- ep
|
||||
end
|
||||
|
||||
manifest = manifest()
|
||||
configs = Mix.Project.config_files ++ Mix.Tasks.Compile.Erlang.manifests
|
||||
@@ -62,16 +64,10 @@ defmodule Mix.Tasks.Compile.Elixir do
|
||||
force = opts[:force] || local_deps_changed?(manifest)
|
||||
|| Mix.Utils.stale?(configs, [manifest])
|
||||
|
||||
result = Mix.Compilers.Elixir.compile(manifest, srcs, [:ex], dest, force, fn ->
|
||||
Mix.Compilers.Elixir.compile(manifest, srcs, skip, [:ex], dest, force, fn ->
|
||||
true = Code.prepend_path(dest)
|
||||
set_compiler_opts(project, opts, [])
|
||||
end)
|
||||
|
||||
# The Mix.Dep.Lock keeps all the project dependencies. Since Elixir
|
||||
# is a dependency itself, we need to touch the lock so the current
|
||||
# Elixir version, used to compile the files above, is properly stored.
|
||||
unless result == :noop, do: Mix.Dep.Lock.touch
|
||||
result
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -39,8 +39,8 @@ defmodule Mix.Tasks.Deps.Clean do
|
||||
do_clean(apps, build, deps)
|
||||
true ->
|
||||
Mix.raise "mix deps.clean expects dependencies as arguments or " <>
|
||||
"a flag indicating which dependencies to clean " <>
|
||||
"The --all option will clean all dependencies while"
|
||||
"a flag indicating which dependencies to clean. " <>
|
||||
"The --all option will clean all dependencies while " <>
|
||||
"the --unused option cleans unused dependencies."
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user