Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddc6b48a49 | ||
|
|
5a2e838def | ||
|
|
ebc6d76034 | ||
|
|
42fc70a0eb | ||
|
|
87fee37711 | ||
|
|
4870351d2f | ||
|
|
aba387c526 | ||
|
|
36d8e1ceee | ||
|
|
68c9a28d06 | ||
|
|
30c9af3631 | ||
|
|
f42055be57 | ||
|
|
17a5c37d06 | ||
|
|
8c45982cc6 | ||
|
|
6697100f95 | ||
|
|
94812b4242 | ||
|
|
4fae777e89 | ||
|
|
7ea4b02460 | ||
|
|
11423af777 | ||
|
|
23c67ac47c | ||
|
|
3ae00072ae | ||
|
|
b4b9ecccb4 | ||
|
|
53b68887b6 | ||
|
|
ea8e555f53 | ||
|
|
407427f627 | ||
|
|
2b2d9dc2de | ||
|
|
b4ad1ff5bc | ||
|
|
ab538670d3 | ||
|
|
691a9b8b2b | ||
|
|
ee3fcdb96a | ||
|
|
ba88ee7c92 | ||
|
|
9cd5a47bbe | ||
|
|
be3ab7d13d | ||
|
|
cdbd480f4d | ||
|
|
0538e46023 | ||
|
|
ebf84037cc | ||
|
|
1b3cdbc7fb | ||
|
|
1d13d189c1 | ||
|
|
7fcdecde95 | ||
|
|
bc852a6fba | ||
|
|
28ff6b3532 | ||
|
|
b0e1c09579 | ||
|
|
d3a5cd22a6 | ||
|
|
96a1628033 | ||
|
|
d6de0979fb | ||
|
|
19301b9005 | ||
|
|
02c76da51a | ||
|
|
435597286f | ||
|
|
8e3b5e38e4 | ||
|
|
9fcab6e881 | ||
|
|
afd5ebb563 | ||
|
|
e169c0f49b | ||
|
|
8c97056b0e | ||
|
|
4be1501f57 | ||
|
|
59402569ed | ||
|
|
0aff2a3139 | ||
|
|
9c4271891a | ||
|
|
7c240199f3 | ||
|
|
1e9ab79cad | ||
|
|
d37b7b3702 | ||
|
|
ac2c79a7ce | ||
|
|
84cc7d5eb0 | ||
|
|
27467bdb67 | ||
|
|
1ee1a3f687 | ||
|
|
05c6f9f822 | ||
|
|
952052869f | ||
|
|
df8b216357 | ||
|
|
79faa1f706 | ||
|
|
31cec42b23 | ||
|
|
05beafd5bb | ||
|
|
cba7c5bd80 | ||
|
|
9b22b6d883 | ||
|
|
2a6940014a | ||
|
|
2a1941112e | ||
|
|
5e2ea082d1 | ||
|
|
ba33806cc3 | ||
|
|
62eeb97eb8 | ||
|
|
0a24252fca | ||
|
|
243f4e6b0a | ||
|
|
3bc277f525 | ||
|
|
f0bfdfaf19 | ||
|
|
2979430137 | ||
|
|
1c4b759c4e | ||
|
|
f67022267f | ||
|
|
febee82e9e | ||
|
|
66d8cdc56e | ||
|
|
8d59c007c8 | ||
|
|
3a0b4988fc | ||
|
|
b7bbe13c91 | ||
|
|
4cbe174c13 | ||
|
|
bbe63e9a93 | ||
|
|
6f0f0d5136 | ||
|
|
e055c2ccf2 | ||
|
|
08d61e4b25 | ||
|
|
9c9d8e855e | ||
|
|
bb7b990055 | ||
|
|
0aee0805a9 | ||
|
|
9afc1043c9 | ||
|
|
1622eb63b4 | ||
|
|
d769f8f35e | ||
|
|
9cd56804ea | ||
|
|
6d17b42beb | ||
|
|
ac546881e8 | ||
|
|
77aca31ffe | ||
|
|
9395bb105d | ||
|
|
47feb1b6a0 | ||
|
|
f98afc1d83 | ||
|
|
6b407a339b | ||
|
|
c91d2d9c2b | ||
|
|
f82fbdcaed | ||
|
|
c6817c80b0 | ||
|
|
6fc468bddb | ||
|
|
5b6fc9b415 | ||
|
|
5c45e4aed4 | ||
|
|
5fa617a8aa | ||
|
|
387dd72d54 | ||
|
|
91bbfa52ba | ||
|
|
b80fe27705 | ||
|
|
dbcb3717d6 | ||
|
|
7836493d23 | ||
|
|
d27c7b0e1f | ||
|
|
26bc9d9d61 | ||
|
|
684cf05d75 | ||
|
|
417c719c78 | ||
|
|
dc63e25995 | ||
|
|
74e9f76aaa | ||
|
|
40d383faae | ||
|
|
eaac3bbd76 | ||
|
|
ae12432294 | ||
|
|
d0a6792bdf | ||
|
|
a5b67f3084 | ||
|
|
6e48e5dfcb | ||
|
|
83c93beaa6 | ||
|
|
ddd4afbdba | ||
|
|
a2cc2623a8 | ||
|
|
d4da5be4ee | ||
|
|
b03e2173a5 | ||
|
|
f547ef82e2 | ||
|
|
254065d3c4 | ||
|
|
29b38ab9e0 | ||
|
|
0e627c9b09 | ||
|
|
f8b019c7c8 | ||
|
|
c4419268b8 | ||
|
|
ade26dedd8 | ||
|
|
574e92fc57 | ||
|
|
8c6e31bda0 | ||
|
|
70d7a28a36 | ||
|
|
78def5da6e | ||
|
|
ffe2cc74b8 | ||
|
|
99b1d9b677 | ||
|
|
869378d1ca | ||
|
|
f306ca5a49 | ||
|
|
b39c762557 | ||
|
|
98e15914eb | ||
|
|
8c47368fff | ||
|
|
4176480f39 | ||
|
|
f81a15b7cb | ||
|
|
b3ba12593d | ||
|
|
51aef55c83 | ||
|
|
ec41e0fe1e | ||
|
|
2d36a76b26 | ||
|
|
9f5e7c4a41 | ||
|
|
e98f3eae10 | ||
|
|
5b5075b2c7 | ||
|
|
238baa14fa | ||
|
|
620532049c | ||
|
|
67566f155b | ||
|
|
d976937486 | ||
|
|
3f4410ff68 | ||
|
|
4c26585ef6 | ||
|
|
6fa7d64814 | ||
|
|
67eeedfb1d | ||
|
|
d0a823c8b0 | ||
|
|
df9d535769 | ||
|
|
219ded1b91 | ||
|
|
a0eef07958 | ||
|
|
c7af8bc2cc | ||
|
|
349873ab63 | ||
|
|
f2cf4a393f | ||
|
|
2c1c504fc5 | ||
|
|
625a27fac2 | ||
|
|
5164073bf0 | ||
|
|
66dd76e42b | ||
|
|
bb05011dca | ||
|
|
c296e62543 | ||
|
|
8847f8ed68 | ||
|
|
b536a2ac7c | ||
|
|
a33cbbe6b1 | ||
|
|
afb7efd11b | ||
|
|
613298c4ab | ||
|
|
d39401e2d3 | ||
|
|
a5602a71fa | ||
|
|
0bd45a090a | ||
|
|
68f570c0a5 | ||
|
|
f0456b6377 | ||
|
|
dfeeeacb8c | ||
|
|
72d683a86a | ||
|
|
ee337bbfb8 | ||
|
|
d4729a3795 | ||
|
|
352a2d8851 | ||
|
|
c7da787265 | ||
|
|
159ba856a6 | ||
|
|
6c84492618 | ||
|
|
eab8b46879 | ||
|
|
67e0367987 | ||
|
|
cd43f618e0 | ||
|
|
3687b0f104 | ||
|
|
04623a6edf | ||
|
|
a3afaeec8e | ||
|
|
5d7a1cf7a0 | ||
|
|
4e29f274c5 | ||
|
|
05a321dc8c | ||
|
|
7818551e6a | ||
|
|
9b6f104ffe | ||
|
|
80118f4561 | ||
|
|
474b05e5c4 | ||
|
|
7d1e6b545d | ||
|
|
01e31cf3c3 | ||
|
|
640cc06d63 | ||
|
|
b2f97f5406 | ||
|
|
d510356f20 | ||
|
|
cb6753742b | ||
|
|
73253206f2 | ||
|
|
90f9162e3f |
+2
-2
@@ -1,10 +1,10 @@
|
||||
language: erlang
|
||||
script: "make compile && make test"
|
||||
script: "make compile && rm -rf .git && make test"
|
||||
notifications:
|
||||
irc: "irc.freenode.org#elixir-lang"
|
||||
recipients:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- yrashk@gmail.com
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- R16B02
|
||||
- R16B01
|
||||
|
||||
+54
-1
@@ -1,7 +1,60 @@
|
||||
# v0.12.2 (2014-01-15)
|
||||
|
||||
* Enhancements
|
||||
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
|
||||
* [Enum] Add `Enum.flat_map_reduce/3`
|
||||
* [ExUnit] Support @moduletag in ExUnit cases
|
||||
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
|
||||
* [Stream] Add `Stream.transform/3`
|
||||
|
||||
* Bug fixes
|
||||
* [ExUnit] `:include` in ExUnit only has effect if a test was previously excluded with `:exclude`
|
||||
* [ExUnit] Only run `setup_all` and `teardown_all` if there are tests in the case
|
||||
* [Kernel] Ensure bitstring modifier arguments are expanded
|
||||
* [Kernel] Ensure compiler does not block on missing modules
|
||||
* [Kernel] Ensure `<>/2` works only with binaries
|
||||
* [Kernel] Fix usage of string literals inside `<<>>` when `utf8`/`utf16`/`utf32` is used as specifier
|
||||
* [Mix] Ensure mix properly copies _build dependencies on Windows
|
||||
|
||||
* Deprecations
|
||||
* [Enum] Deprecate `Enum.first/1` in favor of `Enum.at/2` and `List.first/1`
|
||||
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behavior has been deprecated
|
||||
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
|
||||
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
|
||||
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
|
||||
* [Kernel] Deprecate `<-/2` in favor of `send/2`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
|
||||
|
||||
# v0.12.1 (2014-01-04)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
|
||||
* [ExUnit] Allow doctests to match against `#MyModule<>`
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Abort when a pattern given to elixirc does not match any file
|
||||
* [Float] Fix `Float.parse/1` to handle numbers of the form "-0.x"
|
||||
* [IEx] Improve error message for `IEx.Helpers.r` when module does not exist
|
||||
* [Mix] Ensure `deps.get` updates origin if lock origin and dep origin do not match
|
||||
* [Mix] Use relative symlinks in _build
|
||||
* [Typespec] Fix conversion of unary ops from typespec format to ast
|
||||
* [Typespec] Fix handling of `tuple()` and `{}`
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] Do not leak clause heads. Previously, a variable defined in a case/receive head clauses would leak to the outer scope. This behaviour is deprecated and will be removed in the next release.
|
||||
* [Kernel] Deprecate `__FILE__` in favor of `__DIR__` or `__ENV__.file`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenFSM] GenServer now stops on unknown event/sync_event requests
|
||||
* [GenServer] GenServer now stops on unknown call/cast requests
|
||||
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
|
||||
|
||||
# v0.12.0 (2013-12-15)
|
||||
|
||||
* Enhancements
|
||||
* [Exception] Allow `exception/1` to be overriden and promote it as the main mechanism to customize exceptions
|
||||
* [Exception] Allow `exception/1` to be overridden and promote it as the main mechanism to customize exceptions
|
||||
* [File] Add `File.stream_to!/3`
|
||||
* [Float] Add `Float.floor/1`, `Float.ceil/1` and `Float.round/3`
|
||||
* [Kernel] Add `List.delete_at/2` and `List.updated_at/3`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
REBAR := "$(shell echo `pwd`/rebar)"
|
||||
REBAR := rebar
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -20,6 +20,8 @@ define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
||||
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||
$(Q) rm -rf lib/$(1)/_build
|
||||
@@ -27,7 +29,7 @@ lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ rm -rf lib/$(1)/ebin
|
||||
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
$(Q) cd lib/$(1) && ../../$$(ELIXIRC) "lib/**/*.ex" -o ebin
|
||||
|
||||
test_$(1): $(1)
|
||||
@ echo "==> $(1) (exunit)"
|
||||
@@ -50,7 +52,7 @@ lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
$(Q) cat src/elixir.app.src >>lib/elixir/src/elixir.app.src
|
||||
|
||||
erlang:
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
$(Q) cd lib/elixir && ../../$(REBAR) compile
|
||||
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
@@ -63,18 +65,18 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@ echo "==> kernel (compile)";
|
||||
$(Q) $(ELIXIRC) "lib/elixir/lib/kernel.ex" -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
@ echo "==> elixir (compile)";
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/kernel.ex" -o ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin;
|
||||
$(Q) $(MAKE) unicode
|
||||
$(Q) rm -rf lib/elixir/ebin/elixir.app
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
$(Q) cd lib/elixir && ../../$(REBAR) compile
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/unicode.ex lib/elixir/unicode/UnicodeData.txt lib/elixir/unicode/GraphemeBreakProperty.txt
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
$(Q) $(ELIXIRC) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/graphemes.ex unicode/unicode.ex -o ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
@@ -95,7 +97,7 @@ install: compile
|
||||
done
|
||||
|
||||
clean:
|
||||
cd lib/elixir && $(REBAR) clean
|
||||
cd lib/elixir && ../../$(REBAR) clean
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf lib/elixir/test/ebin
|
||||
@@ -154,7 +156,7 @@ test_doc_test: compile
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: 'nofile', line: 1, start_line: 1
|
||||
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: "nofile", line: 1, start_line: 1
|
||||
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
@@ -23,7 +23,7 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
expr = maybe_block :elixir_translator.forms!(chars, line, state.file, [])
|
||||
expr = Code.string_to_quoted!(chars, [line: line, file: state.file])
|
||||
buffer = state.engine.handle_expr(buffer, mark, expr)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
@@ -41,7 +41,7 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
|
||||
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
|
||||
tuples = maybe_block :elixir_translator.forms!(wrapped, state.start_line, state.file, [])
|
||||
tuples = Code.string_to_quoted!(wrapped, [line: state.start_line, file: state.file])
|
||||
buffer = insert_quotes(tuples, state.dict)
|
||||
{ buffer, t }
|
||||
end
|
||||
@@ -63,7 +63,7 @@ defmodule EEx.Compiler do
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
|
||||
if state.dict == [] and is_empty?(buffer) do
|
||||
if state.dict == [] and empty?(buffer) do
|
||||
{ current ++ new_lines ++ chars, state }
|
||||
else
|
||||
key = length(state.dict)
|
||||
@@ -74,24 +74,18 @@ defmodule EEx.Compiler do
|
||||
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp is_empty?(bin) when is_binary(bin) do
|
||||
defp empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
end
|
||||
|
||||
defp is_empty?({ :<>, _, [left, right] }) do
|
||||
is_empty?(left) and is_empty?(right)
|
||||
defp empty?({ :<>, _, [left, right] }) do
|
||||
empty?(left) and empty?(right)
|
||||
end
|
||||
|
||||
defp is_empty?(_) do
|
||||
defp empty?(_) do
|
||||
false
|
||||
end
|
||||
|
||||
# Block wrapping
|
||||
|
||||
defp maybe_block([]), do: nil
|
||||
defp maybe_block([h]), do: h
|
||||
defp maybe_block(other), do: { :__block__, [], other }
|
||||
|
||||
# Changes placeholder to real expression
|
||||
|
||||
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
|
||||
|
||||
@@ -88,7 +88,7 @@ defmodule EEx.AssignsEngine do
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Keyword.get(var!(assigns), unquote(name))
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
|
||||
@@ -7,16 +7,21 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
|
||||
test "evaluates with assigns" do
|
||||
test "evaluates with assigns as a ListDict" do
|
||||
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a HashDict" do
|
||||
assigns = HashDict.new [foo: 1]
|
||||
assert_eval "1", "<%= @foo %>", assigns: assigns
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding // []) do
|
||||
result = EEx.eval_string(actual, binding, file: __FILE__)
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
||||
assert result == expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -321,7 +321,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(__FILE__), line: 7]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(__FILE__), line: 18]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ foo
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual) do
|
||||
result = EEx.eval_string(actual, [], file: __FILE__, engine: EEx.Engine)
|
||||
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
|
||||
assert result == expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
-define(line(Opts), elixir_utils:get_line(Opts)).
|
||||
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be assign, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match for fns
|
||||
context=nil, %% can be match, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match and do_match
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
@@ -18,37 +18,29 @@
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
temp_vars=nil, %% a set of all variables defined in a particular assign
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
match_vars=nil, %% a set of all variables defined in a particular match
|
||||
export_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a counter for the variables defined
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
context_modules=[], %% modules defined in the current context
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
macro_counter=0, %% macros expansions counter
|
||||
lexical_tracker=nil, %% holds the lexical tracker pid
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
file, %% the current scope filename
|
||||
requires, %% a set with modules required
|
||||
macros, %% a list with macros imported from module
|
||||
functions %% a list with functions imported from module
|
||||
counter=[], %% a dict counting the variables defined
|
||||
file=(<<"nofile">>) %% the current scope filename
|
||||
}).
|
||||
|
||||
-record(elixir_env, {
|
||||
module=nil,
|
||||
file=nil,
|
||||
line=nil,
|
||||
function=nil,
|
||||
context=nil,
|
||||
requires,
|
||||
aliases,
|
||||
functions,
|
||||
macros,
|
||||
macro_aliases=[],
|
||||
macro_counter=0,
|
||||
context_modules=[],
|
||||
vars=[],
|
||||
lexical_tracker=nil
|
||||
module=nil, %% the current module
|
||||
file=(<<"nofile">>), %% the current filename
|
||||
line=1, %% the current line
|
||||
function=nil, %% the current function
|
||||
context=nil, %% can be match_vars, guards or nil
|
||||
requires=[], %% a set with modules required
|
||||
aliases=[], %% an orddict with aliases by new -> old names
|
||||
functions=[], %% a list with functions imported from module
|
||||
macros=[], %% a list with macros imported from module
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
context_modules=[], %% modules defined in the current context
|
||||
vars=[], %% a set of defined variables
|
||||
export_vars=nil, %% a set of variables to be exported in certain constructs, may be nil
|
||||
lexical_tracker=nil, %% holds the lexical tracker pid
|
||||
local=nil %% the module to delegate local functions to
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
|
||||
+84
-17
@@ -36,7 +36,7 @@ defmodule Behaviour do
|
||||
`@callback` attributes. `defcallback` is a simple
|
||||
mechanism that defines the `@callback` attribute
|
||||
according to the given type specification. `defcallback` allows
|
||||
documentsion to be created for the callback and defines
|
||||
documentation to be created for the callback and defines
|
||||
a custom function signature.
|
||||
|
||||
The callbacks and their documentation can be retrieved
|
||||
@@ -46,44 +46,60 @@ defmodule Behaviour do
|
||||
@doc """
|
||||
Define a function callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback({ :::, _, [fun, return] }) do
|
||||
do_defcallback(fun, return, __CALLER__)
|
||||
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defcallback(fun, return, guard, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defcallback(fun) do
|
||||
do_defcallback(fun, quote(do: term), __CALLER__)
|
||||
do_defcallback(fun, quote(do: term), [], __CALLER__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback({ :::, _, [fun, return] }) do
|
||||
do_defmacrocallback(fun, return, __CALLER__)
|
||||
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defmacrocallback(fun, return, guard, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defmacrocallback(fun) do
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), __CALLER__)
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, spec, caller) do
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } ->
|
||||
do_callback(:def, name, args, name, length(args), spec, caller)
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_defmacrocallback(fun, spec, caller) do
|
||||
defp do_defmacrocallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args], name, length(args), spec, caller)
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
|
||||
name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, return, caller) do
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
ensure_not_default(left)
|
||||
@@ -95,24 +111,75 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
quote do
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
|
||||
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(kind), unquote(docs_name), unquote(docs_arity)
|
||||
docs_args = unquote(Macro.escape(docs_args))
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
|
||||
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
|
||||
unquote(docs_name), docs_args, unquote(docs_arity))
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_not_default({ ://, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback"
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def store_docs(module, line, kind, name, arity) do
|
||||
def store_docs(module, line, kind, name, args, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
|
||||
|
||||
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
|
||||
{ simplify_signature(arg, ix + 1), [line: line], nil }
|
||||
end)
|
||||
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
|
||||
end
|
||||
|
||||
defp simplify_signature({ :::, _, [var, _] }, i) do
|
||||
simplify_signature(var, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :|, _, [first, _] }, i) do
|
||||
simplify_signature(first, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :., _, [_, name] }, _i) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
|
||||
:"fun#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
|
||||
:"range#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
|
||||
:"bitstring#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
|
||||
:"tuple#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ name, _, args }, _i)
|
||||
when is_atom(name) and (is_atom(args) or is_list(args)) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
|
||||
simplify_signature(ast, i)
|
||||
end
|
||||
|
||||
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
|
||||
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
|
||||
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
|
||||
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
|
||||
defp simplify_signature(_, i), do: :"arg#{i}"
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
||||
+7
-13
@@ -127,7 +127,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp do_eval_string(string, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@@ -162,8 +162,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@@ -229,10 +228,10 @@ defmodule Code do
|
||||
def string_to_quoted(string, opts // []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms(to_char_list(string), line, file, opts)
|
||||
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
|
||||
{ :ok, forms } -> { :ok, forms }
|
||||
_ -> res
|
||||
end
|
||||
end
|
||||
@@ -250,14 +249,9 @@ defmodule Code do
|
||||
def string_to_quoted!(string, opts // []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms!(to_char_list(string), line, file, opts)
|
||||
unpack_quote(line, res)
|
||||
:elixir.string_to_quoted!(to_char_list(string), line, file, opts)
|
||||
end
|
||||
|
||||
defp unpack_quote(_line, []), do: nil
|
||||
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
@@ -353,7 +347,7 @@ defmodule Code do
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
def compile_string(string, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string String.to_char_list!(string), file
|
||||
:elixir_compiler.string to_char_list(string), file
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -364,7 +358,7 @@ defmodule Code do
|
||||
binary.
|
||||
"""
|
||||
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted [quoted], file
|
||||
:elixir_compiler.quoted quoted, file
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc """
|
||||
Default implementations for some required functions in the `Dict` module.
|
||||
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
module, by providing default implementations for some required functions.
|
||||
|
||||
Usage:
|
||||
## Example
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
@@ -13,17 +15,39 @@ defmodule Dict.Behaviour do
|
||||
# override default implementations if needed
|
||||
end
|
||||
|
||||
The client module must contain following functions: `size/1`, `fetch/2`,
|
||||
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
|
||||
the Dict behaviour, so no extra functions are actually required.
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
* `update/4`
|
||||
|
||||
All of which are part of the `Dict` behaviour, so no extra functions are
|
||||
actually required.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for other functions such as `drop`, `take`, etc. All of the functions are
|
||||
defined as overridable, so you can provide your own implementation if
|
||||
needed.
|
||||
for the following functions:
|
||||
|
||||
If you implement `new/0` and `new/1` functions, you can also test your custom
|
||||
module via `Dict` doctests:
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `put_new/3`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `values/1`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide your own
|
||||
implementation if needed.
|
||||
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
def new(keywords // []) do
|
||||
|
||||
+43
-35
@@ -282,24 +282,6 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def chunks(coll, n) do
|
||||
IO.write "Enum.chunks/2 is deprecated, please use Enum.chunk/2 instead\n#{Exception.format_stacktrace}"
|
||||
chunk(coll, n)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def chunks(coll, n, step, pad) do
|
||||
IO.write "Enum.chunks/4 is deprecated, please use Enum.chunk/4 instead\n#{Exception.format_stacktrace}"
|
||||
chunk(coll, n, step, pad)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def chunks_by(coll, fun) do
|
||||
IO.write "Enum.chunks_by/2 is deprecated, please use Enum.chunk_by/2 instead\n#{Exception.format_stacktrace}"
|
||||
chunk_by(coll, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shortcut to `chunk(coll, n, n)`.
|
||||
"""
|
||||
@@ -759,22 +741,15 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first item in the collection or `nil` if the collection is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.first([])
|
||||
nil
|
||||
iex> Enum.first([1, 2, 3])
|
||||
1
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec first(t) :: :nil | element
|
||||
def first([]), do: nil
|
||||
def first([h|_]), do: h
|
||||
def first(list) when is_list(list) do
|
||||
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 or List.first/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.first(list)
|
||||
end
|
||||
|
||||
def first(collection) do
|
||||
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enumerable.reduce(collection, { :cont, nil }, fn(entry, _) ->
|
||||
{ :halt, entry }
|
||||
end) |> elem(1)
|
||||
@@ -784,7 +759,7 @@ defmodule Enum do
|
||||
Returns a new collection appending the result of invoking `fun`
|
||||
on each corresponding item of `collection`.
|
||||
|
||||
The given function should return a list.
|
||||
The given function should return an enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -795,15 +770,48 @@ defmodule Enum do
|
||||
[1, 2, 3, 4, 5, 6]
|
||||
|
||||
"""
|
||||
@spec flat_map(t, (element -> any)) :: list
|
||||
def flat_map([], _fun), do: []
|
||||
|
||||
@spec flat_map(t, (element -> t)) :: list
|
||||
def flat_map(collection, fun) do
|
||||
reduce(collection, [], fn(entry, acc) ->
|
||||
reduce(fun.(entry), acc, &[&1|&2])
|
||||
end) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
Maps and reduces a collection, flattening the given results.
|
||||
|
||||
It expects an accumulator and a function that receives each stream item
|
||||
and an accumulator, and must return a tuple containing a new stream
|
||||
(often a list) with the new accumulator or a tuple with `:halt` as first
|
||||
element and the accumulator as second.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> enum = 1..100
|
||||
iex> n = 3
|
||||
iex> Enum.flat_map_reduce(enum, 0, fn i, acc ->
|
||||
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
|
||||
...> end)
|
||||
{ [1,2,3], 3 }
|
||||
|
||||
"""
|
||||
@spec flat_map_reduce(t, acc, fun) :: list when
|
||||
fun: (element, acc -> { t, acc } | { :halt, acc }),
|
||||
acc: any
|
||||
def flat_map_reduce(collection, acc, fun) do
|
||||
{ _, { list, acc } } =
|
||||
Enumerable.reduce(collection, { :cont, { [], acc } }, fn(entry, { list, acc }) ->
|
||||
case fun.(entry, acc) do
|
||||
{ :halt, acc } ->
|
||||
{ :halt, { list, acc } }
|
||||
{ entries, acc } ->
|
||||
{ :cont, { reduce(entries, list, &[&1|&2]), acc } }
|
||||
end
|
||||
end)
|
||||
|
||||
{ :lists.reverse(list), acc }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Intersperses `element` between each element of the enumeration.
|
||||
|
||||
|
||||
+30
-33
@@ -9,19 +9,22 @@ defexception SystemLimitError, message: "a system limit has been reached"
|
||||
|
||||
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
end
|
||||
end
|
||||
|
||||
@@ -212,33 +215,37 @@ defmodule Exception do
|
||||
|
||||
@doc """
|
||||
Receives a tuple representing a stacktrace entry and formats it.
|
||||
|
||||
The current working directory may be given as an argument,
|
||||
otherwise one is automatically retrieved.
|
||||
"""
|
||||
def format_stacktrace_entry(entry, cwd // nil)
|
||||
def format_stacktrace_entry(entry)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{inspect module} (module)"
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, location }) do
|
||||
format_location(location) <> inspect(module) <> " (module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 1, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}(module)"
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 1, location }) do
|
||||
format_location(location) <> "(module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 1, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}(file)"
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 1, location }) do
|
||||
format_location(location) <> "(file)"
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{format_mfa(module, fun, arity)}"
|
||||
def format_stacktrace_entry({module, fun, arity, location}) do
|
||||
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{format_fa(fun, arity)}"
|
||||
def format_stacktrace_entry({fun, arity, location}) do
|
||||
format_location(location) <> format_fa(fun, arity)
|
||||
end
|
||||
|
||||
defp format_application(module) do
|
||||
case :application.get_application(module) do
|
||||
{ :ok, app } -> "(" <> atom_to_binary(app) <> ") "
|
||||
:undefined -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -255,11 +262,9 @@ defmodule Exception do
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
cwd = System.cwd
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1, cwd)) <> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -330,21 +335,16 @@ defmodule Exception do
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{format_module module}#{fun}/#{arity}"
|
||||
"#{inspect module}.#{fun}/#{arity}"
|
||||
end
|
||||
end
|
||||
|
||||
defp format_module(mod), do: "#{inspect mod}."
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
If any of the values are nil, they are omitted.
|
||||
|
||||
The current working directory may be given as an argument,
|
||||
otherwise one is automatically retrieved.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Exception.format_file_line("foo", 1)
|
||||
@@ -357,11 +357,8 @@ defmodule Exception do
|
||||
""
|
||||
|
||||
"""
|
||||
def format_file_line(file, line, cwd // nil) do
|
||||
def format_file_line(file, line) do
|
||||
if file do
|
||||
file = to_string(file)
|
||||
file = if cwd, do: Path.relative_to(file, cwd), else: Path.relative_to_cwd(file)
|
||||
|
||||
if line && line != 0 do
|
||||
"#{file}:#{line}: "
|
||||
else
|
||||
@@ -372,8 +369,8 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp format_location(opts, cwd) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
|
||||
defp format_location(opts) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line)
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
|
||||
+5
-14
@@ -85,7 +85,7 @@ defmodule File do
|
||||
|
||||
## Examples
|
||||
|
||||
File.regular? __FILE__ #=> true
|
||||
File.regular? __ENV__.file #=> true
|
||||
|
||||
"""
|
||||
def regular?(path) do
|
||||
@@ -649,7 +649,7 @@ defmodule File do
|
||||
do_rm_rf(path, { :ok, [] })
|
||||
end
|
||||
|
||||
defp do_rm_rf(path, { :ok, acc } = entry) do
|
||||
defp do_rm_rf(path, { :ok, _ } = entry) do
|
||||
case safe_list_dir(path) do
|
||||
{ :ok, files } when is_list(files) ->
|
||||
res =
|
||||
@@ -1024,15 +1024,6 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binstream!(file, mode // [], line_or_bytes // :line) do
|
||||
IO.write "File.binstream! is deprecated, simply use File.stream! instead\n" <>
|
||||
Exception.format_stacktrace
|
||||
Stream.resource(fn -> open!(file, mode) end,
|
||||
&IO.do_binstream(&1, line_or_bytes),
|
||||
&F.close/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the unix file `mode` for a given `file`.
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
@@ -1074,7 +1065,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the owner given by the user id `gid`
|
||||
Changes the owner given by the user id `uid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
@@ -1085,8 +1076,8 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chown!(file, gid) do
|
||||
case chown(file, gid) do
|
||||
def chown!(file, uid) do
|
||||
case chown(file, uid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change owner for", path: to_string(file)
|
||||
|
||||
+29
-22
@@ -22,28 +22,40 @@ defmodule Float do
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { float, binary } | :error
|
||||
def parse(binary) when is_binary(binary) do
|
||||
def parse("-" <> binary) do
|
||||
case parse_unsign(binary) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
end
|
||||
end
|
||||
|
||||
def parse(binary) do
|
||||
parse_unsign(binary)
|
||||
end
|
||||
|
||||
defp parse_unsign("-" <> _), do: :error
|
||||
defp parse_unsign(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{ integer_part, after_integer } -> parse after_integer, integer_part
|
||||
{ integer_part, after_integer } -> parse_unsign after_integer, integer_part
|
||||
end
|
||||
end
|
||||
|
||||
# Dot followed by digit is required afterwards or we are done
|
||||
defp parse(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse(rest, char - ?0, 1, int)
|
||||
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse_unsign(rest, char - ?0, 1, int)
|
||||
end
|
||||
|
||||
defp parse(rest, int) do
|
||||
defp parse_unsign(rest, int) do
|
||||
{ :erlang.float(int), rest }
|
||||
end
|
||||
|
||||
# Handle decimal points
|
||||
defp parse(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
end
|
||||
|
||||
defp parse(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
case Integer.parse after_e do
|
||||
:error ->
|
||||
# Note we rebuild the binary here instead of breaking it apart at
|
||||
@@ -56,7 +68,7 @@ defmodule Float do
|
||||
end
|
||||
end
|
||||
|
||||
defp parse(bitstring, float, decimal, int) do
|
||||
defp parse_unsign(bitstring, float, decimal, int) do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
end
|
||||
|
||||
@@ -129,27 +141,22 @@ defmodule Float do
|
||||
|
||||
@doc """
|
||||
Rounds a floating point value to an arbitrary number of fractional digits
|
||||
(between 0 and 15) with an optional midpoint rounding mode (:up or :down,
|
||||
defaults to :up).
|
||||
(between 0 and 15).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.round(5.5674, 3)
|
||||
5.567
|
||||
iex> Float.round(5.5675, 3)
|
||||
5.568
|
||||
iex> Float.round(5.5675, 3, :down)
|
||||
5.567
|
||||
iex> Float.round(-5.5675, 3)
|
||||
iex> Float.round(-5.5674, 3)
|
||||
-5.567
|
||||
iex> Float.round(-5.5675, 3, :down)
|
||||
iex> Float.round(-5.5675, 3)
|
||||
-5.568
|
||||
"""
|
||||
@spec round(float, integer, atom | nil) :: float
|
||||
def round(number, precision, midpoint_rounding // :up) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
# Default to :up if anything but :down is provided for midpoint rounding mode
|
||||
case midpoint_rounding do
|
||||
:down -> Kernel.round(Float.floor(number * :math.pow(10, precision))) / :math.pow(10, precision)
|
||||
_ -> Kernel.round(Float.ceil(number * :math.pow(10, precision))) / :math.pow(10, precision)
|
||||
end
|
||||
@spec round(float, integer) :: float
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -25,7 +25,6 @@ defmodule GenEvent.Behaviour do
|
||||
def handle_call(:notifications, notifications) do
|
||||
{:ok, Enum.reverse(notifications), []}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
|
||||
@@ -21,78 +21,67 @@ defmodule GenFSM.Behaviour do
|
||||
works - we hereby declare a full disclaimer for any lawsuits that the
|
||||
behaviour of the CVM in its original state might encur.
|
||||
|
||||
|
||||
defmodule MyFsm do
|
||||
use GenFSM.Behaviour
|
||||
|
||||
# keeping track of what is going on inside the CVM.
|
||||
# 3 is the target price for a cup of coffee
|
||||
defrecord StateData, coins: 0, price: 3
|
||||
# keeping track of what is going on inside the CVM.
|
||||
# 3 is the target price for a cup of coffee
|
||||
defrecord StateData, coins: 0, price: 3
|
||||
|
||||
#
|
||||
# API functions
|
||||
#
|
||||
# API functions
|
||||
|
||||
def start_link() do
|
||||
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin() do
|
||||
:gen_fsm.send_event(:cvm, :coin)
|
||||
end
|
||||
|
||||
def request_coffee() do
|
||||
:gen_fsm.send_event(:cvm, :request_coffee)
|
||||
end
|
||||
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
|
||||
def short_paid(:coin, state_data = StateData[coins: c, price: p])
|
||||
when c + 1 < p do
|
||||
{ :next_state, :short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
|
||||
def requested_short_paid(:request_coffee, state_data) do
|
||||
{:next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
|
||||
when c+1 < p do
|
||||
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
def start_link() do
|
||||
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin() do
|
||||
:gen_fsm.send_event(:cvm, :coin)
|
||||
end
|
||||
|
||||
def request_coffee() do
|
||||
:gen_fsm.send_event(:cvm, :request_coffee)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
|
||||
{ :next_state, :short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
|
||||
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, _pid } = MyFsm.start_link()
|
||||
|
||||
@@ -138,15 +127,15 @@ defmodule GenFSM.Behaviour do
|
||||
for you. The list of callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the FSM is started;
|
||||
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
|
||||
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
|
||||
handle `sync_send_all_state_event` messages;
|
||||
* `handle_event(event, state_name, state_data)` - invoked to handle
|
||||
* `handle_event(event, state_name, state_data)` - invoked to handle
|
||||
`send_all_state_event` messages;
|
||||
* `handle_info(msg, state_name, state_data)` - handle all other
|
||||
* `handle_info(msg, state_name, state_data)` - handle all other
|
||||
messages which are normally received by processes;
|
||||
* `terminate(reason, state_name, state_data)` - called when the FSM
|
||||
* `terminate(reason, state_name, state_data)` - called when the FSM
|
||||
is about to terminate, useful for cleaning up;
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
|
||||
@@ -154,9 +143,9 @@ defmodule GenFSM.Behaviour do
|
||||
|
||||
For each state you need to define either or both of these:
|
||||
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
`send_event` messages;
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
`sync_send_event` messages;
|
||||
|
||||
If you send asynchronous events you only need to implement the
|
||||
@@ -180,13 +169,13 @@ defmodule GenFSM.Behaviour do
|
||||
@behavior :gen_fsm
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state_name, state_data) do
|
||||
{ :next_state, state_name, state_data }
|
||||
def handle_event(event, state_name, state_data) do
|
||||
{ :stop, {:bad_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_sync_event(_event, from, state_name, state_data) do
|
||||
{ :reply, :ok, state_name, state_data }
|
||||
def handle_sync_event(event, from, state_name, state_data) do
|
||||
{ :stop, {:bad_sync_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -94,8 +94,8 @@ defmodule GenServer.Behaviour do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, _from, state) do
|
||||
{ :noreply, state }
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -104,8 +104,8 @@ defmodule GenServer.Behaviour do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(_msg, state) do
|
||||
{ :noreply, state }
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
+64
-28
@@ -1,7 +1,13 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
|
||||
defrecord Inspect.Opts,
|
||||
records: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
pretty: false,
|
||||
width: 80
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
@@ -13,11 +19,9 @@ defprotocol Inspect do
|
||||
followed by the inspecting options, represented by the record
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in
|
||||
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
|
||||
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
|
||||
receives an `Inspect.Opts` record as the second argument, it returns
|
||||
the underlying algebra document instead of the formatted string.
|
||||
Inspection is done using the functions available in `Inspect.Algebra`.
|
||||
|
||||
## Examples
|
||||
|
||||
Many times, inspecting a structure can be implemented in function
|
||||
of existing entities. For example, here is `HashSet`'s `inspect`
|
||||
@@ -27,7 +31,7 @@ defprotocol Inspect do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
|
||||
concat ["#HashSet<", to_doc(HashSet.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,14 +39,24 @@ defprotocol Inspect do
|
||||
concatenates algebra documents together. In the example above,
|
||||
it is concatenating the string `"HashSet<"` (all strings are
|
||||
valid algebra documents that keep their formatting when pretty
|
||||
printed), the document returned by `Kernel.inspect/2` and the
|
||||
printed), the document returned by `Inspect.Algebra.to_doc/2` and the
|
||||
other string `">"`.
|
||||
|
||||
Since regular strings are valid entities in an algebra document,
|
||||
an implementation of inspect may simply return a string,
|
||||
although that will devoid it of any pretty-printing.
|
||||
"""
|
||||
|
||||
## Error handling
|
||||
|
||||
In case there is an error while your structure is being inspected,
|
||||
Elixir will automatically fall back to tuple inspection for records.
|
||||
You can however access the underlying error by invoking the Inspect
|
||||
implementation directly. For example, to test Inspect.HashSet above,
|
||||
you just need to do:
|
||||
|
||||
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
|
||||
|
||||
"""
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
@@ -86,7 +100,7 @@ defimpl Inspect, for: Atom do
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:{}, :[], :<<>>] ->
|
||||
atom in [:{}, :<<>>] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
@@ -150,8 +164,8 @@ defimpl Inspect, for: BitString do
|
||||
"<<0, 1, 2>>"
|
||||
|
||||
"""
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(thing) do
|
||||
def inspect(thing, Inspect.Opts[binaries: bins] = opts) when is_binary(thing) do
|
||||
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
|
||||
<< ?", escape(thing, ?") :: binary, ?" >>
|
||||
else
|
||||
inspect_bitstring(thing, opts)
|
||||
@@ -205,11 +219,29 @@ defimpl Inspect, for: BitString do
|
||||
defp escape(<<?\v, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp escape(<<h :: utf8, t :: binary>>, char, binary) do
|
||||
head = << h :: utf8 >>
|
||||
if String.printable?(head) do
|
||||
escape(t, char, append(head, binary))
|
||||
else
|
||||
<< byte :: size(8), h :: binary >> = head
|
||||
t = << h :: binary, t :: binary >>
|
||||
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
|
||||
end
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, h >>)
|
||||
escape(t, char, << binary :: binary, octify(h) :: binary >>)
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
defp octify(byte) do
|
||||
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
|
||||
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
|
||||
end
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
@@ -262,21 +294,21 @@ defimpl Inspect, for: List do
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, Inspect.Opts[] = opts) do
|
||||
def inspect(thing, Inspect.Opts[char_lists: lists] = opts) do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
lists == :as_char_lists or (lists == :infer and :io_lib.printable_list(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) && not opts.raw ->
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, &Kernel.inspect(&1, opts))
|
||||
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
Kernel.inspect(value, opts)
|
||||
to_doc(value, opts)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -314,20 +346,24 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
unless opts.raw do
|
||||
def inspect(tuple, Inspect.Opts[] = opts) do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
else
|
||||
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
defp record_inspect(record, Inspect.Opts[] = opts) do
|
||||
[name|tail] = tuple_to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
surround_record(name, fields, tail, opts)
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
else
|
||||
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
@@ -342,7 +378,7 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, opts) do
|
||||
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
@@ -361,7 +397,7 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
|
||||
defp keyword({ k, v }, opts) do
|
||||
concat(k <> ": ", Kernel.inspect(v, opts))
|
||||
concat(k <> ": ", to_doc(v, opts))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -410,11 +446,11 @@ defimpl Inspect, for: Regex do
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Kernel.inspect(other, opts.raw(true))
|
||||
def inspect(other, Inspect.Opts[] = opts) do
|
||||
to_doc(other, opts.records(false))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -100,6 +100,27 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts an Elixir structure to an algebra document
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
|
||||
if is_tuple(arg) do
|
||||
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
catch
|
||||
_, _ -> Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
else
|
||||
Inspect.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
|
||||
@@ -1,75 +1,90 @@
|
||||
defmodule IEx.ANSIDocs do
|
||||
defmodule IO.ANSI.Docs do
|
||||
@moduledoc false
|
||||
|
||||
@bullets [?*, ?-, ?+]
|
||||
|
||||
@doc """
|
||||
Prints the head of the documentation (i.e. the function signature)
|
||||
"""
|
||||
def print_heading(string, use_ansi // IO.ANSI.terminal?) do
|
||||
if use_ansi do
|
||||
write_doc_heading(string)
|
||||
else
|
||||
IO.puts "* #{string}\n"
|
||||
end
|
||||
IEx.dont_display_result
|
||||
end
|
||||
@default_colors [ enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline" ]
|
||||
|
||||
defp write_doc_heading(heading) do
|
||||
@doc """
|
||||
Prints the head of the documentation (i.e. the function signature).
|
||||
|
||||
See `print/3` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, colors // @default_colors) do
|
||||
IO.puts IO.ANSI.reset
|
||||
width = column_width()
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading)
|
||||
write(:doc_title, heading, colors)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a truth value for whether to use ANSI
|
||||
escape codes, and a keyword list for the printing color settings. Supported
|
||||
keys for the color settings are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
|
||||
Values for the color settings are strings with comma-separated attributes.
|
||||
Supported attributes are:
|
||||
|
||||
* Colors: `black red green yellow blue magenta cyan white`
|
||||
* Intensity: `normal bright`
|
||||
* Decoration: `underline reverse`
|
||||
"""
|
||||
def print(doc, use_ansi // IO.ANSI.terminal?) do
|
||||
if use_ansi do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("")
|
||||
else
|
||||
IO.puts doc
|
||||
end
|
||||
IEx.dont_display_result
|
||||
def print(doc, colors // @default_colors) do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("", colors)
|
||||
end
|
||||
|
||||
defp process([], _indent), do: nil
|
||||
defp process([], _indent, _colors), do: nil
|
||||
|
||||
defp process(["# " <> heading | rest], _indent) do
|
||||
write_h1(String.strip(heading))
|
||||
process(rest, "")
|
||||
defp process(["# " <> heading | rest], _indent, colors) do
|
||||
write_h1(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
end
|
||||
|
||||
defp process(["## " <> heading | rest], _indent) do
|
||||
write_h2(String.strip(heading))
|
||||
process(rest, "")
|
||||
defp process(["## " <> heading | rest], _indent, colors) do
|
||||
write_h2(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
end
|
||||
|
||||
defp process(["### " <> heading | rest], indent) do
|
||||
write_h3(String.strip(heading), indent)
|
||||
process(rest, indent)
|
||||
defp process(["### " <> heading | rest], indent, colors) do
|
||||
write_h3(String.strip(heading), indent, colors)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp process(["" | rest], indent) do
|
||||
process(rest, indent)
|
||||
defp process(["" | rest], indent, colors) do
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp process([" " <> line | rest], indent) do
|
||||
process_code(rest, [line], indent)
|
||||
defp process([" " <> line | rest], indent, colors) do
|
||||
process_code(rest, [line], indent, colors)
|
||||
end
|
||||
|
||||
defp process([line | rest], indent) do
|
||||
defp process([line | rest], indent, colors) do
|
||||
{ stripped, count } = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
|
||||
process_list(item, rest, count, indent)
|
||||
process_list(item, rest, count, indent, colors)
|
||||
_ ->
|
||||
process_text(rest, [line], indent, false)
|
||||
process_text(rest, [line], indent, false, colors)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,27 +98,27 @@ defmodule IEx.ANSIDocs do
|
||||
|
||||
## Headings
|
||||
|
||||
defp write_h1(heading) do
|
||||
write_h2(String.upcase(heading))
|
||||
defp write_h1(heading, colors) do
|
||||
write_h2(String.upcase(heading), colors)
|
||||
end
|
||||
|
||||
defp write_h2(heading) do
|
||||
write(:doc_headings, heading)
|
||||
defp write_h2(heading, colors) do
|
||||
write(:doc_headings, heading, colors)
|
||||
end
|
||||
|
||||
defp write_h3(heading, indent) do
|
||||
defp write_h3(heading, indent, colors) do
|
||||
IO.write(indent)
|
||||
write(:doc_headings, heading)
|
||||
write(:doc_headings, heading, colors)
|
||||
end
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_list(line, rest, count, indent) do
|
||||
defp process_list(line, rest, count, indent, colors) do
|
||||
IO.write indent <> "• "
|
||||
{ contents, rest, done } = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true)
|
||||
process_text(contents, [line], indent <> " ", true, colors)
|
||||
if done, do: IO.puts(IO.ANSI.reset)
|
||||
process(rest, indent)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
# Process the thing after a list item entry. It can be either:
|
||||
@@ -141,35 +156,35 @@ defmodule IEx.ANSIDocs do
|
||||
|
||||
## Text (paragraphs / lists)
|
||||
|
||||
defp process_text(doc=["" | _], para, indent, from_list) do
|
||||
write_text(Enum.reverse(para), indent, from_list)
|
||||
process(doc, indent)
|
||||
defp process_text(doc=["" | _], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
process(doc, indent, colors)
|
||||
end
|
||||
|
||||
defp process_text([], para, indent, from_list) do
|
||||
write_text(Enum.reverse(para), indent, from_list)
|
||||
defp process_text([], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, true) do
|
||||
defp process_text([line | rest], para, indent, true, colors) do
|
||||
{ stripped, count } = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(Enum.reverse(para), indent, true)
|
||||
process_list(item, rest, count, indent)
|
||||
write_text(Enum.reverse(para), indent, true, colors)
|
||||
process_list(item, rest, count, indent, colors)
|
||||
_ ->
|
||||
process_text(rest, [line | para], indent, true)
|
||||
process_text(rest, [line | para], indent, true, colors)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, from_list) do
|
||||
process_text(rest, [line | para], indent, from_list)
|
||||
defp process_text([line | rest], para, indent, from_list, colors) do
|
||||
process_text(rest, [line | para], indent, from_list, colors)
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, from_list) do
|
||||
defp write_text(lines, indent, from_list, colors) do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [])
|
||||
|> handle_inline(nil, [], [], colors)
|
||||
|> String.split(%r{\s})
|
||||
|> write_with_wrap(column_width() - size(indent), indent, from_list)
|
||||
|
||||
@@ -178,32 +193,32 @@ defmodule IEx.ANSIDocs do
|
||||
|
||||
## Code blocks
|
||||
|
||||
defp process_code([], code, indent) do
|
||||
write_code(code, indent)
|
||||
defp process_code([], code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
end
|
||||
|
||||
# Blank line between code blocks
|
||||
defp process_code([ "", " " <> line | rest ], code, indent) do
|
||||
process_code(rest, [line, "" | code], indent)
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line, "" | code], indent, colors)
|
||||
end
|
||||
|
||||
defp process_code([ " " <> line | rest ], code, indent) do
|
||||
process_code(rest, [line|code], indent)
|
||||
defp process_code([ " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line|code], indent, colors)
|
||||
end
|
||||
|
||||
defp process_code(rest, code, indent) do
|
||||
write_code(code, indent)
|
||||
process(rest, indent)
|
||||
defp process_code(rest, code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp write_code(code, indent) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}")
|
||||
defp write_code(code, indent, colors) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", colors)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp write(style, string) do
|
||||
IO.puts IEx.color(style, string)
|
||||
defp write(style, string, colors) do
|
||||
IO.puts color(style, colors) <> string <> IO.ANSI.reset
|
||||
IO.puts IO.ANSI.reset
|
||||
end
|
||||
|
||||
@@ -249,8 +264,8 @@ defmodule IEx.ANSIDocs do
|
||||
|
||||
defp length_without_escape(rest, count) do
|
||||
case String.next_grapheme(rest) do
|
||||
:no_grapheme -> count
|
||||
{ _, rest } -> length_without_escape(rest, count + 1)
|
||||
nil -> count
|
||||
end
|
||||
end
|
||||
|
||||
@@ -280,81 +295,77 @@ defmodule IEx.ANSIDocs do
|
||||
@spaced [?_, ?*]
|
||||
|
||||
# Clauses for handling spaces
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc)
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc)
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, colors) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc)
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc)
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, colors) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc, colors)
|
||||
end
|
||||
|
||||
# Clauses for handling escape
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc)
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc)
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, colors)
|
||||
end
|
||||
|
||||
# A escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc)
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, colors)
|
||||
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark|buffer], acc)
|
||||
handle_inline(rest, limit, [mark|buffer], acc, colors)
|
||||
end
|
||||
|
||||
# Inline start
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc])
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, colors) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc])
|
||||
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, colors) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], colors)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer)|acc])
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc) when mark in @single do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer)|acc])
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, colors) when mark in @single do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc) do
|
||||
handle_inline(rest, mark, [char|buffer], acc)
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, colors) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<>>, _mark, buffer, acc) do
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _colors) do
|
||||
iolist_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
end
|
||||
|
||||
defp inline_buffer(buffer) do
|
||||
defp inline_buffer(buffer, colors) do
|
||||
[h|t] = Enum.reverse([IO.ANSI.reset|buffer])
|
||||
[color_for(h)|t]
|
||||
[color_for(h, colors)|t]
|
||||
end
|
||||
|
||||
defp color_for("`"), do: color(:doc_inline_code)
|
||||
defp color_for("_"), do: color(:doc_underline)
|
||||
defp color_for("*"), do: color(:doc_bold)
|
||||
defp color_for("**"), do: color(:doc_bold)
|
||||
defp color_for("`", colors), do: color(:doc_inline_code, colors)
|
||||
defp color_for("_", colors), do: color(:doc_underline, colors)
|
||||
defp color_for("*", colors), do: color(:doc_bold, colors)
|
||||
defp color_for("**", colors), do: color(:doc_bold, colors)
|
||||
|
||||
defp color(color_name) do
|
||||
colors = IEx.Options.get(:colors)
|
||||
|
||||
if colors[:enabled] do
|
||||
IO.ANSI.escape_fragment("%{#{colors[color_name]}}", true)
|
||||
else
|
||||
""
|
||||
end
|
||||
defp color(style, colors) do
|
||||
color = colors[style]
|
||||
enabled = colors[:enabled]
|
||||
IO.ANSI.escape_fragment("%{#{color}}", enabled)
|
||||
end
|
||||
|
||||
defp column_width() do
|
||||
+132
-159
@@ -506,12 +506,10 @@ defmodule Kernel do
|
||||
:erlang.iolist_to_binary(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if the local node is alive; that is,
|
||||
if the node can be part of a distributed system.
|
||||
"""
|
||||
@doc false
|
||||
@spec is_alive :: boolean
|
||||
def is_alive do
|
||||
IO.write "is_alive/0 is deprecated, please use Node.alive?/0 instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.is_alive
|
||||
end
|
||||
|
||||
@@ -868,6 +866,24 @@ defmodule Kernel do
|
||||
:erlang.round(number)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a message to the given `dest` and returns the message.
|
||||
|
||||
`dest` may be a remote or local pid, a (local) port, a locally
|
||||
registered name, or a tuple `{registed_name, node}` for a registered
|
||||
name at another node.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> send self(), :hello
|
||||
:hello
|
||||
|
||||
"""
|
||||
@spec send(dest :: pid | port | atom | { atom, node }, msg) :: msg when msg: any
|
||||
def send(dest, msg) do
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid (process identifier) of the calling process.
|
||||
Allowed in guard clauses.
|
||||
@@ -895,7 +911,7 @@ defmodule Kernel do
|
||||
## Examples
|
||||
|
||||
current = Kernel.self
|
||||
child = spawn(fn -> current <- { Kernel.self, 1 + 2 } end)
|
||||
child = spawn(fn -> send current, { Kernel.self, 1 + 2 } end)
|
||||
|
||||
receive do
|
||||
{ ^child, 3 } -> IO.puts "Received 3 back"
|
||||
@@ -933,7 +949,7 @@ defmodule Kernel do
|
||||
## Examples
|
||||
|
||||
current = Kernel.self
|
||||
child = spawn_link(fn -> current <- { Kernel.self, 1 + 2 } end)
|
||||
child = spawn_link(fn -> send current, { Kernel.self, 1 + 2 } end)
|
||||
|
||||
receive do
|
||||
{ ^child, 3 } -> IO.puts "Received 3 back"
|
||||
@@ -1120,19 +1136,10 @@ defmodule Kernel do
|
||||
quote do: __op__(:/, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a message to the process identified on the left.
|
||||
A process can be identified by its PID or, if it is registered,
|
||||
by an atom.
|
||||
|
||||
## Examples
|
||||
|
||||
process = Kernel.self
|
||||
process <- { :ok, "Sending myself a message" }
|
||||
|
||||
"""
|
||||
defmacro pid <- msg do
|
||||
quote do: :erlang.!(unquote(pid), unquote(msg))
|
||||
@doc false
|
||||
def pid <- msg do
|
||||
IO.write "<-/2 is deprecated, please use send/2 instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.!(pid, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1423,25 +1430,19 @@ defmodule Kernel do
|
||||
# Extracts concatenations in order to optimize many
|
||||
# concatenations into one single clause.
|
||||
defp extract_concatenations({ :<>, _, [left, right] }) do
|
||||
wrap_concatenation(left) ++ extract_concatenations(right)
|
||||
[wrap_concatenation(left)|extract_concatenations(right)]
|
||||
end
|
||||
|
||||
defp extract_concatenations(other) do
|
||||
wrap_concatenation(other)
|
||||
[wrap_concatenation(other)]
|
||||
end
|
||||
|
||||
# If it is a binary, we don't need to add the binary
|
||||
# tag. This allows us to use <> on pattern matching.
|
||||
defp wrap_concatenation(binary) when is_binary(binary) do
|
||||
[binary]
|
||||
end
|
||||
|
||||
defp wrap_concatenation({ :<<>>, _, parts }) do
|
||||
parts
|
||||
binary
|
||||
end
|
||||
|
||||
defp wrap_concatenation(other) do
|
||||
[{ :::, [], [other, { :binary, [], nil }] }]
|
||||
{ :::, [], [other, { :binary, [], nil }] }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1714,17 +1715,24 @@ defmodule Kernel do
|
||||
The second argument is a keywords list with options to control
|
||||
inspection.
|
||||
|
||||
The second argument may also be an instance of the `Inspect.Opts`
|
||||
record and, in such cases, instead of returning a string, it
|
||||
returns an algebra document which can be converted to a string
|
||||
via `Inspect.Algebra`.
|
||||
|
||||
## Options
|
||||
|
||||
The following options are supported:
|
||||
|
||||
* `:raw` - when true, record tuples are not formatted by the inspect protocol,
|
||||
but are printed as just tuples, defaults to false;
|
||||
* `:records` - when false, records are not formatted by the inspect protocol,
|
||||
they are instead printed as just tuples, defaults to true;
|
||||
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped; when `:as_binaries` all
|
||||
binaries will be printed in bit syntax; when the default
|
||||
`:infer`, the binary will be printed as a string if it is
|
||||
printable, otherwise in bit syntax;
|
||||
|
||||
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
|
||||
non-printable elements will be escaped; when `:as_lists` all
|
||||
lists will be printed as lists; when the default `:infer`, the
|
||||
list will be printed as a char list if it is printable,
|
||||
otherwise as list;
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
|
||||
and lists, does not apply to strings nor char lists, defaults to 50;
|
||||
@@ -1745,9 +1753,18 @@ defmodule Kernel do
|
||||
iex> inspect(ArgumentError[])
|
||||
"ArgumentError[message: \"argument error\"]"
|
||||
|
||||
iex> inspect(ArgumentError[], raw: true)
|
||||
iex> inspect(ArgumentError[], records: false)
|
||||
"{ArgumentError, :__exception__, \"argument error\"}"
|
||||
|
||||
iex> inspect("josé" <> <<0>>)
|
||||
"<<106, 111, 115, 195, 169, 0>>"
|
||||
|
||||
iex> inspect("josé" <> <<0>>, binaries: :as_strings)
|
||||
"\"josé\\000\""
|
||||
|
||||
iex> inspect("josé", binaries: :as_binaries)
|
||||
"<<106, 111, 115, 195, 169>>"
|
||||
|
||||
Note that the inspect protocol does not necessarily return a valid
|
||||
representation of an Elixir term. In such cases, the inspected result
|
||||
must start with `#`. For example, inspecting a function will return:
|
||||
@@ -1756,25 +1773,29 @@ defmodule Kernel do
|
||||
#=> #Function<...>
|
||||
|
||||
"""
|
||||
@spec inspect(Inspect.t, Inspect.Opts.t) :: Inspect.Algebra.t
|
||||
@spec inspect(Inspect.t, Keyword.t) :: String.t
|
||||
def inspect(arg, opts // [])
|
||||
|
||||
def inspect(arg, opts) when is_tuple(opts) and tuple_size(opts) > 0 and
|
||||
elem(opts, 0) == Inspect.Opts do
|
||||
case is_tuple(arg) and elem(opts, 1) do
|
||||
true -> Inspect.Tuple.inspect(arg, opts)
|
||||
false -> Inspect.inspect(arg, opts)
|
||||
end
|
||||
def inspect(arg, opts) when is_tuple(opts) and tuple_size(opts) > 0 and elem(opts, 0) == Inspect.Opts do
|
||||
IO.write "Kernel.inspect/2 with Inspect.Opts is deprecated, please use Inspect.Algebra.to_doc/2 instead\n#{Exception.format_stacktrace}"
|
||||
Inspect.Algebra.to_doc(arg, opts)
|
||||
end
|
||||
|
||||
def inspect(arg, opts) when is_list(opts) do
|
||||
opts = Inspect.Opts.new(opts)
|
||||
|
||||
case opts.pretty do
|
||||
true -> Inspect.Algebra.pretty(inspect(arg, opts), opts.width)
|
||||
false -> Inspect.Algebra.pretty(inspect(arg, opts), :infinity)
|
||||
case Keyword.get(opts, :raw) do
|
||||
nil ->
|
||||
:ok
|
||||
raw ->
|
||||
IO.write "Kernel.inspect/2 with :raw option is deprecated, please use :records instead\n#{Exception.format_stacktrace}"
|
||||
opts = opts ++ [records: not raw]
|
||||
end
|
||||
|
||||
opts = Inspect.Opts.new(opts)
|
||||
limit = case opts.pretty do
|
||||
true -> opts.width
|
||||
false -> :infinity
|
||||
end
|
||||
Inspect.Algebra.pretty(Inspect.Algebra.to_doc(arg, opts), limit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1863,8 +1884,6 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defmacro match?(left, right) do
|
||||
{ left, _ } = kernelfy_var(left, [], &kernelfy_all/2)
|
||||
|
||||
quote do
|
||||
case unquote(right) do
|
||||
unquote(left) ->
|
||||
@@ -1875,51 +1894,6 @@ defmodule Kernel do
|
||||
end
|
||||
end
|
||||
|
||||
defp kernelfy_all({ var, meta, scope }, acc) when is_atom(var) and is_atom(scope) do
|
||||
{ { var, meta, Kernel }, [{ var, scope }|acc] }
|
||||
end
|
||||
|
||||
defp kernelfy_selected({ var, meta, scope } = original, acc) when is_atom(var) and is_atom(scope) do
|
||||
case :lists.member({ var, scope }, acc) do
|
||||
true -> { { var, meta, Kernel }, acc }
|
||||
false -> { original, acc }
|
||||
end
|
||||
end
|
||||
|
||||
defp kernelfy_var({ :^, _, [_] } = contents, acc, _fun) do
|
||||
{ contents, acc }
|
||||
end
|
||||
|
||||
defp kernelfy_var({ :when, meta, [left, right] }, acc, fun) do
|
||||
{ left, acc } = kernelfy_var(left, acc, fun)
|
||||
{ right, acc } = kernelfy_var(right, acc, &kernelfy_selected/2)
|
||||
{ { :when, meta, [left, right] }, acc }
|
||||
end
|
||||
|
||||
defp kernelfy_var({ var, _, scope } = original, acc, fun) when is_atom(var) and is_atom(scope) do
|
||||
fun.(original, acc)
|
||||
end
|
||||
|
||||
defp kernelfy_var({ left, meta, right }, acc, fun) do
|
||||
{ left, acc } = kernelfy_var(left, acc, fun)
|
||||
{ right, acc } = kernelfy_var(right, acc, fun)
|
||||
{ { left, meta, right }, acc }
|
||||
end
|
||||
|
||||
defp kernelfy_var({ left, right }, acc, fun) do
|
||||
{ left, acc } = kernelfy_var(left, acc, fun)
|
||||
{ right, acc } = kernelfy_var(right, acc, fun)
|
||||
{ { left, right }, acc }
|
||||
end
|
||||
|
||||
defp kernelfy_var(list, acc, fun) when is_list(list) do
|
||||
:lists.mapfoldl(&kernelfy_var(&1, &2, fun), acc, list)
|
||||
end
|
||||
|
||||
defp kernelfy_var(other, acc, _fun) do
|
||||
{ other, acc }
|
||||
end
|
||||
|
||||
@doc """
|
||||
This macro is a shortcut to read and add attributes to the module
|
||||
being compiled. Elixir module attributes are similar to Erlang's with
|
||||
@@ -2160,7 +2134,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Evaluates the expression corresponding to the first clause that
|
||||
evaluates to true. Raises an error if all conditions evaluate to
|
||||
to falsy values (nil or false).
|
||||
to nil or false.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2172,9 +2146,10 @@ defmodule Kernel do
|
||||
true ->
|
||||
"This will"
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro cond([do: { :->, _, pairs }]) do
|
||||
[{ [condition], meta, clause }|t] = :lists.reverse pairs
|
||||
defmacro cond([do: pairs]) do
|
||||
[{ :->, meta, [[condition], clause] }|t] = :lists.reverse pairs
|
||||
|
||||
new_acc =
|
||||
case condition do
|
||||
@@ -2205,35 +2180,27 @@ defmodule Kernel do
|
||||
# end
|
||||
# end
|
||||
#
|
||||
defp build_cond_clauses([{ [condition], new, clause }|t], acc, old) do
|
||||
stab = { :->, [], [falsy_clause(old, acc), truthy_clause(new, clause)] }
|
||||
acc = quote do
|
||||
case unquote(condition), do: unquote(stab)
|
||||
end
|
||||
defp build_cond_clauses([{ :->, new, [[condition], clause] }|t], acc, old) do
|
||||
clauses = [falsy_clause(old, acc), truthy_clause(new, clause)]
|
||||
acc = quote do: (case unquote(condition), do: unquote(clauses))
|
||||
build_cond_clauses(t, acc, new)
|
||||
end
|
||||
|
||||
defp build_cond_clauses([], acc, _), do: acc
|
||||
|
||||
defp falsy_clause(meta, acc) do
|
||||
{ [quote(do: unquote(cond_var) when unquote(cond_var) in [false, nil])], meta, acc }
|
||||
{ :->, meta, [[quote(do: unquote(cond_var) when unquote(cond_var) in [false, nil])], acc] }
|
||||
end
|
||||
|
||||
defp truthy_clause(meta, clause) do
|
||||
{ [quote(do: _)], meta, clause }
|
||||
{ :->, meta, [[quote(do: _)], clause] }
|
||||
end
|
||||
|
||||
# Setting cond: true in metadata turns on a small optimization
|
||||
# in Elixir compiler. In the long run, we want to bring this
|
||||
# optimization to Elixir land, but not right now.
|
||||
defp cond_var do
|
||||
{ :x, [cond: true, temp: true], Kernel }
|
||||
end
|
||||
|
||||
# A temporary var only lasts its current clause, never leak
|
||||
# into other clauses.
|
||||
defp temp_var do
|
||||
{ :x, [temp: true], Kernel }
|
||||
{ :x, [cond: true], Kernel }
|
||||
end
|
||||
|
||||
defp get_line(meta) do
|
||||
@@ -2297,15 +2264,17 @@ defmodule Kernel do
|
||||
it will raise a CaseClauseError.
|
||||
"""
|
||||
defmacro destructure(left, right) when is_list(left) do
|
||||
List.foldl left, right, fn item, acc ->
|
||||
quote do
|
||||
case unquote(acc) do
|
||||
[unquote(item)|t] ->
|
||||
t
|
||||
other when other == [] or other == nil ->
|
||||
unquote(item) = nil
|
||||
Enum.reduce left, right, fn item, acc ->
|
||||
{ :case, meta, args } =
|
||||
quote do
|
||||
case unquote(acc) do
|
||||
[unquote(item)|t] ->
|
||||
t
|
||||
other when other == [] or other == nil ->
|
||||
unquote(item) = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
{ :case, [{:export_all,true}|meta], args }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2353,8 +2322,8 @@ defmodule Kernel do
|
||||
defmacro left && right do
|
||||
quote do
|
||||
case unquote(left) do
|
||||
unquote(temp_var) when unquote(temp_var) in [false, nil] ->
|
||||
unquote(temp_var)
|
||||
x when x in [false, nil] ->
|
||||
x
|
||||
_ ->
|
||||
unquote(right)
|
||||
end
|
||||
@@ -2384,10 +2353,10 @@ defmodule Kernel do
|
||||
defmacro left || right do
|
||||
quote do
|
||||
case unquote(left) do
|
||||
unquote(temp_var) when unquote(temp_var) in [false, nil] ->
|
||||
x when x in [false, nil] ->
|
||||
unquote(right)
|
||||
unquote(temp_var) ->
|
||||
unquote(temp_var)
|
||||
x ->
|
||||
x
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2407,7 +2376,7 @@ defmodule Kernel do
|
||||
|
||||
Enum.map(List.flatten([1, [2], 3]), &(&1 * 2))
|
||||
|
||||
Be aware of operator precendence when using this operator.
|
||||
Be aware of operator precedence when using this operator.
|
||||
For example, the following expression:
|
||||
|
||||
String.graphemes "Hello" |> Enum.reverse
|
||||
@@ -2531,14 +2500,14 @@ defmodule Kernel do
|
||||
end
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
# We first try to call __access__ and just then check if
|
||||
# We first try to call __record__ and just then check if
|
||||
# it is loaded so we allow the ParallelCompiler to solve
|
||||
# conflicts.
|
||||
case :code.ensure_loaded(atom) do
|
||||
{ :error, _ } ->
|
||||
:elixir_aliases.ensure_loaded(caller.line, caller.file, atom, caller.context_modules)
|
||||
:elixir_aliases.ensure_loaded(caller.line, atom, :elixir_env.ex_to_env(caller))
|
||||
_ ->
|
||||
raise ArgumentError, message: "cannot use module #{inspect atom} in access protocol because it does not export __record__/1"
|
||||
raise ArgumentError, message: "cannot access module #{inspect atom} because it is not a record"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2795,7 +2764,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
{ escaped, _ } = :elixir_quote.escape(block, false)
|
||||
module_vars = module_vars(env_vars(env))
|
||||
module_vars = module_vars(env_vars(env), 0)
|
||||
|
||||
quote do
|
||||
unquote(with_alias)
|
||||
@@ -2830,12 +2799,7 @@ defmodule Kernel do
|
||||
do: :elixir_aliases.concat([env.module, module])
|
||||
|
||||
# quote vars to be injected into the module definition
|
||||
defp module_vars(vars) do
|
||||
{ vars, _ } = :lists.mapfoldl(&module_var_to_tuple/2, 0, vars)
|
||||
vars
|
||||
end
|
||||
|
||||
defp module_var_to_tuple({ key, kind }, counter) do
|
||||
defp module_vars([{ key, kind }|vars], counter) do
|
||||
var =
|
||||
case is_atom(kind) do
|
||||
true -> { key, [], kind }
|
||||
@@ -2843,7 +2807,11 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
args = [key, kind, binary_to_atom(<<"_@", integer_to_binary(counter)::binary>>), var]
|
||||
{ { :{}, [], args }, counter + 1 }
|
||||
[{ :{}, [], args }|module_vars(vars, counter+1)]
|
||||
end
|
||||
|
||||
defp module_vars([], _counter) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Gets two modules names and return an alias
|
||||
@@ -2972,16 +2940,18 @@ defmodule Kernel do
|
||||
defp define(kind, call, expr, env) do
|
||||
assert_module_scope(env, kind, 2)
|
||||
assert_no_function_scope(env, kind, 2)
|
||||
line = env_line(env)
|
||||
|
||||
{ call, uc } = :elixir_quote.escape(call, true)
|
||||
{ expr, ue } = :elixir_quote.escape(expr, true)
|
||||
|
||||
# Do not check clauses if any expression was unquoted
|
||||
check_clauses = not(ue or uc)
|
||||
pos = :elixir_locals.cache_env(env)
|
||||
|
||||
quote do
|
||||
:elixir_def.store_definition(unquote(kind), unquote(check_clauses),
|
||||
unquote(call), unquote(expr), __ENV__)
|
||||
:elixir_def.store_definition(unquote(line), unquote(kind), unquote(check_clauses),
|
||||
unquote(call), unquote(expr), unquote(pos))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3024,7 +2994,7 @@ defmodule Kernel do
|
||||
the record name, we can get the raw record representation as
|
||||
follows:
|
||||
|
||||
inspect User.new, raw: true
|
||||
inspect User.new, records: false
|
||||
#=> { User, nil, 0 }
|
||||
|
||||
In addition to defining readers and writers for each attribute, Elixir also
|
||||
@@ -3542,10 +3512,11 @@ defmodule Kernel do
|
||||
append_first = Keyword.get(opts, :append_first, false)
|
||||
|
||||
lc fun inlist List.wrap(funs) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } -> :ok
|
||||
_ -> raise ArgumentError, message: "invalid syntax in defdelegate #{Macro.to_string(fun)}"
|
||||
end
|
||||
{ name, args } =
|
||||
case Macro.decompose_call(fun) do
|
||||
{ _, _ } = pair -> pair
|
||||
_ -> raise ArgumentError, message: "invalid syntax in defdelegate #{Macro.to_string(fun)}"
|
||||
end
|
||||
|
||||
actual_args =
|
||||
case append_first and args != [] do
|
||||
@@ -3565,7 +3536,7 @@ defmodule Kernel do
|
||||
## Sigils
|
||||
|
||||
@doc """
|
||||
Handles the sigil %S. It simples returns a string
|
||||
Handles the sigil %S. It simply returns a string
|
||||
without escaping characters and without interpolations.
|
||||
|
||||
## Examples
|
||||
@@ -3613,7 +3584,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles the sigil %c. It returns a char list as if it was a single
|
||||
Handles the sigil %c. It returns a char list as if it were a single
|
||||
quoted string, unescaping characters and replacing interpolations.
|
||||
|
||||
## Examples
|
||||
@@ -3676,9 +3647,9 @@ defmodule Kernel do
|
||||
|
||||
## Modifiers
|
||||
|
||||
- `b`: binaries (default)
|
||||
- `a`: atoms
|
||||
- `c`: char lists
|
||||
* `s`: strings (default)
|
||||
* `a`: atoms
|
||||
* `c`: char lists
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -3706,9 +3677,9 @@ defmodule Kernel do
|
||||
|
||||
## Modifiers
|
||||
|
||||
- `b`: binaries (default)
|
||||
- `a`: atoms
|
||||
- `c`: char lists
|
||||
* `s`: strings (default)
|
||||
* `a`: atoms
|
||||
* `c`: char lists
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -3723,11 +3694,12 @@ defmodule Kernel do
|
||||
defp split_words("", _modifiers), do: []
|
||||
|
||||
defp split_words(string, modifiers) do
|
||||
mod = case modifiers do
|
||||
[] -> ?s
|
||||
[mod] when mod == ?s or mod == ?a or mod == ?c -> mod
|
||||
_else -> raise ArgumentError, message: "modifier must be one of: s, a, c"
|
||||
end
|
||||
mod =
|
||||
case modifiers do
|
||||
[] -> ?s
|
||||
[mod] when mod == ?s or mod == ?a or mod == ?c -> mod
|
||||
_else -> raise ArgumentError, message: "modifier must be one of: s, a, c"
|
||||
end
|
||||
|
||||
case is_binary(string) do
|
||||
true ->
|
||||
@@ -3773,9 +3745,10 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defp env_module(env), do: :erlang.element(2, env)
|
||||
defp env_line(env), do: :erlang.element(4, env)
|
||||
defp env_function(env), do: :erlang.element(5, env)
|
||||
defp env_context(env), do: :erlang.element(6, env)
|
||||
defp env_vars(env), do: :erlang.element(14, env)
|
||||
defp env_vars(env), do: :erlang.element(13, env)
|
||||
|
||||
defp expand_compact([{ :compact, false }|t]), do: expand_compact(t)
|
||||
defp expand_compact([{ :compact, true }|t]), do: [:compact|expand_compact(t)]
|
||||
|
||||
@@ -221,7 +221,7 @@ defmodule Kernel.CLI do
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
|
||||
end
|
||||
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
process_compiler t, config.verbose_compile(true)
|
||||
end
|
||||
@@ -339,18 +339,42 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, &Path.wildcard(&1)
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
case match_regular_files(patterns) do
|
||||
{ :ok, [] } ->
|
||||
{ :error, "No files matched provided patterns" }
|
||||
{ :ok, files } ->
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
end
|
||||
{ :missing, missing } ->
|
||||
{ :error, "No files matched pattern(s) #{Enum.join(missing, ",")}" }
|
||||
end
|
||||
end
|
||||
|
||||
if files != [] do
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
defp match_regular_files(patterns) do
|
||||
matched_files = Enum.map patterns, fn(pattern) ->
|
||||
case Path.wildcard(pattern) do
|
||||
[] -> { :missing, pattern }
|
||||
files -> { :ok, files }
|
||||
end
|
||||
end
|
||||
|
||||
files = Enum.filter_map matched_files,
|
||||
fn(match) -> elem(match, 0) == :ok end,
|
||||
&elem(&1, 1)
|
||||
|
||||
missing_patterns = Enum.filter_map matched_files,
|
||||
fn(match) -> elem(match, 0) == :missing end,
|
||||
&elem(&1, 1)
|
||||
|
||||
if missing_patterns == [] do
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
{ :ok, files }
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
{ :missing, Enum.uniq(missing_patterns) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -18,9 +18,19 @@ defmodule Kernel.ErrorHandler do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
parent <- { :waiting, self(), module }
|
||||
ref = :erlang.make_ref
|
||||
send parent, { :waiting, self(), ref, module }
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{ :release, ^parent } -> :ok
|
||||
{ ^ref, :ready } ->
|
||||
:ok
|
||||
{ ^ref, :release } ->
|
||||
# On release, get rid of the compiler pid,
|
||||
# no further allow elixir_ensure_compiled directives
|
||||
# and revert to the original error handler.
|
||||
:erlang.erase(:elixir_compiler_pid)
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Kernel.LexicalTracker do
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
@behaviour :gen_server
|
||||
|
||||
@import 2
|
||||
@alias 3
|
||||
@@ -104,8 +104,8 @@ defmodule Kernel.LexicalTracker do
|
||||
{ :reply, d, d }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
def handle_call(request, _from, d) do
|
||||
{ :stop, { :bad_call, request }, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :remote_dispatch, module }, d) do
|
||||
@@ -137,8 +137,8 @@ defmodule Kernel.LexicalTracker do
|
||||
{ :stop, :normal, d }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_cast(msg, d) do
|
||||
{ :stop, { :bad_cast, msg }, d }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
|
||||
@@ -62,29 +62,42 @@ defmodule Kernel.ParallelCompiler do
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{ ^h, ref, _ } = List.keyfind(waiting, h, 0)
|
||||
send h, { ref, :ready }
|
||||
waiting = List.keydelete(waiting, h, 0)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
child = spawn_link fn ->
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_ensure_compiled, true)
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
{ pid, ref } =
|
||||
:erlang.spawn_monitor fn ->
|
||||
# Notify Code.ensure_compiled/2 that we should
|
||||
# attempt to compile the module by doing a dispatch.
|
||||
:erlang.put(:elixir_ensure_compiled, true)
|
||||
|
||||
try do
|
||||
if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
parent <- { :compiled, self(), h }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self(), kind, reason, System.stacktrace }
|
||||
# Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
exit(try do
|
||||
if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
{ :compiled, h }
|
||||
catch
|
||||
kind, reason ->
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{child, h}|queued], schedulers, result)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
@@ -92,7 +105,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
|
||||
Enum.each queued, fn { child, _, _ } ->
|
||||
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
|
||||
send child, { ref, :release }
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
@@ -104,38 +120,61 @@ defmodule Kernel.ParallelCompiler do
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{ :compiled, child, file } ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
new_queued = List.keydelete(queued, child, 0)
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
# Release the module loader which is waiting for an ack
|
||||
child <- { ref, :ack }
|
||||
send child, { ref, :ack }
|
||||
|
||||
new_waiting = release_waiting_processes(module, waiting)
|
||||
new_result = [module|result]
|
||||
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
|
||||
{ :waiting, child, on } ->
|
||||
new_waiting = :orddict.store(child, on, waiting)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
|
||||
{ :failure, child, kind, reason, stacktrace } ->
|
||||
available = lc { pid, _, waiting_module } inlist waiting,
|
||||
waiting_module == module,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ files, original, output, callbacks,
|
||||
waiting, queued, schedulers, [module|result])
|
||||
|
||||
{ :waiting, child, ref, on } ->
|
||||
# Oops, we already got this module. Do not put it on waiting.
|
||||
if :lists.member(on, result) do
|
||||
send child, { :release, ref }
|
||||
else
|
||||
waiting = [{ child, ref, on }|waiting]
|
||||
end
|
||||
spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, down_pid, 0)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
|
||||
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, other } ->
|
||||
handle_failure(down_ref, :exit, other, [], files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
|
||||
case List.keyfind(queued, ref, 1) do
|
||||
{ child, ^ref, file } ->
|
||||
if many_missing?(child, files, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
IO.puts "Compilation failed on the following files:\n"
|
||||
|
||||
Enum.each Enum.reverse(queued), fn { pid, file } ->
|
||||
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
|
||||
case List.keyfind(waiting, pid, 0) do
|
||||
{ _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
@@ -143,29 +182,18 @@ defmodule Kernel.ParallelCompiler do
|
||||
IO.puts "\nThe first failure is shown below..."
|
||||
end
|
||||
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, files, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
|
||||
match?({ ^child, _ }, List.keyfind(waiting, child, 0)) and
|
||||
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
|
||||
waiting_length > 1 and files == [] and
|
||||
waiting_length == length(queued)
|
||||
end
|
||||
|
||||
# Release waiting processes that are waiting for the given module
|
||||
defp release_waiting_processes(module, waiting) do
|
||||
Enum.filter waiting, fn { child, waiting_module } ->
|
||||
if waiting_module == module do
|
||||
child <- { :release, self() }
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,7 +35,7 @@ defmodule Kernel.ParallelRequire do
|
||||
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
|
||||
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
|
||||
|
||||
child = spawn_link fn ->
|
||||
{ pid, ref } = :erlang.spawn_monitor fn ->
|
||||
if compiler_pid != :undefined do
|
||||
:erlang.put(:elixir_compiler_pid, compiler_pid)
|
||||
end
|
||||
@@ -46,25 +46,35 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
:erlang.process_flag(:error_handler, handler)
|
||||
|
||||
try do
|
||||
exit(try do
|
||||
new = Code.require_file(h) || []
|
||||
parent <- { :required, self, Enum.map(new, &elem(&1, 0)), h }
|
||||
{ :required, Enum.map(new, &elem(&1, 0)), h }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self, kind, reason, System.stacktrace }
|
||||
end
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_requires(t, [child|waiting], callback, schedulers, result)
|
||||
spawn_requires(t, [{ pid, ref }|waiting], callback, schedulers, result)
|
||||
end
|
||||
|
||||
defp wait_for_messages(files, waiting, callback, schedulers, result) do
|
||||
receive do
|
||||
{ :required, child, mods, file } ->
|
||||
callback.(file)
|
||||
spawn_requires(files, List.delete(waiting, child), callback, schedulers, mods ++ result)
|
||||
{ :failure, _child, kind, reason, stacktrace } ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
{ :DOWN, ref, :process, pid, status } ->
|
||||
tuple = { pid, ref }
|
||||
if tuple in waiting do
|
||||
case status do
|
||||
{ :required, mods, file } ->
|
||||
callback.(file)
|
||||
result = mods ++ result
|
||||
waiting = List.delete(waiting, tuple)
|
||||
{ :failure, kind, reason, stacktrace } ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
other ->
|
||||
:erlang.raise(:exit, other, [])
|
||||
end
|
||||
end
|
||||
spawn_requires(files, waiting, callback, schedulers, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,7 +111,7 @@ defmodule Kernel.RecordRewriter do
|
||||
|
||||
defp optimize_record_other_call(line, record, res, function, left, args) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, line, record }, { :atom, 0, function } },
|
||||
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
|
||||
args ++ [left]
|
||||
}
|
||||
{ call, res }
|
||||
@@ -128,7 +128,7 @@ defmodule Kernel.RecordRewriter do
|
||||
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args } = call, module, dict) do
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
defmodule Kernel.SpecialForms do
|
||||
@moduledoc """
|
||||
In this module we define Elixir special forms. Special forms
|
||||
cannot be overriden by the developer and are the basic
|
||||
cannot be overridden by the developer and are the basic
|
||||
building blocks of Elixir code.
|
||||
|
||||
Some of those forms are lexical (like `alias`, `case`, etc).
|
||||
The macros `{}` and `<<>>` are also special forms used to define
|
||||
tuple and binary data structures respectively.
|
||||
|
||||
This module also documents Elixir's pseudo variables (`__MODULE__`,
|
||||
`__FILE__`, `__ENV__`, `__DIR__` and `__CALLER__`). Pseudo variables
|
||||
return information about Elixir's compilation environment and can only
|
||||
This module also documents Elixir's pseudo variables (`__ENV__`,
|
||||
`__MODULE__`, `__DIR__` and `__CALLER__`). Pseudo variables return
|
||||
information about Elixir's compilation environment and can only
|
||||
be read, never assigned to.
|
||||
|
||||
Finally, it also documents 2 special forms, `__block__` and
|
||||
@@ -471,19 +471,45 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __MODULE__
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
|
||||
Although the file can be accessed in the `__ENV__`, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
defmacro __FILE__
|
||||
|
||||
@doc """
|
||||
Returns the current directory as a binary.
|
||||
|
||||
Although the directory can be accessed as `Path.dirname(__ENV__.file)`,
|
||||
this macro is a convenient shortcut.
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc """
|
||||
Accesses an already bound variable in match clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
Elixir allows variables to be rebound via static single assignment:
|
||||
|
||||
iex> x = 1
|
||||
iex> x = 2
|
||||
iex> x
|
||||
2
|
||||
|
||||
However, in some situations, it is useful to match against an existing
|
||||
value, instead of rebinding. This can be done with the `^` special form:
|
||||
|
||||
iex> x = 1
|
||||
iex> ^x = 1
|
||||
iex> ^x = 2
|
||||
** (MatchError) no match of right hand side value: 2
|
||||
|
||||
Note that `^` always refers to the value of x prior to the match. The
|
||||
following example will match:
|
||||
|
||||
iex> x = 0
|
||||
iex> { x, ^x } = { 1, 0 }
|
||||
iex> x
|
||||
1
|
||||
|
||||
"""
|
||||
defmacro ^(var)
|
||||
|
||||
@doc %S"""
|
||||
Gets the representation of any expression.
|
||||
|
||||
@@ -532,14 +558,14 @@ defmodule Kernel.SpecialForms do
|
||||
2.0 #=> Floats
|
||||
[1, 2] #=> Lists
|
||||
"strings" #=> Strings
|
||||
{key, value} #=> Tuple with two elements
|
||||
{key, value} #=> Tuples with two elements
|
||||
|
||||
## Quote and macros
|
||||
|
||||
`quote` is commonly used with macros for code generation. As an exercise,
|
||||
let's define a macro that multiplies a number by itself (squared). Note
|
||||
there is no reason to define such as a macro (and it would actually be
|
||||
seen as a bad practice), but it is simple enough that allows us to focus
|
||||
seen as a bad practice), but it is simple enough that it allows us to focus
|
||||
on the important aspects of quotes and macros:
|
||||
|
||||
defmodule Math do
|
||||
@@ -557,8 +583,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
At first, there is nothing in this example that actually reveals it is a
|
||||
macro. But what is happening is that, at compilation time, `squared(5)`
|
||||
becomes `5 * 5`. We can see this behaviour in practice though
|
||||
because our macro actually has a bug:
|
||||
becomes `5 * 5`. The argument `5` is duplicated in the produced code, we
|
||||
can see this behaviour in practice though because our macro actually has
|
||||
a bug:
|
||||
|
||||
import Math
|
||||
my_number = fn ->
|
||||
@@ -613,11 +640,11 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
`:bind_quoted` will translate to the same example as above. `:bind_quoted`
|
||||
can be used in many cases and is seen as good practice, not only because
|
||||
it helps us from running into common mistakes but also because it allows
|
||||
us to leverage other tools exposed by macros, as unquote fragments discussed
|
||||
in some sections below.
|
||||
`:bind_quoted` will translate to the same code as the example above.
|
||||
`:bind_quoted` can be used in many cases and is seen as good practice,
|
||||
not only because it helps us from running into common mistakes but also
|
||||
because it allows us to leverage other tools exposed by macros, such as
|
||||
unquote fragments discussed in some sections below.
|
||||
|
||||
Before we finish this brief introduction, you will notice that, even though
|
||||
we defined a variable `x` inside our quote:
|
||||
@@ -637,7 +664,7 @@ defmodule Kernel.SpecialForms do
|
||||
because Elixir macros are hygienic, a topic we will discuss at length
|
||||
in the next sections as well.
|
||||
|
||||
### Hygiene in variables
|
||||
## Hygiene in variables
|
||||
|
||||
Consider the following example:
|
||||
|
||||
@@ -655,8 +682,8 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
In the example above, `a` returns 10 even if the macro
|
||||
is apparently setting it to 1 because variables defined
|
||||
in the macro does not affect the context the macro is executed.
|
||||
If you want to set or get a variable in the user context, you
|
||||
in the macro does not affect the context the macro is executed in.
|
||||
If you want to set or get a variable in the caller's context, you
|
||||
can do it with the help of the `var!` macro:
|
||||
|
||||
defmodule NoHygiene do
|
||||
@@ -671,7 +698,7 @@ defmodule Kernel.SpecialForms do
|
||||
NoHygiene.interference
|
||||
a #=> 1
|
||||
|
||||
Note that you cannot even access variables defined by the same
|
||||
Note that you cannot even access variables defined in the same
|
||||
module unless you explicitly give it a context:
|
||||
|
||||
defmodule Hygiene do
|
||||
@@ -717,7 +744,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
quote hygiene: [vars: false], do: x
|
||||
|
||||
### Hygiene in aliases
|
||||
## Hygiene in aliases
|
||||
|
||||
Aliases inside quote are hygienic by default.
|
||||
Consider the following example:
|
||||
@@ -738,7 +765,7 @@ defmodule Kernel.SpecialForms do
|
||||
because `D` still expands to `HashDict`.
|
||||
|
||||
Similarly, even if we defined an alias with the same name
|
||||
before invoking a macro, it won't affect the macro result:
|
||||
before invoking a macro, it won't affect the macro's result:
|
||||
|
||||
defmodule Hygiene do
|
||||
alias HashDict, as: D
|
||||
@@ -878,12 +905,12 @@ defmodule Kernel.SpecialForms do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
|
||||
In the example above, we have generated the function `foo/0` and
|
||||
In the example above, we have generated the functions `foo/0` and
|
||||
`bar/0` dynamically. Now, imagine that, we want to convert this
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
Enum.map kv, fn { k, v } ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
@@ -900,13 +927,13 @@ defmodule Kernel.SpecialForms do
|
||||
defkv kv
|
||||
|
||||
This is because the macro is expecting its arguments to be a
|
||||
key-value at **compilation** time. Since in the example above
|
||||
keyword list at **compilation** time. Since in the example above
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practice, we want to avoid doing work at compilation time as
|
||||
much as we can. That said, let's attempt to improve our macro:
|
||||
much as possible. That said, let's attempt to improve our macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
@@ -918,12 +945,12 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
If you try to run our new macro, you will notice it won't
|
||||
even compile, complaining that the variables `k` and `v`
|
||||
do not exist. This is because of the ambiguity: `unquote(k)`
|
||||
does not exist. This is because of the ambiguity: `unquote(k)`
|
||||
can either be an unquote fragment, as previously, or a regular
|
||||
unquote as in `unquote(kv)`.
|
||||
|
||||
One solution for this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject
|
||||
One solution to this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject the
|
||||
`kv` representation into the tree. That's when the `:bind_quoted`
|
||||
option comes to the rescue (again!). By using `:bind_quoted`, we
|
||||
can automatically disable unquoting while still injecting the
|
||||
@@ -1200,44 +1227,54 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro super(args)
|
||||
|
||||
@doc """
|
||||
Matches the given expression against the match clauses.
|
||||
Matches the given expression against the given clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
case thing do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
value
|
||||
value -> value
|
||||
value ->
|
||||
value
|
||||
end
|
||||
|
||||
In the example above, we compare `thing` with each given match
|
||||
clause and evaluate the expression corresponding to the first clause
|
||||
In the example above, we match `thing` against each clause "head"
|
||||
and execute the clause "body" corresponding to the first clause
|
||||
that matches. If no clause matches, an error is raised.
|
||||
|
||||
Since Elixir variables can be assigned more than once, variables
|
||||
in a match clause will always be assigned instead of matching with
|
||||
its previous values. For example:
|
||||
## Variables handling
|
||||
|
||||
i = 1
|
||||
case 10 do
|
||||
i -> i * 2
|
||||
Notice that variables bound in a clause "head" do not leak to the
|
||||
outer context:
|
||||
|
||||
case data do
|
||||
{ :ok, value } -> value
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
The example above will return 20, because `i` is assigned to 10
|
||||
and then multiplied by 2. If you desire to match the value of `i`
|
||||
against the given condition, you need to use the `^` operator:
|
||||
value #=> unbound variable value
|
||||
|
||||
i = 1
|
||||
case 10 do
|
||||
^i -> i * 2
|
||||
However, variables explicitly bound in the clause "body" are
|
||||
accessible from the outer context:
|
||||
|
||||
value = 7
|
||||
|
||||
case lucky? do
|
||||
false -> value = 13
|
||||
true -> true
|
||||
end
|
||||
|
||||
The example above will actually fail because 10 does not match 1.
|
||||
value #=> 7 or 13
|
||||
|
||||
In the example above, value is going to be `7` or `13` depending on
|
||||
the value of `lucky?`. In case `value` has no previous value before
|
||||
case, clauses that do not explicitly bind a value have the variable
|
||||
bound to nil.
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
|
||||
@doc """
|
||||
Evaluate the given expressions and catch any error, exit
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
or throw that may have happened.
|
||||
|
||||
## Examples
|
||||
@@ -1284,14 +1321,14 @@ defmodule Kernel.SpecialForms do
|
||||
[UndefinedFunctionError] -> nil
|
||||
end
|
||||
|
||||
# rescue and assign to x
|
||||
# rescue and bind to x
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
x in [UndefinedFunctionError] -> nil
|
||||
end
|
||||
|
||||
# rescue all and assign to x
|
||||
# rescue all and bind to x
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
@@ -1396,7 +1433,7 @@ defmodule Kernel.SpecialForms do
|
||||
with a tail call as the final call inside an else clause. The same
|
||||
is true for rescue and catch clauses.
|
||||
|
||||
## Variable visibility
|
||||
## Variable handling
|
||||
|
||||
Since an expression inside `try` may not have been evaluated
|
||||
due to an exception, any variable created inside `try` cannot
|
||||
@@ -1410,7 +1447,7 @@ defmodule Kernel.SpecialForms do
|
||||
_, _ -> :failed
|
||||
end
|
||||
|
||||
x #=> Cannot access `x`
|
||||
x #=> unbound variable `x`
|
||||
|
||||
In the example above, `x` cannot be accessed since it was defined
|
||||
inside the `try` clause. A common practice to address this issue
|
||||
@@ -1429,8 +1466,11 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro try(args)
|
||||
|
||||
@doc """
|
||||
The current process will hang until it receives a message
|
||||
from other processes that matches the given clauses.
|
||||
Checks if there is a message matching the given clauses
|
||||
in the current process mailbox.
|
||||
|
||||
In case there is no such message, the current process hangs
|
||||
until a message arrives or waits until a given timeout value.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1443,8 +1483,6 @@ defmodule Kernel.SpecialForms do
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
end
|
||||
|
||||
The match clauses above follows the same rules as `case/2`.
|
||||
|
||||
An optional after clause can be given in case the message was not
|
||||
received after the specified period of time:
|
||||
|
||||
@@ -1468,6 +1506,11 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately.
|
||||
|
||||
## Variables handling
|
||||
|
||||
The `receive` special form handles variables exactly as the `case`
|
||||
special macro. For more information, check the docs for `case/2`.
|
||||
"""
|
||||
defmacro receive(args)
|
||||
end
|
||||
|
||||
+218
-101
@@ -2,56 +2,95 @@ defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
macros defined by this module.
|
||||
Elixir comes with a notation for declaring types and specifications. Elixir is
|
||||
dynamically typed, as such typespecs are never used by the compiler to
|
||||
optimize or modify code. Still, using typespecs is useful as documentation and
|
||||
tools such as [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) can
|
||||
analyze the code with typespecs to find bugs.
|
||||
|
||||
## Defining a type
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and `@callback` available
|
||||
in modules are handled by the equivalent macros defined by this module. See
|
||||
sub-sections "Defining a type" and "Defining a specification" below.
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
## Types and their syntax
|
||||
|
||||
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
|
||||
below.
|
||||
The type syntax provided by Elixir is fairly similar to the one in
|
||||
[Erlang](http://www.erlang.org/doc/reference_manual/typespec.html).
|
||||
|
||||
## Defining a specification
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`) are
|
||||
expressed the same way: `pid()` or simply `pid`. Parametrized types are also
|
||||
supported (`list(integer)`) and so are remote types (`Enum.t`).
|
||||
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
|
||||
`false`). All other types are built of unions of predefined types. Certain
|
||||
shorthands are allowed, such as `[...]`, `<<>>` and `{...}`.
|
||||
|
||||
For more details, see documentation for `defspec` and `defcallback` below.
|
||||
### Predefined types
|
||||
|
||||
## Types
|
||||
Type :: any # the top type, the set of all terms
|
||||
| none # the bottom type, contains no terms
|
||||
| pid
|
||||
| port
|
||||
| reference
|
||||
| Atom
|
||||
| Bitstring
|
||||
| float
|
||||
| Fun
|
||||
| Integer
|
||||
| List
|
||||
| Tuple
|
||||
| Union
|
||||
| UserDefined # Described in section "Defining a type"
|
||||
|
||||
The type syntax provided by Elixir is fairly similar to the one
|
||||
in Erlang.
|
||||
Atom :: atom
|
||||
| ElixirAtom # `:foo`, `:bar`, ...
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`)
|
||||
are expressed the same way: `pid()` or simply `pid`. Parametrized types
|
||||
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
|
||||
Bitstring :: <<>>
|
||||
| << _ :: M >> # M is a positive integer
|
||||
| << _ :: _ * N >> # N is a positive integer
|
||||
| << _ :: M, _ :: _ * N >>
|
||||
|
||||
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
|
||||
well.
|
||||
Fun :: (... -> any) # any function
|
||||
| (... -> Type) # any arity, returning Type
|
||||
| (() -> Type))
|
||||
| (TList -> Type)
|
||||
|
||||
Main differences lie in how bit strings and functions are defined:
|
||||
Integer :: integer
|
||||
| ElixirInteger # ..., -1, 0, 1, ... 42 ...
|
||||
| ElixirInteger..ElixirInteger # an integer range
|
||||
|
||||
### Bit Strings
|
||||
List :: list(Type) # proper list ([]-terminated)
|
||||
| improper_list(Type1, Type2) # Type1=contents, Type2=termination
|
||||
| maybe_improper_list(Type1, Type2) # Type1 and Type2 as above
|
||||
| nonempty_list(Type) # proper non-empty list
|
||||
| [] # empty list
|
||||
| [Type] # shorthand for list(Type)
|
||||
| [Type, ...] # shorthand for nonempty_list(Type)
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
| { TList }
|
||||
|
||||
TList :: Type
|
||||
| Type, TList
|
||||
|
||||
Union :: Type1 | Type2
|
||||
|
||||
### Bit strings
|
||||
|
||||
Bit string with a base size of 3:
|
||||
|
||||
<<_ :: 3>>
|
||||
<< _ :: 3 >>
|
||||
|
||||
Bit string with a unit size of 8:
|
||||
|
||||
<<_ :: _ * 8>>
|
||||
<< _ :: _ * 8 >>
|
||||
|
||||
### Anonymous functions
|
||||
|
||||
Any anonymous function:
|
||||
|
||||
((...) -> any)
|
||||
or
|
||||
(... -> any)
|
||||
|
||||
Anonymous function with arity of zero:
|
||||
@@ -61,27 +100,98 @@ defmodule Kernel.Typespec do
|
||||
Anonymous function with some arity:
|
||||
|
||||
((type, type) -> type)
|
||||
or
|
||||
(type, type -> type)
|
||||
|
||||
## Built-in types
|
||||
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{ atom, atom, arity }`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
|
||||
Some built-in types cannot be expressed with valid syntax according to the
|
||||
language defined above.
|
||||
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
|
||||
Types defined in other modules are referred to as "remote types", they are
|
||||
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
A type defined with `@typep` is private. An opaque type, defined with
|
||||
`@opaque` is a type where the internal structure of the type will not be
|
||||
visible, but the type is still public.
|
||||
|
||||
Types can be parametrised by defining variables as parameters, these variables
|
||||
can then be used to define the type.
|
||||
|
||||
@type dict(key, value) :: [{ key, value }]
|
||||
|
||||
Types can also be defined for records, see `defrecord/3`.
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type1, type2) :: return_type
|
||||
@callback function_name(type1, type2) :: return_type
|
||||
|
||||
Callbacks are used to define the callbacks functions of behaviours (see
|
||||
`Behaviour`).
|
||||
|
||||
Guards can be used to restrict type variables given as arguments to the
|
||||
function.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: atom
|
||||
|
||||
Type variables with no restriction can also be defined.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: var
|
||||
|
||||
Specifications can be overloaded just like ordinary functions.
|
||||
|
||||
@spec function(integer) :: atom
|
||||
@spec function(atom) :: integer
|
||||
|
||||
## Notes
|
||||
|
||||
Elixir discourages the use of type `string()` as it might be confused
|
||||
with binaries which are referred to as "strings" in Elixir (as opposed to
|
||||
character lists). In order to use the type that is called `string()` in Erlang,
|
||||
one has to use the `char_list()` type which is a synonym for `string()`. If you
|
||||
use `string()`, you'll get a warning from the compiler.
|
||||
Elixir discourages the use of type `string` as it might be confused with
|
||||
binaries which are referred to as "strings" in Elixir (as opposed to character
|
||||
lists). In order to use the type that is called `string` in Erlang, one has to
|
||||
use the `char_list` type which is a synonym for `string`. If you use `string`,
|
||||
you'll get a warning from the compiler.
|
||||
|
||||
If you want to refer to the "string" type (the one operated by functions in the
|
||||
String module), use `String.t()` type instead.
|
||||
|
||||
See http://www.erlang.org/doc/reference_manual/typespec.html
|
||||
for more information.
|
||||
If you want to refer to the "string" type (the one operated on by functions in
|
||||
the `String` module), use `String.t` type instead.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a type.
|
||||
This macro is the one responsible for handling the attribute `@type`.
|
||||
This macro is responsible for handling the attribute `@type`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -96,7 +206,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines an opaque type.
|
||||
This macro is the one responsible for handling the attribute `@opaque`.
|
||||
This macro is responsible for handling the attribute `@opaque`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -111,7 +221,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a private type.
|
||||
This macro is the one responsible for handling the attribute `@typep`.
|
||||
This macro is responsible for handling the attribute `@typep`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -126,7 +236,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a spec.
|
||||
This macro is the one responsible for handling the attribute `@spec`.
|
||||
This macro is responsible for handling the attribute `@spec`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -141,7 +251,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a callback.
|
||||
This macro is the one responsible for handling the attribute `@callback`.
|
||||
This macro is responsible for handling the attribute `@callback`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -243,19 +353,21 @@ defmodule Kernel.Typespec do
|
||||
Converts a spec clause back to Elixir AST.
|
||||
"""
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
ast = { :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }
|
||||
meta = [line: line]
|
||||
body = { name, meta, Enum.map(args, &typespec_to_ast/1) }
|
||||
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Enum.map(&{ &1, { :var, [line: line], nil } })
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|
||||
unless vars == [] do
|
||||
ast = { :when, [line: line], [ast, vars] }
|
||||
result = if vars == [] do
|
||||
typespec_to_ast(result)
|
||||
else
|
||||
{ :when, meta, [typespec_to_ast(result), vars] }
|
||||
end
|
||||
|
||||
ast
|
||||
{ :::, meta, [body, result] }
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :fun, [] }) do
|
||||
@@ -268,16 +380,20 @@ defmodule Kernel.Typespec do
|
||||
{ var, typespec_to_ast(type) }
|
||||
end
|
||||
|
||||
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
meta = [line: line]
|
||||
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|
||||
vars = vars -- Keyword.keys(guards)
|
||||
|> Enum.map(&{ &1, { :var, [line: line], nil } })
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
|
||||
{ :when, [line: line], [{ :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }, guards ++ vars] }
|
||||
{ :::, meta, [
|
||||
{ name, [line: line], args },
|
||||
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
|
||||
] }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -298,10 +414,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all type docs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first element is the pair of type
|
||||
The result is returned as a list of tuples where the first element is the pair of type
|
||||
name and arity and the second element is the documentation.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_typedocs(module | binary) :: [tuple] | nil
|
||||
@@ -318,10 +434,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all types available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_types(module | binary) :: [tuple] | nil
|
||||
@@ -346,10 +462,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all specs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_specs(module | binary) :: [tuple] | nil
|
||||
@@ -360,10 +476,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all callbacks available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file on the disk
|
||||
The module must have a corresponding beam file
|
||||
which can be located by the runtime system.
|
||||
"""
|
||||
@spec beam_callbacks(module | binary) :: [tuple] | nil
|
||||
@@ -414,7 +530,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification `#{type_spec}`"
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
@@ -435,16 +551,17 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, { :when, meta2, [{ :::, _, [{ name, meta, args }, return] }, guard] }, caller) do
|
||||
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) do
|
||||
if is_atom(args), do: args = []
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
guard = Macro.to_string(guard)
|
||||
compile_error caller, "invalid guard in function type specification `#{guard}`"
|
||||
compile_error caller, "expected keywords as guard in function type specification, got: #{guard}"
|
||||
end
|
||||
|
||||
vars = Keyword.keys(guard)
|
||||
constraints = guard_to_constraints(guard, vars, meta2, caller)
|
||||
constraints = guard_to_constraints(guard, vars, meta, caller)
|
||||
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
|
||||
if constraints != [] do
|
||||
@@ -456,17 +573,22 @@ defmodule Kernel.Typespec do
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(type, { :::, _, [{ name, meta, args }, return] }, caller) do
|
||||
if is_atom(args), do: args = []
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(_type, other, caller) do
|
||||
spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid function type specification `#{spec}`"
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
@@ -485,7 +607,7 @@ defmodule Kernel.Typespec do
|
||||
## To AST conversion
|
||||
|
||||
defp collect_vars({ :ann_type, _line, args }) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
[]
|
||||
end
|
||||
|
||||
defp collect_vars({ :type, _line, _kind, args }) when is_list(args) do
|
||||
@@ -513,7 +635,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, :any }) do
|
||||
typespec_to_ast({:type, line, :tuple, []})
|
||||
{ :tuple, [line: line], [] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
@@ -546,17 +668,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
Enum.reduce tl(args), hd(args),
|
||||
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
|
||||
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
|
||||
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
|
||||
[{ :->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)] }]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
@@ -576,6 +697,10 @@ defmodule Kernel.Typespec do
|
||||
{ erl_to_ex_var(var), line, nil }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :op, line, op, arg }) do
|
||||
{ op, [line: line], [typespec_to_ast(arg)] }
|
||||
end
|
||||
|
||||
# Special shortcut(s)
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
@@ -635,7 +760,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
exprs = collect_union(exprs)
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
@@ -669,11 +794,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
|
||||
defp typespec([{:->, meta, [arguments, return]}], vars, caller) when is_list(arguments) do
|
||||
args = fn_args(meta, arguments, return, vars, caller)
|
||||
{ :type, line(meta), :fun, args }
|
||||
end
|
||||
@@ -707,11 +828,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle tuples
|
||||
defp typespec({:tuple, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
typespec({:{}, meta, []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:{}, meta, []}, _, _) do
|
||||
defp typespec({:tuple, meta, args}, _vars, _caller) when args == [] or is_atom(args) do
|
||||
{ :type, line(meta), :tuple, :any }
|
||||
end
|
||||
|
||||
@@ -720,6 +837,10 @@ defmodule Kernel.Typespec do
|
||||
{ :type, line(meta), :tuple, args }
|
||||
end
|
||||
|
||||
defp typespec({ left, right }, vars, caller) do
|
||||
typespec({ :{}, [], [left, right] }, vars, caller)
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
|
||||
typespec(arg, vars, caller)
|
||||
@@ -736,7 +857,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
IO.write "warning: string() type use is discouraged. For character lists, use " <>
|
||||
:elixir_errors.warn "warning: string() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :string, arguments }
|
||||
@@ -776,18 +897,14 @@ defmodule Kernel.Typespec do
|
||||
typespec({ :nonempty_list, [], [spec] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([h|t] = l, vars, caller) do
|
||||
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l, caller)] }
|
||||
defp typespec(list, vars, caller) do
|
||||
[h|t] = Enum.reverse(list)
|
||||
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
|
||||
{ :|, [], [validate_kw(x, list, caller), acc] }
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
args = lc e inlist tuple_to_list(t), do: typespec(e, vars, caller)
|
||||
{ :type, 0, :tuple, args }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
@@ -799,12 +916,12 @@ defmodule Kernel.Typespec do
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
end
|
||||
|
||||
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
|
||||
defp collect_union({ :|, _, [a, b] }), do: [a|collect_union(b)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
|
||||
defp validate_kw(_, original, caller) do
|
||||
compile_error(caller, "unexpected list `#{Macro.to_string original}` in typespec")
|
||||
compile_error(caller, "unexpected list in typespec: #{Macro.to_string original}")
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
|
||||
@@ -25,7 +25,9 @@ defmodule Keyword do
|
||||
|
||||
@type key :: atom
|
||||
@type value :: any
|
||||
|
||||
@type t :: [{key, value}]
|
||||
@type t(value) :: [{ key, value }]
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enum. Unlike `Keyword.new`
|
||||
@@ -209,6 +211,27 @@ defmodule Keyword do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entry in the keyword list for a `key` with `value`.
|
||||
If no `key` with `value` exists, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2], :a, 1)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2, a: 3], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
|
||||
iex> Keyword.delete([b: 2], :a, 5)
|
||||
[b: 2]
|
||||
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_atom(key) do
|
||||
lc { k, v } = tuple inlist keywords, key != k or value != v, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keyword list for a specific `key`.
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
@@ -254,7 +277,7 @@ defmodule Keyword do
|
||||
Puts the given `value` under `key`.
|
||||
|
||||
If a previous value is already stored, all entries are
|
||||
removed and the value is overriden.
|
||||
removed and the value is overridden.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+20
-6
@@ -116,6 +116,23 @@ defmodule List do
|
||||
:lists.foldr(function, acc, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first element in `list` or `nil` if `list` is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.first([])
|
||||
nil
|
||||
iex> List.first([1])
|
||||
1
|
||||
iex> List.first([1, 2, 3])
|
||||
1
|
||||
|
||||
"""
|
||||
@spec first([elem]) :: nil | elem when elem: var
|
||||
def first([]), do: nil
|
||||
def first([h|_]), do: h
|
||||
|
||||
@doc """
|
||||
Returns the last element in `list` or `nil` if `list` is empty.
|
||||
|
||||
@@ -130,12 +147,9 @@ defmodule List do
|
||||
|
||||
"""
|
||||
@spec last([elem]) :: nil | elem when elem: var
|
||||
|
||||
def last([]), do: nil
|
||||
|
||||
def last(list) do
|
||||
:lists.last(list)
|
||||
end
|
||||
def last([]), do: nil
|
||||
def last([h]), do: h
|
||||
def last([_|t]), do: last(t)
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
|
||||
+137
-131
@@ -5,16 +5,19 @@ defmodule Macro do
|
||||
Conveniences for working with macros.
|
||||
"""
|
||||
|
||||
@typedoc "Abstract Syntax Tree (AST) node"
|
||||
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
|
||||
@typedoc "Abstract Syntax Tree (AST)"
|
||||
@type t :: expr | { t, t } | atom | number | binary | pid | fun | [t]
|
||||
|
||||
@binary_ops [ :===, :!==,
|
||||
@typedoc "Expr node (remaining ones are literals)"
|
||||
@type expr :: { expr | atom, Keyword.t, atom | [t] }
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~ ]
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
defmacro binary_ops, do: @binary_ops
|
||||
@@ -27,22 +30,18 @@ defmodule Macro do
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
:when -> {:right, 30}
|
||||
::: -> {:right, 40}
|
||||
o when o in [:inlist, :inbits] -> {:left, 50}
|
||||
:// -> {:right, 60}
|
||||
:| -> {:left, 70}
|
||||
o when o in [:inlist, :inbits, ://, :|] -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
|
||||
:in -> {:left, 170}
|
||||
:.. -> {:left, 200}
|
||||
o when o in [:++, :--, :**, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
o when o in [:<>] -> {:right, 230}
|
||||
o when o in [:++, :--, :**] -> {:right, 240}
|
||||
:^^^ -> {:left, 250}
|
||||
:. -> {:left, 310}
|
||||
end
|
||||
@@ -82,10 +81,39 @@ defmodule Macro do
|
||||
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
def extract_args(expr) do
|
||||
IO.write "Macro.extract_args/1 is deprecated, use Macro.decompose_call/1 instead\n#{Exception.format_stacktrace}"
|
||||
decompose_call(expr)
|
||||
@doc """
|
||||
Recurs the quoted expression applying the given function to
|
||||
each metadata node.
|
||||
|
||||
This is often useful to remove information like lines and
|
||||
hygienic counters from the expression for either storage or
|
||||
comparison.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> quoted = quote line: 10, do: sample()
|
||||
{:sample, [line: 10], []}
|
||||
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
|
||||
{:sample, [], []}
|
||||
|
||||
"""
|
||||
@spec update_meta(t, (Keyword.t -> Keyword.t)) :: t
|
||||
def update_meta(quoted, fun)
|
||||
|
||||
def update_meta({ left, meta, right }, fun) when is_list(meta) do
|
||||
{ update_meta(left, fun), fun.(meta), update_meta(right, fun) }
|
||||
end
|
||||
|
||||
def update_meta({ left, right }, fun) do
|
||||
{ update_meta(left, fun), update_meta(right, fun) }
|
||||
end
|
||||
|
||||
def update_meta(list, fun) when is_list(list) do
|
||||
lc x inlist list, do: update_meta(x, fun)
|
||||
end
|
||||
|
||||
def update_meta(other, _fun) do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -298,33 +326,27 @@ defmodule Macro do
|
||||
|
||||
# Tuple containers
|
||||
def to_string({ :{}, _, args } = ast, fun) do
|
||||
if match?([_], args) do
|
||||
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
|
||||
else
|
||||
args = args_to_string(args, fun)
|
||||
tuple = "{" <> args <> "}"
|
||||
end
|
||||
|
||||
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
|
||||
fun.(ast, tuple)
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_string({ :fn, _, [{ :->, _, [{_, _, tuple}] } = arrow] } = ast, fun)
|
||||
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [{ :->, _, [_] } = block] } = ast, fun) do
|
||||
def to_string({ :fn, _, [{ :->, _, _ }] = block } = ast, fun) do
|
||||
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [block] } = ast, fun) do
|
||||
def to_string({ :fn, _, block } = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(block, fun), "\n "
|
||||
fun.(ast, "fn\n " <> block <> "\nend")
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_string({ :->, _, _ } = ast, fun) do
|
||||
def to_string([{ :->, _, _ }|_] = ast, fun) do
|
||||
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
|
||||
end
|
||||
|
||||
@@ -367,7 +389,7 @@ defmodule Macro do
|
||||
# All other calls
|
||||
def to_string({ target, _, args } = ast, fun) when is_list(args) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
fun.(ast, case is_kw_blocks?(last) do
|
||||
fun.(ast, case kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||
false -> call_to_string_with_args(target, args, fun)
|
||||
end)
|
||||
@@ -381,28 +403,29 @@ defmodule Macro do
|
||||
# Lists
|
||||
def to_string(list, fun) when is_list(list) do
|
||||
fun.(list, cond do
|
||||
list == [] ->
|
||||
"[]"
|
||||
:io_lib.printable_list(list) ->
|
||||
"'" <> Inspect.BitString.escape(String.from_char_list!(list), ?') <> "'"
|
||||
Keyword.keyword?(list) ->
|
||||
"[" <> kw_list_to_string(list, fun) <> "]"
|
||||
not match?([_], list) ->
|
||||
args = args_to_string(list, fun)
|
||||
"[" <> args <> "]"
|
||||
true ->
|
||||
"[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]"
|
||||
end)
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
|
||||
|
||||
# Block keywords
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp is_kw_blocks?([_|_] = kw) do
|
||||
defp kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, &match?({x, _} when x in @kw_keywords, &1))
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
defp kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
|
||||
@@ -442,8 +465,8 @@ defmodule Macro do
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string({ :->, _, exprs }, fun) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
|
||||
defp block_to_string([{ :->, _, _ }|_] = block, fun) do
|
||||
Enum.map_join(block, "\n", fn({ :->, _, [left, right] }) ->
|
||||
left = comma_join_or_empty_paren(left, fun, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
|
||||
end)
|
||||
@@ -483,8 +506,8 @@ defmodule Macro do
|
||||
|
||||
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
|
||||
|
||||
defp arrow_to_string({ :->, _, pairs }, fun, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
|
||||
defp arrow_to_string(pairs, fun, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
|
||||
left = comma_join_or_empty_paren(left, fun, paren)
|
||||
left <> "-> " <> to_string(right, fun)
|
||||
end)
|
||||
@@ -513,13 +536,13 @@ defmodule Macro do
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (`__FILE__`, `__MODULE__`, etc);
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
|
||||
* Module attributes reader (`@foo`);
|
||||
|
||||
If the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `expand/2` for expansion
|
||||
until the node no longer represents a macro.
|
||||
until the node can no longer be expanded.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -583,109 +606,122 @@ defmodule Macro do
|
||||
|
||||
"""
|
||||
def expand_once(ast, env) do
|
||||
elem(expand_once(ast, env, nil), 0)
|
||||
elem(do_expand_once(ast, env), 0)
|
||||
end
|
||||
|
||||
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
|
||||
defp do_expand_once({ :__aliases__, _, _ } = original, env) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
|
||||
receiver when is_atom(receiver) ->
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true, cache }
|
||||
{ receiver, true }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
|
||||
aliases = lc alias inlist aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
receiver = :elixir_aliases.concat(aliases)
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true, cache }
|
||||
false -> { original, false, cache }
|
||||
{ receiver, true }
|
||||
false ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
defp expand_once({ :@, _, [{ name, _, args }] } = original, env, cache) when is_atom(args) or args == [] do
|
||||
defp do_expand_once({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> { Module.get_attribute(module, name), true, cache }
|
||||
false -> { original, false, cache }
|
||||
true -> { Module.get_attribute(module, name), true }
|
||||
false -> { original, false }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.module, true, cache }
|
||||
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.file, true, cache }
|
||||
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true, cache }
|
||||
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env, true, cache }
|
||||
defp do_expand_once({ :__MODULE__, _, atom }, env) when is_atom(atom),
|
||||
do: { env.module, true }
|
||||
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true }
|
||||
|
||||
defp do_expand_once({ :__FILE__, _, atom }, env) when is_atom(atom) do
|
||||
IO.write "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead\n#{Exception.format_stacktrace}"
|
||||
{ env.file, true }
|
||||
end
|
||||
|
||||
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
|
||||
do: { { :{}, [], tuple_to_list(env) }, true }
|
||||
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
|
||||
is_atom(atom) and is_atom(field) do
|
||||
case :erlang.function_exported(Macro.Env, field, 1) do
|
||||
true -> { apply(env, field, []), true }
|
||||
false -> { original, false }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro import invocation
|
||||
defp expand_once({ atom, meta, context } = original, env, cache)
|
||||
defp do_expand_once({ atom, meta, context } = original, env)
|
||||
when is_atom(atom) and is_list(meta) and is_atom(context) do
|
||||
if :lists.member({ atom, Keyword.get(meta, :counter, context) }, env.vars) do
|
||||
{ original, false, cache }
|
||||
{ original, false }
|
||||
else
|
||||
case expand_once({ atom, meta, [] }, env, cache) do
|
||||
{ _, true, _ } = exp -> exp
|
||||
{ _, false, cache } -> { original, false, cache }
|
||||
case do_expand_once({ atom, meta, [] }, env) do
|
||||
{ _, true } = exp -> exp
|
||||
{ _, false } -> { original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_once({ atom, meta, args } = original, env, cache)
|
||||
defp do_expand_once({ atom, meta, args } = original, env)
|
||||
when is_atom(atom) and is_list(args) and is_list(meta) do
|
||||
case not is_partial?(args) do
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
module = env.module
|
||||
module = env.module
|
||||
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
|
||||
env.module, extra, cache)
|
||||
arity = length(args)
|
||||
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
if :elixir_import.special_form(atom, arity) do
|
||||
{ original, false }
|
||||
else
|
||||
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
|
||||
:elixir_env.ex_to_env(env), extra)
|
||||
|
||||
case expand do
|
||||
{ :ok, receiver, quoted } ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
{ :ok, _receiver } ->
|
||||
{ original, false }
|
||||
:error ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
{ receiver, _, _ } = expand_once(left, env, cache)
|
||||
defp do_expand_once({ { :., _, [left, right] }, meta, args } = original, env) when is_atom(right) do
|
||||
{ receiver, _ } = do_expand_once(left, env)
|
||||
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> { original, false, cache }
|
||||
case is_atom(receiver) do
|
||||
false -> { original, false }
|
||||
true ->
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, cache)
|
||||
expand = :elixir_dispatch.expand_require(meta, receiver, { right, length(args) },
|
||||
args, :elixir_env.ex_to_env(env))
|
||||
|
||||
case expand do
|
||||
{ :ok, _receiver, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
{ :ok, receiver, quoted } ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
:error ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
defp expand_once(other, _env, cache), do: { other, false, cache }
|
||||
|
||||
defp to_erl_env(env, nil), do: :elixir_env.ex_to_scope(env)
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(&match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
defp do_expand_once(other, _env), do: { other, false }
|
||||
|
||||
@doc """
|
||||
Receives an AST node and expands it until it no longer represents
|
||||
@@ -695,45 +731,15 @@ defmodule Macro do
|
||||
expansion works.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
elem(expand(tree, env, nil), 0)
|
||||
expand_until({ tree, true }, env)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand(tree, env, cache) do
|
||||
expand_until({ tree, true, cache }, env)
|
||||
defp expand_until({ tree, true }, env) do
|
||||
expand_until(do_expand_once(tree, env), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, true, cache }, env) do
|
||||
expand_until(expand_once(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, false, cache }, _env) do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand_all(tree, env, cache) do
|
||||
expand_all_until(expand(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, meta, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, meta, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ list, cache }, env) when is_list(list) do
|
||||
:lists.mapfoldl(&expand_all(&1, env, &2), cache, list)
|
||||
end
|
||||
|
||||
defp expand_all_until({ other, cache }, _env) do
|
||||
{ other, cache }
|
||||
defp expand_until({ tree, false }, _env) do
|
||||
tree
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -12,16 +12,19 @@ defmodule Macro.Env do
|
||||
* `function` - a tuple as `{ atom, integer` }, where the first
|
||||
element is the function name and the seconds its arity. Returns
|
||||
`nil` if not inside a function
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `context` - the context of the environment. It can be nil
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `vars` - a list keeping all defined varaibles as { var, context }
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as { var, context }
|
||||
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
|
||||
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@@ -35,18 +38,19 @@ defmodule Macro.Env do
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{ atom, atom | non_neg_integer }]
|
||||
@type export_vars :: vars | nil
|
||||
@type lexical_tracker :: pid
|
||||
@type counter :: non_neg_integer
|
||||
@type local :: module | nil
|
||||
|
||||
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
|
||||
:macros, :macro_aliases, :macro_counter,
|
||||
:context_modules, :vars, :lexical_tracker]
|
||||
:macros, :macro_aliases, :context_modules, :vars, :export_vars, :lexical_tracker,
|
||||
:local]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, context: context, requires: requires, aliases: aliases,
|
||||
functions: functions, macros: macros, macro_aliases: aliases,
|
||||
macro_counter: counter, context_modules: context_modules, vars: vars,
|
||||
lexical_tracker: lexical_tracker]
|
||||
context_modules: context_modules, vars: vars, export_vars: export_vars,
|
||||
lexical_tracker: lexical_tracker, local: local]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
|
||||
+27
-28
@@ -69,7 +69,7 @@ defmodule Module do
|
||||
@before_compile A
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the british spelling)
|
||||
* `@behaviour` (notice the British spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
@@ -183,7 +183,7 @@ defmodule Module do
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__on_definition__/6`.
|
||||
|
||||
Note that you can\'t provide the current module to `@on_definition`
|
||||
Note that you can't provide the current module to `@on_definition`
|
||||
because the hook function will not be defined in time. Finally, since
|
||||
the `on_definition` hook is executed inside the context of the defined
|
||||
function (i.e. `env.function` returns the current function), the hook
|
||||
@@ -298,7 +298,7 @@ defmodule Module do
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id69430 for
|
||||
more information.
|
||||
"""
|
||||
def __info__(kind)
|
||||
@@ -353,15 +353,15 @@ defmodule Module do
|
||||
def eval_quoted(module, quoted, binding, opts) do
|
||||
assert_not_compiled!(:eval_quoted, module)
|
||||
:elixir_def.reset_last(module)
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval_quoted [quoted], binding, Keyword.put(opts, :module, module)
|
||||
{ value, binding, _env, _scope } =
|
||||
:elixir.eval_quoted quoted, binding, Keyword.put(opts, :module, module)
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a module with the given name and given by
|
||||
Creates a module with the given name and defined by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be given as options.
|
||||
is defined and its file can be passed as options.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -374,7 +374,7 @@ defmodule Module do
|
||||
|
||||
Hello.world #=> true
|
||||
|
||||
## Differences with `defmodule`
|
||||
## Differences from `defmodule`
|
||||
|
||||
`Module.create` works similarly to `defmodule` and
|
||||
return the same results. While one could also use
|
||||
@@ -394,12 +394,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
|
||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list of aliases and returns a new alias.
|
||||
Concatenates a list of aliases and returns a new alias.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -415,7 +414,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the two given aliases and returns a new alias.
|
||||
Concatenates two aliases and returns a new alias.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -431,9 +430,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list aliases and returns a new alias only
|
||||
Concatenates a list of aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with ArgumentError.
|
||||
referenced yet, fails with `ArgumentError`.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
@@ -451,9 +450,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the two aliases and returns a new alias only
|
||||
Concatenates two aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with ArgumentError.
|
||||
referenced yet, fails with `ArgumentError`.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
@@ -486,7 +485,7 @@ defmodule Module do
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
integer), the kind (def or defmacro), a tuple representing
|
||||
integer), the kind (`def` or `defmacro`), a tuple representing
|
||||
the function and its arity, the function signature (the signature
|
||||
should be omitted for types) and the documentation, which should
|
||||
be either a binary or a boolean.
|
||||
@@ -585,7 +584,7 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
Use `defines?/3` to assert for an specific type.
|
||||
Use `defines?/3` to assert for a specific type.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -603,8 +602,8 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the module defines a function or macro with the
|
||||
given `kind`. `kind` can be either `:def`, `:defp`,
|
||||
Checks if the module defines a function or macro of the
|
||||
given `kind`. `kind` can be any of `:def`, `:defp`,
|
||||
`:defmacro` or `:defmacrop`.
|
||||
|
||||
## Examples
|
||||
@@ -626,7 +625,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return all functions defined in the given module.
|
||||
Return all functions defined in `module`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -643,7 +642,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all functions defined in te given module according
|
||||
Returns all functions defined in `module`, according
|
||||
to its kind.
|
||||
|
||||
## Examples
|
||||
@@ -662,7 +661,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in the given module overridable.
|
||||
Makes the given functions in `module` overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
more information and documentation.
|
||||
@@ -678,8 +677,8 @@ defmodule Module do
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
|
||||
neighbours = if loaded?(Module.DispatchTracker) do
|
||||
Module.DispatchTracker.yank(module, tuple)
|
||||
neighbours = if loaded?(Module.LocalsTracker) do
|
||||
Module.LocalsTracker.yank(module, tuple)
|
||||
else
|
||||
[]
|
||||
end
|
||||
@@ -695,7 +694,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given tuple in module is marked as overridable.
|
||||
Returns `true` if `tuple` in `module` is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
@@ -790,7 +789,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that matches the given key.
|
||||
Deletes all attributes that match the given key.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -822,7 +821,7 @@ defmodule Module do
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are false.
|
||||
By default, both options are `false`.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+60
-95
@@ -11,32 +11,27 @@
|
||||
#
|
||||
# We also have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import or remotely
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
# * `{ :import, name, arity }` - an invoked function/arity import
|
||||
# * `{ :remote, name, arity }` - an remotely invoked function/arity
|
||||
#
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * in neighbours: `{ :import, name, arity }`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * out neighbours: `{ :import, name, arity }`
|
||||
#
|
||||
# * `{ :import, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# * `{ :remote, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Module.DispatchTracker do
|
||||
defmodule Module.LocalsTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@@ -48,44 +43,9 @@ defmodule Module.DispatchTracker do
|
||||
|
||||
@type local :: { name, arity }
|
||||
@type import :: { :import, name, arity }
|
||||
@type remote :: { :remote, name, arity }
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Receives a dispatch or a module and returns all dispatches
|
||||
that calls it.
|
||||
|
||||
In case the argument is a module, the response will be
|
||||
made by import and remote dispatches.
|
||||
|
||||
In case the argument is another dispatch, the response
|
||||
will be made by local dispatches.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, A does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_to(ref, module) :: [import | remote]
|
||||
@spec dispatches_to(ref, local | import | remote) :: [local]
|
||||
def dispatches_to(ref, dispatch) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.in_neighbours(d, dispatch) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a local and returns all dispatches from that local.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, C does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_from(ref, local) :: [local | import | remote]
|
||||
def dispatches_from(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
@@ -96,16 +56,6 @@ defmodule Module.DispatchTracker do
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all modules that had the given `{ name, arity }`
|
||||
invoked remotely.
|
||||
"""
|
||||
@spec remotes_with_dispatch(ref, name_arity) :: [module]
|
||||
def remotes_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :remote, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all locals that are reachable.
|
||||
|
||||
@@ -130,14 +80,10 @@ defmodule Module.DispatchTracker 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, :__dispatch_tracker)
|
||||
[{ _, val }] = :ets.lookup(table, :__locals_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
defp only_tuples(list) do
|
||||
lc x inlist list, is_tuple(x), do: x
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@@ -173,16 +119,10 @@ defmodule Module.DispatchTracker do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
end
|
||||
|
||||
# Adds a remote dispatch to the given target.
|
||||
@doc false
|
||||
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_external, :remote, function, module, target })
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_external, :import, function, module, target })
|
||||
:gen_server.cast(pid, { :add_import, function, module, target })
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@@ -246,6 +186,16 @@ defmodule Module.DispatchTracker do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def cache_env(pid, env) do
|
||||
:gen_server.call(pid, { :cache_env, env }, @timeout)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_cached_env(pid, ref) do
|
||||
:gen_server.call(pid, { :get_cached_env, ref }, @timeout)
|
||||
end
|
||||
|
||||
# Stops the gen server
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
@@ -257,77 +207,92 @@ defmodule Module.DispatchTracker do
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
{ :ok, d }
|
||||
{ :ok, { d, [] } }
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, d) do
|
||||
def handle_call({ :cache_env, env }, _from, { d, cache }) do
|
||||
case cache do
|
||||
[{i,^env}|_] ->
|
||||
{ :reply, i, { d, cache } }
|
||||
t ->
|
||||
i = length(t)
|
||||
{ :reply, i, { d, [{i,env}|t] } }
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call({ :get_cached_env, ref }, _from, { _, cache } = state) do
|
||||
{ ^ref, env } = :lists.keyfind(ref, 1, cache)
|
||||
{ :reply, env, state }
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, { d, _ } = state) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{ :reply, { in_vertices, out_vertices }, d }
|
||||
{ :reply, { in_vertices, out_vertices }, state }
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, d) do
|
||||
{ :reply, d, d }
|
||||
def handle_call(:digraph, _from, { d, _ } = state) do
|
||||
{ :reply, d, state }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_local, from, to }, d) do
|
||||
def handle_cast({ :add_local, from, to }, { d, _ } = state) do
|
||||
handle_add_local(d, from, to)
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, kind, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, { d, _ } = state) do
|
||||
handle_import(d, function, module, name, arity)
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, d) do
|
||||
def handle_cast({ :add_definition, kind, tuple }, { d, _ } = state) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) do
|
||||
lc i inlist :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, { name, i })
|
||||
handle_add_local(d, { name, i }, { name, i + 1 })
|
||||
end
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
|
||||
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
|
||||
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
def handle_cast(:stop, state) do
|
||||
{ :stop, :normal, state }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
defp handle_import_or_remote(d, kind, function, module, name, arity) do
|
||||
defp handle_import(d, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
|
||||
tuple = { kind, name, arity }
|
||||
tuple = { :import, name, arity }
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule OptionParser do
|
||||
|
||||
Extra information about switches can be given as arguments, too.
|
||||
This is useful when a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overriden or accumulated.
|
||||
or if duplicated switches should be kept, overridden or accumulated.
|
||||
|
||||
The following types are supported:
|
||||
|
||||
|
||||
@@ -264,8 +264,8 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Associates the name with a pid or a port identifier. name, which must
|
||||
be an atom, can be used instead of the pid / port identifier in the
|
||||
send operator (name <- message).
|
||||
be an atom, can be used instead of the pid / port identifier with the
|
||||
`Kernel.send/2` function.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
|
||||
"""
|
||||
|
||||
@@ -73,7 +73,9 @@ defimpl Range.Iterator, for: Integer do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Range do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
|
||||
concat [to_doc(first, opts), "..", to_doc(last, opts)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -96,9 +96,9 @@ defmodule Record do
|
||||
|
||||
Notice that now since the record definition is accessible, Elixir
|
||||
shows the record nicely formatted, no longer as a simple tuple. We
|
||||
can get the raw formatting by passing `raw: true` to `inspect`:
|
||||
can get the raw formatting by passing `records: false` to `inspect`:
|
||||
|
||||
inspect user(), raw: true
|
||||
inspect user(), records: false
|
||||
{ User, "José", 25 }
|
||||
|
||||
Since working with external records is common, Elixir allows
|
||||
@@ -436,7 +436,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
# Store fields that can be optimized and that cannot be
|
||||
# optimized as they are overriden
|
||||
# optimized as they are overridden
|
||||
@doc false
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = { name, length(args) }
|
||||
@@ -487,10 +487,10 @@ defmodule Record do
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
quote do: { unquote_splicing([atom|match]) }
|
||||
{ :{}, [], [atom|match] }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -713,7 +713,7 @@ defmodule Record do
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(:orddict.from_list(sorted))
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -808,11 +808,11 @@ defmodule Record do
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
v = find_index(values, key, 1)
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> elem(record, unquote(v))
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,10 +6,11 @@ defmodule Record.Extractor do
|
||||
def retrieve(name, from: file) when is_binary(file) do
|
||||
file = String.to_char_list!(file)
|
||||
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> realfile = file
|
||||
realfile -> :ok
|
||||
end
|
||||
realfile =
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> file
|
||||
realfile -> realfile
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
end
|
||||
|
||||
+167
-96
@@ -340,7 +340,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.each([1, 2, 3], fn(x) -> self <- x end)
|
||||
iex> stream = Stream.each([1, 2, 3], fn(x) -> send self, x end)
|
||||
iex> Enum.to_list(stream)
|
||||
iex> receive do: (x when is_integer(x) -> x)
|
||||
1
|
||||
@@ -360,6 +360,22 @@ defmodule Stream do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on enumeration and
|
||||
flatten the result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1, 2, 2, 4, 3, 6]
|
||||
|
||||
"""
|
||||
@spec flat_map(Enumerable.t, (element -> Enumerable.t)) :: Enumerable.t
|
||||
def flat_map(enum, mapper) do
|
||||
transform(enum, nil, fn val, nil -> { mapper.(val), nil } end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that filters elements according to
|
||||
the given function on enumeration.
|
||||
@@ -410,99 +426,6 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.map(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that applies the given function to each
|
||||
element, emits the result and uses the same result as the accumulator
|
||||
for the next computation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.scan(1..5, &(&1 + &2))
|
||||
iex> Enum.to_list(stream)
|
||||
[1,3,6,10,15]
|
||||
|
||||
"""
|
||||
@spec scan(Enumerable.t, (element, acc -> any)) :: Enumerable.t
|
||||
def scan(enum, fun) do
|
||||
lazy enum, :first, fn(f1) -> R.scan_2(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that applies the given function to each
|
||||
element, emits the result and uses the same result as the accumulator
|
||||
for the next computation. Uses the given `acc` as the starting value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.scan(1..5, 0, &(&1 + &2))
|
||||
iex> Enum.to_list(stream)
|
||||
[1,3,6,10,15]
|
||||
|
||||
"""
|
||||
@spec scan(Enumerable.t, acc, (element, acc -> any)) :: Enumerable.t
|
||||
def scan(enum, acc, fun) do
|
||||
lazy enum, acc, fn(f1) -> R.scan_3(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on enumeration and
|
||||
flatten the result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1, 2, 2, 4, 3, 6]
|
||||
|
||||
"""
|
||||
@spec flat_map(Enumerable.t, (element -> any)) :: Enumerable.t
|
||||
def flat_map(enum, mapper) do
|
||||
&do_flat_map(enum, mapper, &1, &2)
|
||||
end
|
||||
|
||||
defp do_flat_map(enumerables, mapper, acc, fun) do
|
||||
fun = &do_flat_map_each(fun, &1, &2)
|
||||
step = &do_flat_map_step/2
|
||||
next = &Enumerable.reduce(enumerables, &1, step)
|
||||
do_flat_map([], next, mapper, acc, fun)
|
||||
end
|
||||
|
||||
defp do_flat_map(next_acc, next, mapper, acc, fun) do
|
||||
case next.({ :cont, next_acc }) do
|
||||
{ :suspended, [val|next_acc], next } ->
|
||||
do_flat_map(next_acc, next, mapper, acc, fun, &Enumerable.reduce(mapper.(val), &1, fun))
|
||||
{ reason, _ } ->
|
||||
{ reason, elem(acc, 1) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_flat_map(next_acc, next, mapper, acc, fun, reduce) do
|
||||
try do
|
||||
reduce.(acc)
|
||||
catch
|
||||
{ :stream_flat_map, h } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, h }
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ _, acc } -> do_flat_map(next_acc, next, mapper, { :cont, acc }, fun)
|
||||
{ :suspended, acc, c } -> { :suspended, acc, &do_flat_map(next_acc, next, mapper, &1, fun, c) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_flat_map_each(f, x, acc) do
|
||||
case f.(x, acc) do
|
||||
{ :halt, h } -> throw({ :stream_flat_map, h })
|
||||
{ _, _ } = o -> o
|
||||
end
|
||||
end
|
||||
|
||||
defp do_flat_map_step(x, acc) do
|
||||
{ :suspend, [x|acc] }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will reject elements according to
|
||||
the given function on enumeration.
|
||||
@@ -543,6 +466,40 @@ defmodule Stream do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that applies the given function to each
|
||||
element, emits the result and uses the same result as the accumulator
|
||||
for the next computation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.scan(1..5, &(&1 + &2))
|
||||
iex> Enum.to_list(stream)
|
||||
[1,3,6,10,15]
|
||||
|
||||
"""
|
||||
@spec scan(Enumerable.t, (element, acc -> any)) :: Enumerable.t
|
||||
def scan(enum, fun) do
|
||||
lazy enum, :first, fn(f1) -> R.scan_2(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that applies the given function to each
|
||||
element, emits the result and uses the same result as the accumulator
|
||||
for the next computation. Uses the given `acc` as the starting value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.scan(1..5, 0, &(&1 + &2))
|
||||
iex> Enum.to_list(stream)
|
||||
[1,3,6,10,15]
|
||||
|
||||
"""
|
||||
@spec scan(Enumerable.t, acc, (element, acc -> any)) :: Enumerable.t
|
||||
def scan(enum, acc, fun) do
|
||||
lazy enum, acc, fn(f1) -> R.scan_3(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily takes the next `n` items from the enumerable and stops
|
||||
enumeration.
|
||||
@@ -639,6 +596,120 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
It expects an accumulator and a function that receives each stream item
|
||||
and an accumulator, and must return a tuple containing a new stream
|
||||
(often a list) with the new accumulator or a tuple with `:halt` as first
|
||||
element and the accumulator as second.
|
||||
|
||||
Note: this function is similar to `Enum.flat_map_reduce/3` except the
|
||||
latter returns both the flat list and accumulator, while this one returns
|
||||
only the stream.
|
||||
|
||||
## Examples
|
||||
|
||||
`Stream.transform/3` is a useful as it can be used as basis to implement
|
||||
many of the functions defined in this module. For example, we can implement
|
||||
`Stream.take(enum, n)` as follows:
|
||||
|
||||
iex> enum = 1..100
|
||||
iex> n = 3
|
||||
iex> stream = Stream.transform(enum, 0, fn i, acc ->
|
||||
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
|
||||
...> end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec transform(Enumerable.t, acc, fun) :: Enumerable.t when
|
||||
fun: (element, acc -> { Enumerable.t, acc } | { :halt, acc }),
|
||||
acc: any
|
||||
def transform(enum, acc, reducer) do
|
||||
&do_transform(enum, acc, reducer, &1, &2)
|
||||
end
|
||||
|
||||
defp do_transform(enumerables, user_acc, user, inner_acc, fun) do
|
||||
inner = &do_transform_each(&1, &2, fun)
|
||||
step = &do_transform_step(&1, &2)
|
||||
next = &Enumerable.reduce(enumerables, &1, step)
|
||||
do_transform(user_acc, user, fun, [], next, inner_acc, inner)
|
||||
end
|
||||
|
||||
defp do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner) do
|
||||
case next.({ :cont, next_acc }) do
|
||||
{ :suspended, [val|next_acc], next } ->
|
||||
try do
|
||||
user.(val, user_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ [], user_acc } ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
||||
{ list, user_acc } when is_list(list) ->
|
||||
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
|
||||
{ :halt, _user_acc } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, elem(inner_acc, 1) }
|
||||
{ other, user_acc } ->
|
||||
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
|
||||
end
|
||||
{ reason, _ } ->
|
||||
{ reason, elem(inner_acc, 1) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
|
||||
try do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ :done, acc } ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
|
||||
{ :halted, acc } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, acc }
|
||||
{ :suspended, acc, c } ->
|
||||
{ :suspended, acc, &do_list_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
|
||||
try do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
{ :stream_transform, h } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, h }
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ _, acc } ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
|
||||
{ :suspended, acc, c } ->
|
||||
{ :suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_each(x, acc, f) do
|
||||
case f.(x, acc) do
|
||||
{ :halt, h } -> throw({ :stream_transform, h })
|
||||
{ _, _ } = o -> o
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_step(x, acc) do
|
||||
{ :suspend, [x|acc] }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that only emits elements if they are unique.
|
||||
|
||||
@@ -892,14 +963,14 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
Stream.resource(fn -> File.open("sample") end,
|
||||
Stream.resource(fn -> File.open!("sample") end,
|
||||
fn file ->
|
||||
case IO.read(file, :line) do
|
||||
data when is_binary(data) -> { data, file }
|
||||
_ -> nil
|
||||
end
|
||||
end,
|
||||
fn file -> File.close!(file) end)
|
||||
fn file -> File.close(file) end)
|
||||
|
||||
"""
|
||||
@spec resource((() -> acc), (acc -> { element, acc } | nil), (acc -> term)) :: Enumerable.t
|
||||
|
||||
+13
-13
@@ -149,7 +149,7 @@ defmodule String do
|
||||
def printable?(<<?\a, t :: binary>>), do: printable?(t)
|
||||
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(_), do: false
|
||||
def printable?(b) when is_binary(b), do: false
|
||||
|
||||
@doc """
|
||||
Divides a string into substrings at each Unicode whitespace
|
||||
@@ -497,7 +497,7 @@ defmodule String do
|
||||
"a-b,c"
|
||||
|
||||
The pattern can also be a regex. In those cases, one can give `\N`
|
||||
in the `replacement` string to access a specific catpure in the regex:
|
||||
in the `replacement` string to access a specific capture in the regex:
|
||||
|
||||
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
|
||||
"a,bb,cc"
|
||||
@@ -562,7 +562,7 @@ defmodule String do
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
|
||||
defp do_reverse(nil, acc), do: iolist_to_binary(acc)
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
@@ -602,7 +602,7 @@ defmodule String do
|
||||
Returns the next codepoint in a String.
|
||||
|
||||
The result is a tuple with the codepoint and the
|
||||
remaining of the string or `:no_codepoint` in case
|
||||
remaining of the string or `nil` in case
|
||||
the string reached its end.
|
||||
|
||||
As with other functions in the String module, this
|
||||
@@ -617,7 +617,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@compile { :inline, next_codepoint: 1 }
|
||||
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
|
||||
@spec next_codepoint(t) :: {codepoint, t} | nil
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc %S"""
|
||||
@@ -693,7 +693,7 @@ defmodule String do
|
||||
Returns the next grapheme in a String.
|
||||
|
||||
The result is a tuple with the grapheme and the
|
||||
remaining of the string or `:no_grapheme` in case
|
||||
remaining of the string or `nil` in case
|
||||
the String reached its end.
|
||||
|
||||
## Examples
|
||||
@@ -703,7 +703,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@compile { :inline, next_grapheme: 1 }
|
||||
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
|
||||
@spec next_grapheme(t) :: { grapheme, t } | nil
|
||||
defdelegate next_grapheme(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
@@ -722,7 +722,7 @@ defmodule String do
|
||||
def first(string) do
|
||||
case next_grapheme(string) do
|
||||
{ char, _ } -> char
|
||||
:no_grapheme -> nil
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -747,7 +747,7 @@ defmodule String do
|
||||
do_last(next_grapheme(rest), char)
|
||||
end
|
||||
|
||||
defp do_last(:no_grapheme, last_char), do: last_char
|
||||
defp do_last(nil, last_char), do: last_char
|
||||
|
||||
@doc """
|
||||
Returns the number of unicode graphemes in an utf8 string.
|
||||
@@ -769,7 +769,7 @@ defmodule String do
|
||||
1 + do_length(next_grapheme(rest))
|
||||
end
|
||||
|
||||
defp do_length(:no_grapheme), do: 0
|
||||
defp do_length(nil), do: 0
|
||||
|
||||
@doc """
|
||||
Returns the grapheme in the `position` of the given utf8 `string`.
|
||||
@@ -811,7 +811,7 @@ defmodule String do
|
||||
char
|
||||
end
|
||||
|
||||
defp do_at(:no_grapheme, _, _), do: nil
|
||||
defp do_at(nil, _, _), do: nil
|
||||
|
||||
@doc """
|
||||
Returns a substring starting at the offset given by the first, and
|
||||
@@ -934,11 +934,11 @@ defmodule String do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, _, _, _, acc) do
|
||||
defp do_slice(nil, _, _, _, acc) do
|
||||
case acc do
|
||||
"" -> nil
|
||||
_ -> acc
|
||||
|
||||
@@ -28,8 +28,8 @@ defmodule System do
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped("VERSION") do
|
||||
"" -> raise CompileError, message: "could not read the version number from VERSION"
|
||||
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
|
||||
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
@@ -38,7 +38,7 @@ defmodule System do
|
||||
# the most recent tag. If that is not available, tries to read the commit hash
|
||||
# from .git/HEAD. If that fails, returns an empty string.
|
||||
defmacrop get_describe do
|
||||
dirpath = ".git"
|
||||
dirpath = :filename.join(__DIR__, "../../../.git")
|
||||
case :file.read_file_info(dirpath) do
|
||||
{ :ok, _ } ->
|
||||
if :os.find_executable('git') do
|
||||
|
||||
@@ -76,10 +76,10 @@ defmodule URI do
|
||||
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
|
||||
|
||||
@doc """
|
||||
Decodes a query string into an orddict.
|
||||
Decodes a query string into a `HashDict`.
|
||||
|
||||
Given a query string of the form "key1=value1&key2=value2...", produces an
|
||||
orddict with one entry for each key-value pair. Each key and value will be a
|
||||
Given a query string of the form "key1=value1&key2=value2...", produces a
|
||||
`HashDict` with one entry for each key-value pair. Each key and value will be a
|
||||
binary. Keys and values will be percent-unescaped.
|
||||
|
||||
Use `query_decoder/1` if you want to iterate over each value manually.
|
||||
@@ -119,10 +119,10 @@ defmodule URI do
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
next =
|
||||
{ first, next } =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
[first] -> ""
|
||||
[first, rest] -> { first, rest }
|
||||
[first] -> { first, "" }
|
||||
end
|
||||
|
||||
current =
|
||||
|
||||
@@ -140,7 +140,7 @@ defmodule Version do
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.parse("2.0.1-alpha1")
|
||||
iex> Version.parse("2.0.1-alpha1")
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
|
||||
"""
|
||||
@@ -193,7 +193,7 @@ defmodule Version do
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
#Version.Schema<2.0.1-alpha.1>
|
||||
|
||||
"""
|
||||
|
||||
+82
-51
@@ -1,9 +1,11 @@
|
||||
%% Main entry point for Elixir functions. All of those functions are
|
||||
%% private to the Elixir compiler and reserved to be used by Elixir only.
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4, eval_forms/3,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4]).
|
||||
string_to_quoted/4, 'string_to_quoted!'/4,
|
||||
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
|
||||
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Top level types
|
||||
@@ -11,7 +13,7 @@
|
||||
-type char_list() :: string().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
% OTP APPLICATION API
|
||||
%% OTP Application API
|
||||
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
@@ -22,7 +24,7 @@ start(_Type, _Args) ->
|
||||
io:setopts(standard_io, [binary,{encoding,unicode}]),
|
||||
io:setopts(standard_error, [binary,{encoding,unicode}])
|
||||
end),
|
||||
elixir_sup:start_link([]).
|
||||
elixir_sup:start_link().
|
||||
|
||||
stop(_S) ->
|
||||
ok.
|
||||
@@ -44,45 +46,48 @@ start_cli() ->
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
scope_for_eval(#elixir_scope{
|
||||
file = <<"nofile">>,
|
||||
env_for_eval(Opts) ->
|
||||
env_for_eval(#elixir_env{
|
||||
local = nil,
|
||||
aliases = [],
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
scope_for_eval(Scope, Opts) ->
|
||||
env_for_eval(Env, Opts) ->
|
||||
Line = case lists:keyfind(line, 1, Opts) of
|
||||
{ line, RawLine } when is_integer(RawLine) -> RawLine;
|
||||
false -> Env#elixir_env.line
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } when is_binary(RawFile) -> RawFile;
|
||||
false -> Scope#elixir_scope.file
|
||||
false -> Env#elixir_env.file
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Scope#elixir_scope.local
|
||||
false -> Env#elixir_env.local
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Scope#elixir_scope.aliases
|
||||
false -> Env#elixir_env.aliases
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Scope#elixir_scope.requires
|
||||
false -> Env#elixir_env.requires
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Scope#elixir_scope.functions
|
||||
false -> Env#elixir_env.functions
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Scope#elixir_scope.macros
|
||||
false -> Env#elixir_env.macros
|
||||
end,
|
||||
|
||||
Module = case lists:keyfind(module, 1, Opts) of
|
||||
@@ -90,54 +95,80 @@ scope_for_eval(Scope, Opts) ->
|
||||
false -> nil
|
||||
end,
|
||||
|
||||
Scope#elixir_scope{
|
||||
Env#elixir_env{
|
||||
file=File, local=Local, module=Module,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases }.
|
||||
requires=Requires, aliases=Aliases, line=Line
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
eval(String, Binding) -> eval(String, Binding, []).
|
||||
eval(String, Binding) ->
|
||||
eval(String, Binding, []).
|
||||
|
||||
eval(String, Binding, Opts) ->
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
false -> Line = 1;
|
||||
{ line, Line } -> []
|
||||
end,
|
||||
eval(String, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
eval(String, Binding, Line, #elixir_scope{file=File} = S) when
|
||||
eval(String, Binding, Opts) when is_list(Opts) ->
|
||||
eval(String, Binding, env_for_eval(Opts));
|
||||
eval(String, Binding, #elixir_env{line=Line,file=File} = E) when
|
||||
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, S).
|
||||
Forms = 'string_to_quoted!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, E).
|
||||
|
||||
%% Quoted evaluation
|
||||
|
||||
eval_quoted(Tree, Binding) -> eval_quoted(Tree, Binding, []).
|
||||
eval_quoted(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_quoted(Tree, Binding, env_for_eval(Opts));
|
||||
eval_quoted(Tree, Binding, #elixir_env{line=Line} = E) ->
|
||||
eval_forms(elixir_quote:linify(Line, Tree), Binding, E).
|
||||
|
||||
eval_quoted(Tree, Binding, Opts) ->
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } -> [];
|
||||
false -> Line = 1
|
||||
end,
|
||||
eval_quoted(Tree, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
|
||||
eval_forms(elixir_quote:linify(Line, Tree), Binding, S).
|
||||
|
||||
%% Handle forms evaluation internally, it is an
|
||||
%% internal API not meant for external usage.
|
||||
%% Handle forms evaluation. The main difference to
|
||||
%% to eval_quoted is that it does not linefy the given
|
||||
%% args.
|
||||
|
||||
eval_forms(Tree, Binding, #elixir_env{} = E) ->
|
||||
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E));
|
||||
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, scope_for_eval(Opts));
|
||||
|
||||
eval_forms(Tree, Binding, Scope) ->
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts)).
|
||||
eval_forms(Tree, Binding, Env, Scope) ->
|
||||
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope),
|
||||
{ Exprs, NewScope } = elixir_translator:translate(Tree, ParsedScope),
|
||||
case Exprs of
|
||||
[] ->
|
||||
{ nil, Binding, NewScope };
|
||||
ParsedEnv = Env#elixir_env{vars=[K || {K,_} <- ParsedScope#elixir_scope.vars]},
|
||||
{ Erl, NewEnv, NewScope } = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
|
||||
|
||||
case Erl of
|
||||
{ atom, _, Atom } ->
|
||||
{ Atom, Binding, NewEnv, NewScope };
|
||||
_ ->
|
||||
{ value, Value, NewBinding } = erl_eval:exprs(Exprs, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewScope }
|
||||
{ value, Value, NewBinding } = erl_eval:expr(Erl, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope }
|
||||
end.
|
||||
|
||||
%% Converts a quoted expression to erlang abstract format
|
||||
|
||||
quoted_to_erl(Quoted, Env) ->
|
||||
quoted_to_erl(Quoted, Env, elixir_env:env_to_scope(Env)).
|
||||
|
||||
quoted_to_erl(Quoted, Env, Scope) ->
|
||||
{ Expanded, NewEnv } = elixir_exp:expand(Quoted, Env),
|
||||
{ Erl, NewScope } = elixir_translator:translate(Expanded, Scope),
|
||||
{ Erl, NewEnv, NewScope }.
|
||||
|
||||
%% Converts a given string (char list) into quote expression
|
||||
|
||||
string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, _Line, Tokens } ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
end;
|
||||
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
end.
|
||||
|
||||
'string_to_quoted!'(String, StartLine, File, Opts) ->
|
||||
case string_to_quoted(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } ->
|
||||
Forms;
|
||||
{ error, { Line, Error, Token } } ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
-module(elixir_aliases).
|
||||
-export([last/1, concat/1, safe_concat/1, format_error/1,
|
||||
ensure_loaded/3, ensure_loaded/4, expand/4, store/7]).
|
||||
-export([inspect/1, last/1, concat/1, safe_concat/1, format_error/1,
|
||||
ensure_loaded/3, expand/4, store/7]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
false -> 'Elixir.Inspect.Atom':inspect(Atom)
|
||||
end.
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
|
||||
{ Aliases, MacroAliases };
|
||||
@@ -68,22 +74,17 @@ expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
ensure_loaded(Line, Ref, S) ->
|
||||
ensure_loaded(Line, S#elixir_scope.file, Ref, S#elixir_scope.context_modules).
|
||||
|
||||
ensure_loaded(_Line, _File, 'Elixir.Kernel', _FileModules) ->
|
||||
ok;
|
||||
|
||||
ensure_loaded(Line, File, Ref, FileModules) ->
|
||||
ensure_loaded(_Meta, 'Elixir.Kernel', _E) -> ok;
|
||||
ensure_loaded(Meta, Ref, E) ->
|
||||
try
|
||||
Ref:module_info(compile)
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, FileModules) of
|
||||
Kind = case lists:member(Ref, E#elixir_env.context_modules) of
|
||||
true -> scheduled_module;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { Kind, Ref })
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, { Kind, Ref })
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the last bit as an alias.
|
||||
@@ -136,8 +137,8 @@ lookup(Else, Dict, Counter) ->
|
||||
%% Errors
|
||||
|
||||
format_error({unloaded_module, Module}) ->
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_aliases:inspect(Module)]);
|
||||
|
||||
format_error({scheduled_module, Module}) ->
|
||||
io_lib:format("module ~ts is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[inspect(Module)]).
|
||||
@@ -0,0 +1,202 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_bitstring).
|
||||
-export([translate/3, expand/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Expansion
|
||||
|
||||
expand(Meta, Args, E) ->
|
||||
case E#elixir_env.context of
|
||||
match ->
|
||||
{ EArgs, EA } = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
|
||||
{ { '<<>>', Meta, EArgs }, EA };
|
||||
_ ->
|
||||
{ EArgs, { EC, EV } } = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], { E, E }),
|
||||
{ { '<<>>', Meta, EArgs }, elixir_env:mergea(EV, EC) }
|
||||
end.
|
||||
|
||||
expand_bitstr(_Fun, [], Acc, E) ->
|
||||
{ lists:reverse(Acc), E };
|
||||
expand_bitstr(Fun, [{'::',Meta,[Left,Right]}|T], Acc, E) ->
|
||||
{ ELeft, EL } = Fun(Left, E),
|
||||
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case E of
|
||||
{ ER, _ } -> ok; %% expand_arg, no assigns
|
||||
_ -> ER = E#elixir_env{context=nil} %% expand_each, revert assigns
|
||||
end,
|
||||
|
||||
ERight = expand_bit_info(Meta, Right, ER),
|
||||
expand_bitstr(Fun, T, [{'::',Meta,[ELeft,ERight]}|Acc], EL);
|
||||
|
||||
expand_bitstr(Fun, [H|T], Acc, E) ->
|
||||
{ Expr, ES } = Fun(H, E),
|
||||
expand_bitstr(Fun, T, [Expr|Acc], ES).
|
||||
|
||||
%% Expand bit info
|
||||
|
||||
expand_bit_info(Meta, Info, E) when is_list(Info) ->
|
||||
expand_bit_info(Meta, Info, default, [], E);
|
||||
|
||||
expand_bit_info(Meta, Info, E) ->
|
||||
expand_bit_info(Meta, [Info], E).
|
||||
|
||||
expand_bit_info(Meta, [{ Expr, ExprMeta, Args }|T], Size, Types, E) when is_atom(Expr) ->
|
||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||
case expand_bit_type_or_size(Expr, ListArgs) of
|
||||
type ->
|
||||
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, Size, [{ Expr, [], EArgs }|Types], EE);
|
||||
size ->
|
||||
case Size of
|
||||
default -> ok;
|
||||
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file, "duplicated size definition in bitstring")
|
||||
end,
|
||||
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, { Expr, [], EArgs }, Types, EE);
|
||||
none ->
|
||||
handle_unknown_bit_info(Meta, { Expr, ExprMeta, ListArgs }, T, Size, Types, E)
|
||||
end;
|
||||
|
||||
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
|
||||
expand_bit_info(Meta, [{ size, [], [Int] }|T], Size, Types, E);
|
||||
|
||||
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file,
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
||||
|
||||
expand_bit_info(_Meta, [], Size, Types, _) ->
|
||||
case Size of
|
||||
default -> lists:reverse(Types);
|
||||
_ -> [Size|lists:reverse(Types)]
|
||||
end.
|
||||
|
||||
expand_bit_type_or_size(binary, []) -> type;
|
||||
expand_bit_type_or_size(integer, []) -> type;
|
||||
expand_bit_type_or_size(float, []) -> type;
|
||||
expand_bit_type_or_size(bitstring, []) -> type;
|
||||
expand_bit_type_or_size(bytes, []) -> type;
|
||||
expand_bit_type_or_size(bits, []) -> type;
|
||||
expand_bit_type_or_size(utf8, []) -> type;
|
||||
expand_bit_type_or_size(utf16, []) -> type;
|
||||
expand_bit_type_or_size(utf32, []) -> type;
|
||||
expand_bit_type_or_size(signed, []) -> type;
|
||||
expand_bit_type_or_size(unsigned, []) -> type;
|
||||
expand_bit_type_or_size(big, []) -> type;
|
||||
expand_bit_type_or_size(little, []) -> type;
|
||||
expand_bit_type_or_size(native, []) -> type;
|
||||
expand_bit_type_or_size(unit, [_]) -> type;
|
||||
expand_bit_type_or_size(size, [_]) -> size;
|
||||
expand_bit_type_or_size(_, _) -> none.
|
||||
|
||||
handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:env_to_ex({ ?line(ExprMeta), E })) of
|
||||
Expr ->
|
||||
elixir_errors:compile_error(ExprMeta, E#elixir_env.file,
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
Other ->
|
||||
List = case is_list(Other) of true -> Other; false -> [Other] end,
|
||||
expand_bit_info(Meta, List ++ T, Size, Types, E)
|
||||
end.
|
||||
|
||||
%% Translation
|
||||
|
||||
translate(Meta, Args, S) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate/2, Args, Meta, S);
|
||||
_ ->
|
||||
build_bitstr(fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end, Args, Meta, S)
|
||||
end.
|
||||
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{ Acc, S };
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
{ Size, Types } = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, Types) when is_binary(H) ->
|
||||
Element =
|
||||
case types_allow_splice(Types, []) of
|
||||
true ->
|
||||
%% See explanation in elixir_utils:elixir_to_erl/1 to know
|
||||
%% why we can simply convert the binary to a list.
|
||||
{ bin_element, ?line(Meta), { string, 0, binary_to_list(H) }, default, default };
|
||||
false ->
|
||||
case types_require_conversion(Types) of
|
||||
true ->
|
||||
{ bin_element, ?line(Meta), { string, 0, elixir_utils:characters_to_list(H) }, default, Types };
|
||||
false ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid types for literal string in <<>>. "
|
||||
"Accepted types are: little, big, utf8, utf16, utf32, bits, bytes, binary, bitstring")
|
||||
end
|
||||
end,
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, [Element|Acc]);
|
||||
|
||||
build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_binary(H) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "size is not supported for literal string in <<>>");
|
||||
|
||||
build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_list(H); is_atom(H) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid literal ~ts in <<>>",
|
||||
['Elixir.Macro':to_string(H)]);
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
case Expr of
|
||||
{ bin, _, Elements } ->
|
||||
case (Size == default) andalso types_allow_splice(Types, Elements) of
|
||||
true -> build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
|
||||
false -> build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
end;
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
end.
|
||||
|
||||
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
|
||||
types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_require_conversion(T);
|
||||
types_require_conversion([]) -> true;
|
||||
types_require_conversion(_) -> false.
|
||||
|
||||
types_allow_splice([bytes], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([binary], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([bits], _) -> true;
|
||||
types_allow_splice([bitstring], _) -> true;
|
||||
types_allow_splice(default, _) -> true;
|
||||
types_allow_splice(_, _) -> false.
|
||||
|
||||
has_default_size({ bin_element, _, _, default, _ }) -> true;
|
||||
has_default_size({ bin_element, _, _, _, _ }) -> false.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_info(Meta, [{ size, _, [Arg] }|T], S) ->
|
||||
case elixir_translator:translate(Arg, S) of
|
||||
{ { Kind, _, _ } = Size, _ } when Kind == integer; Kind == var ->
|
||||
{ Size, extract_bit_type(Meta, T, S) };
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
extract_bit_info(Meta, T, S) ->
|
||||
{ default, extract_bit_type(Meta, T, S) }.
|
||||
|
||||
extract_bit_type(Meta, [{ unit, _, [Arg] }|T], S) when is_integer(Arg) ->
|
||||
[{ unit, Arg }|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(Meta, [{ unit, _, [Arg] }|_], S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
|
||||
extract_bit_type(Meta, [{ Other, _, [] }|T], S) ->
|
||||
[Other|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(_Meta, [], _S) ->
|
||||
[].
|
||||
@@ -11,12 +11,12 @@
|
||||
unless_loaded('MACRO-@', [Caller, Tree], fun() -> nil end).
|
||||
|
||||
'MACRO-def'(Caller, Call) -> 'MACRO-def'(Caller, Call, nil).
|
||||
'MACRO-def'(Caller, Call, Expr) -> definition(Caller, def, Call, Expr).
|
||||
'MACRO-defp'(Caller, Call, Expr) -> definition(Caller, defp, Call, Expr).
|
||||
'MACRO-def'(Caller, Call, Expr) -> define(Caller, def, Call, Expr).
|
||||
'MACRO-defp'(Caller, Call, Expr) -> define(Caller, defp, Call, Expr).
|
||||
|
||||
'MACRO-defmacro'(Caller, Call) -> 'MACRO-defmacro'(Caller, Call, nil).
|
||||
'MACRO-defmacro'(Caller, Call, Expr) -> definition(Caller, defmacro, Call, Expr).
|
||||
'MACRO-defmacrop'(Caller, Call, Expr) -> definition(Caller, defmacrop, Call, Expr).
|
||||
'MACRO-defmacro'(Caller, Call, Expr) -> define(Caller, defmacro, Call, Expr).
|
||||
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
|
||||
|
||||
'MACRO-defmodule'(_Caller, Alias, [{do,Block}]) ->
|
||||
{ Escaped, _ } = elixir_quote:escape(Block, false),
|
||||
@@ -35,10 +35,10 @@
|
||||
{defmodule,2},
|
||||
{defp,2}].
|
||||
|
||||
definition(_Caller, Kind, Call, Expr) ->
|
||||
define({Line,E}, Kind, Call, Expr) ->
|
||||
{ EscapedCall, UC } = elixir_quote:escape(Call, true),
|
||||
{ EscapedExpr, UE } = elixir_quote:escape(Expr, true),
|
||||
Args = [Kind, not(UC or UE), EscapedCall, EscapedExpr, env()],
|
||||
Args = [Line, Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E)],
|
||||
{ { '.', [], [elixir_def, store_definition] }, [], Args }.
|
||||
|
||||
unless_loaded(Fun, Args, Callback) ->
|
||||
|
||||
@@ -1,127 +1,109 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
|
||||
get_pairs/4, get_pairs/5, match/3, extract_guards/1]).
|
||||
-export([match/3, clause/6, clauses/3, get_pairs/2, get_pairs/3,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
|
||||
get_pairs(Meta, Key, Clauses, S) ->
|
||||
get_pairs(Meta, Key, Clauses, S, false).
|
||||
|
||||
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, { '->', _, Pairs } } ->
|
||||
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
|
||||
{ Key, Pairs } when is_list(Pairs) ->
|
||||
[{ Key, Meta, Left, Right } || { '->', Meta, [Left, Right] } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
|
||||
_ ->
|
||||
false ->
|
||||
[]
|
||||
end.
|
||||
|
||||
% Function for translating assigns.
|
||||
%% Translate matches
|
||||
|
||||
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
|
||||
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
|
||||
temp_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS } = match(Fun, Args, S#elixir_scope{context=match,
|
||||
match_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS#elixir_scope{context=Context,
|
||||
temp_vars=TempVars, backup_vars=BackupVars} };
|
||||
match_vars=MatchVars, backup_vars=BackupVars} };
|
||||
match(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
%% Translate clauses with args, guards and expressions
|
||||
|
||||
%% Function for translating a block that is preceeded by an
|
||||
%% assignment and optional guards. This is used by def* and fn.
|
||||
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
|
||||
{ TArgs, SA } = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExpr, SE } = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
assigns_block(Line, Fun, BareArgs, Exprs, S) ->
|
||||
{ Args, Guards } = extract_guards(BareArgs),
|
||||
assigns_block(Line, Fun, Args, Exprs, Guards, S).
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
assigns_block(Line, Fun, Args, Exprs, Guards, S) when is_integer(Line) ->
|
||||
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
FArgs = listify(TArgs),
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
FGuards = case Guards of
|
||||
TGuards = case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end,
|
||||
|
||||
% Uncompact expressions from the block.
|
||||
case TExprs of
|
||||
[{ block, _, FExprs }] -> [];
|
||||
_ -> FExprs = TExprs
|
||||
end,
|
||||
|
||||
{ { clause, Line, FArgs, FGuards, FExprs }, SE }.
|
||||
{ { clause, Line, TArgs, TGuards, unblock(TExpr) }, SE }.
|
||||
|
||||
% Translate/Extract guards from the given expression.
|
||||
|
||||
translate_guard(Line, Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right) };
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_guards(Right) };
|
||||
extract_guards(Else) -> { Else, [] }.
|
||||
|
||||
extract_or_clauses({ 'when', _, [Left, Right] }) -> [Left|extract_or_clauses(Right)];
|
||||
extract_or_clauses(Term) -> [Term].
|
||||
extract_or_guards({ 'when', _, [Left, Right] }) -> [Left|extract_or_guards(Right)];
|
||||
extract_or_guards(Term) -> [Term].
|
||||
|
||||
% Extract guards when multiple left side args are allowed.
|
||||
|
||||
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
||||
{ Left, Right } = elixir_utils:split_last(Args),
|
||||
{ Left, extract_or_clauses(Right) };
|
||||
{ Left, extract_or_guards(Right) };
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
|
||||
{ TC, TS } = do_match(Meta, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
|
||||
C2 = TS#elixir_scope.clause_vars,
|
||||
{ TC, TS#elixir_scope{clause_vars=elixir_scope:merge_clause_vars(C1, C2)} }.
|
||||
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
|
||||
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
|
||||
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)} }.
|
||||
|
||||
do_match(_Meta, [], S) ->
|
||||
do_clauses(_Meta, [], S) ->
|
||||
{ [], S };
|
||||
|
||||
do_match(_Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
{ [TDecoupledClause], TS };
|
||||
% do_clauses(_Meta, [DecoupledClause], S) ->
|
||||
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
% { [TDecoupledClause], TS };
|
||||
|
||||
do_match(Meta, DecoupledClauses, S) ->
|
||||
do_clauses(Meta, DecoupledClauses, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {Acc, CV}) ->
|
||||
{ TX, TAcc } = each_clause(X, Acc),
|
||||
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{ TX, TS } = each_clause(X, SAcc),
|
||||
{ TX,
|
||||
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
{ TClauses, { TS, ReverseCV } } =
|
||||
lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
CV = lists:reverse(ReverseCV),
|
||||
AllVars = lists:foldl(fun(KV, Acc) ->
|
||||
elixir_scope:merge_clause_vars(Acc, KV)
|
||||
end, orddict:new(), CV),
|
||||
AllVars = lists:foldl(fun elixir_scope:merge_vars/2, [], CV),
|
||||
|
||||
% Create a new scope that contains a list of all variables
|
||||
% defined inside all the clauses. It returns this new scope and
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
|
||||
normalize_vars(Key, Ref, Acc)
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Val }, Acc) ->
|
||||
normalize_vars(Key, Val, Acc)
|
||||
end, TS, AllVars),
|
||||
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
% that defines variables missing in one clause to the others.
|
||||
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
@@ -142,7 +124,8 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
{ VarName, _, SS } = elixir_scope:build_var('_', S),
|
||||
StorageVar = { var, Line, VarName },
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
@@ -160,21 +143,17 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
|
||||
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
|
||||
{ TBody, SB } = elixir_translator:translate([Expr], SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
||||
|
||||
each_clause({ Key, Meta, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
||||
{ TCondition, SC } = elixir_translator:translate(Condition, S),
|
||||
{ TExpr, SB } = elixir_translator:translate(Expr, SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], unblock(TExpr) }, SB }.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
@@ -182,55 +161,51 @@ each_clause({ Key, Meta, _, _ }, S) ->
|
||||
|
||||
has_match_tuple({'receive', _, _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'receive', _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'case', _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({match, _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'fun', _, { clauses, _ }}) ->
|
||||
false;
|
||||
|
||||
has_match_tuple(H) when is_tuple(H) ->
|
||||
has_match_tuple(tuple_to_list(H));
|
||||
|
||||
has_match_tuple(H) when is_list(H) ->
|
||||
lists:any(fun has_match_tuple/1, H);
|
||||
|
||||
has_match_tuple(_) -> false.
|
||||
|
||||
% Normalize the given var in between clauses
|
||||
% by picking one value as reference and retriving
|
||||
% its previous value.
|
||||
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
|
||||
FS = S#elixir_scope{
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S) ->
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
clause_vars=orddict:store(Key, Value, ClauseVars)
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ ok, OldValue } -> { var, 0, OldValue };
|
||||
{ ok, { PreValue, _ } } -> { var, 0, PreValue };
|
||||
error -> { atom, 0, nil }
|
||||
end,
|
||||
|
||||
{ { Key, Value, Expr }, FS }.
|
||||
{ { Key, Value, Expr }, VS }.
|
||||
|
||||
% Generate match vars by checking if they were updated
|
||||
% or not and assigning the previous value.
|
||||
|
||||
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
|
||||
generate_match_vars([{ Key, Value, Expr }|T], ClauseVars, Left, Right) ->
|
||||
case orddict:find(Key, ClauseVars) of
|
||||
{ ok, NewValue } ->
|
||||
{ ok, Value } ->
|
||||
generate_match_vars(T, ClauseVars, Left, Right);
|
||||
{ ok, ClauseValue } ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
|
||||
{ ok, Clause } ->
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left],
|
||||
[{ var, 0, element(1, Clause) }|Right]);
|
||||
error ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left], [Expr|Right])
|
||||
end;
|
||||
|
||||
generate_match_vars([], _ClauseVars, Left, Right) ->
|
||||
@@ -242,15 +217,5 @@ generate_match(Line, [Left], [Right]) ->
|
||||
generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
|
||||
listify(Expr) when not is_list(Expr) -> [Expr];
|
||||
listify(Expr) -> Expr.
|
||||
|
||||
%% We don't use umergec because imports, aliases and
|
||||
%% what not are not passed from one clause to the other.
|
||||
merge_clauses_scope(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller
|
||||
}.
|
||||
unblock({ 'block', _, Exprs }) -> Exprs;
|
||||
unblock(Exprs) -> [Exprs].
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-behavior(gen_server).
|
||||
|
||||
-define(timeout, 30000).
|
||||
-record(elixir_code_server, {
|
||||
compilation_status=[],
|
||||
argv=[],
|
||||
@@ -14,21 +16,19 @@
|
||||
}).
|
||||
|
||||
call(Args) ->
|
||||
gen_server:call(?MODULE, Args, get_timeout()).
|
||||
gen_server:call(?MODULE, Args, ?timeout).
|
||||
|
||||
cast(Args) ->
|
||||
gen_server:cast(?MODULE, Args).
|
||||
|
||||
get_timeout() ->
|
||||
30000.
|
||||
|
||||
%% Callbacks
|
||||
|
||||
start_link() ->
|
||||
{ ok, _ } = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
|
||||
|
||||
init(_Args) ->
|
||||
code:ensure_loaded('Elixir.Module.DispatchTracker'),
|
||||
init(ok) ->
|
||||
code:ensure_loaded('Elixir.Macro.Env'),
|
||||
code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
{ ok, #elixir_code_server{} }.
|
||||
|
||||
@@ -83,8 +83,8 @@ handle_call(erl_compiler_options, _From, Config) ->
|
||||
{ reply, Opts, Config }
|
||||
end;
|
||||
|
||||
handle_call(_Request, _From, Config) ->
|
||||
{ reply, undef, 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]} };
|
||||
@@ -131,8 +131,8 @@ handle_cast({ unload_files, Files }, Config) ->
|
||||
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
|
||||
|
||||
handle_cast(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
handle_cast(Request, Config) ->
|
||||
{ stop, { badcast, Request }, Config }.
|
||||
|
||||
handle_info(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
@@ -143,7 +143,8 @@ terminate(_Reason, _Config) ->
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
{ ok, Config }.
|
||||
|
||||
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
module_tuple(I) ->
|
||||
{ list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
|
||||
erl_compiler_options() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/4, format_error/1]).
|
||||
-export([core/0, module/4, eval_forms/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
@@ -15,7 +15,7 @@ get_opt(Key) ->
|
||||
%% Compilation entry points.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
|
||||
quoted(Forms, File).
|
||||
|
||||
quoted(Forms, File) when is_binary(File) ->
|
||||
@@ -25,8 +25,8 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
put(elixir_compiled, []),
|
||||
elixir_lexical:run(File, fun
|
||||
(Pid) ->
|
||||
Scope = elixir:scope_for_eval([{file,File}]),
|
||||
eval_forms(Forms, 1, [], Scope#elixir_scope{lexical_tracker=Pid})
|
||||
Env = elixir:env_for_eval([{line,1},{file,File}]),
|
||||
eval_forms(Forms, [], Env#elixir_env{lexical_tracker=Pid})
|
||||
end),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
@@ -45,47 +45,65 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
|
||||
%% Evaluation
|
||||
|
||||
eval_forms(Forms, Line, Vars, S) ->
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
{ Exprs, FS } = elixir_translator:translate(Forms, S),
|
||||
eval_forms(Forms, Vars, E) ->
|
||||
case (E#elixir_env.module == nil) andalso allows_fast_compilation(Forms) of
|
||||
true -> eval_compilation(Forms, Vars, E);
|
||||
false -> code_loading_compilation(Forms, Vars, E)
|
||||
end.
|
||||
|
||||
Fun = eval_fun(S#elixir_scope.module),
|
||||
Form = eval_mod(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
Args = list_to_tuple([V || { _, V } <- Vars]),
|
||||
eval_compilation(Forms, Vars, E) ->
|
||||
Binding = [{ Key, Value } || { _Name, _Kind, Key, Value } <- Vars],
|
||||
{ Result, _Binding, EE, _S } = elixir:eval_forms(Forms, Binding, E),
|
||||
{ Result, EE }.
|
||||
|
||||
code_loading_compilation(Forms, Vars, #elixir_env{line=Line} = E) ->
|
||||
Dict = [{ { Name, Kind }, { Value, 0 } } || { Name, Kind, Value, _ } <- Vars],
|
||||
S = elixir_env:env_to_scope_with_vars(E, Dict),
|
||||
{ Expr, EE, _S } = elixir:quoted_to_erl(Forms, E, S),
|
||||
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
Fun = code_fun(E#elixir_env.module),
|
||||
Form = code_mod(Fun, Expr, Line, E#elixir_env.file, Module, Vars),
|
||||
Args = list_to_tuple([V || { _, _, _, V } <- Vars]),
|
||||
|
||||
%% Pass { native, false } to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
{ module(Form, S#elixir_scope.file, [{native,false}], true, fun(_, Binary) ->
|
||||
Res = Module:Fun(Args),
|
||||
code:delete(Module),
|
||||
|
||||
module(Form, E#elixir_env.file, [{native,false}], true, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
case beam_lib:chunks(Binary, [labeled_locals]) of
|
||||
{ ok, { _, [{ labeled_locals, []}] } } ->
|
||||
code:purge(Module),
|
||||
return_module_name(I);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Purgeable =
|
||||
case beam_lib:chunks(Binary, [labeled_locals]) of
|
||||
{ ok, { _, [{ labeled_locals, []}] } } -> true;
|
||||
_ -> false
|
||||
end,
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
|
||||
end).
|
||||
|
||||
Res
|
||||
end), FS }.
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
|
||||
Res = Module:Fun(Args),
|
||||
code:delete(Module),
|
||||
if Purgeable ->
|
||||
code:purge(Module),
|
||||
return_module_name(I);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ Res, E }.
|
||||
|
||||
eval_fun(nil) -> '__FILE__';
|
||||
eval_fun(_) -> '__MODULE__'.
|
||||
code_fun(nil) -> '__FILE__';
|
||||
code_fun(_) -> '__MODULE__'.
|
||||
|
||||
eval_mod(Fun, Exprs, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Tuple = { tuple, Line, [{ var, Line, K } || { K, _ } <- Vars] },
|
||||
code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Tuple = { tuple, Line, [{ var, Line, K } || { _, _, K, _ } <- Vars] },
|
||||
Relative = elixir_utils:relative_to_cwd(File),
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(Relative), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ Fun, 1 }, { '__RELATIVE__', 0 }] },
|
||||
{ function, Line, Fun, 1, [
|
||||
{ clause, Line, [Tuple], [], Exprs }
|
||||
{ clause, Line, [Tuple], [], [Expr] }
|
||||
] },
|
||||
{ function, Line, '__RELATIVE__', 0, [
|
||||
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
|
||||
@@ -98,6 +116,11 @@ retrieve_module_name() ->
|
||||
return_module_name(I) ->
|
||||
elixir_code_server:cast({ return_module_name, I }).
|
||||
|
||||
allows_fast_compilation({ '__block__', _, Exprs }) ->
|
||||
lists:all(fun allows_fast_compilation/1, Exprs);
|
||||
allows_fast_compilation({defmodule,_,_}) -> true;
|
||||
allows_fast_compilation(_) -> false.
|
||||
|
||||
%% INTERNAL API
|
||||
|
||||
%% Compile the module by forms based on the scope information
|
||||
@@ -177,6 +200,7 @@ core_main() ->
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/stream/reducers.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
@@ -194,8 +218,7 @@ core_main() ->
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>,
|
||||
<<"lib/elixir/lib/module/dispatch_tracker.ex">>
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
|
||||
].
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
@@ -207,17 +230,19 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
format_errors([]) ->
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
|
||||
format_errors(Errors) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
|
||||
BinFile = elixir_utils:characters_to_binary(File),
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
|
||||
end, Errors).
|
||||
|
||||
format_warnings(Bootstrap, Warnings) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
|
||||
BinFile = elixir_utils:characters_to_binary(File),
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
|
||||
end, Warnings).
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
-module(elixir_counter).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
||||
-behavior(gen_server).
|
||||
|
||||
-define(timeout, 1000). %% 1 second
|
||||
-define(limit, 4294967296). %% 2^32
|
||||
|
||||
next() ->
|
||||
gen_server:call(?MODULE, next, ?timeout).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
|
||||
|
||||
init(Counter) ->
|
||||
{ ok, Counter }.
|
||||
|
||||
handle_call(next, _From, Counter) ->
|
||||
{ reply, Counter, bump(Counter) };
|
||||
handle_call(Request, _From, Counter) ->
|
||||
{ stop, { badcall, Request }, Counter }.
|
||||
|
||||
handle_cast(Request, Counter) ->
|
||||
{ stop, { badcast, Request }, Counter }.
|
||||
|
||||
handle_info(_Request, Counter) ->
|
||||
{ noreply, Counter }.
|
||||
|
||||
terminate(_Reason, _Counter) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Counter, _Extra) ->
|
||||
{ ok, Counter }.
|
||||
|
||||
bump(Counter) when Counter < ?limit ->
|
||||
Counter + 1;
|
||||
bump(_Counter) ->
|
||||
0.
|
||||
@@ -2,7 +2,7 @@
|
||||
-module(elixir_def).
|
||||
-export([table/1, clauses_table/1, setup/1,
|
||||
cleanup/1, reset_last/1, lookup_definition/2,
|
||||
delete_definition/2, store_definition/5, unwrap_definitions/2,
|
||||
delete_definition/2, store_definition/6, unwrap_definitions/1,
|
||||
store_each/8, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -48,17 +48,14 @@ delete_definition(Module, Tuple) ->
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Kind, CheckClauses, Call, Body, ExEnv) ->
|
||||
#elixir_env{line=Line} = Env = elixir_env:ex_to_env(ExEnv),
|
||||
S = elixir_env:env_to_scope(Env),
|
||||
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
E = (elixir_locals:get_cached_env(Pos))#elixir_env{line=Line},
|
||||
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
|
||||
|
||||
{ Name, Args } = case NameAndArgs of
|
||||
{ N, _, A } when is_atom(N), is_atom(A) -> { N, [] };
|
||||
{ N, _, A } when is_atom(N), is_list(A) -> { N, A };
|
||||
_ ->
|
||||
Format = [Kind, 'Elixir.Macro':to_string(NameAndArgs)],
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format)
|
||||
_ -> elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE, { invalid_def, Kind, NameAndArgs })
|
||||
end,
|
||||
|
||||
%% Now that we have verified the call format,
|
||||
@@ -78,29 +75,29 @@ store_definition(Kind, CheckClauses, Call, Body, ExEnv) ->
|
||||
LinifyGuards = elixir_quote:linify(Line, Key, Guards),
|
||||
LinifyBody = elixir_quote:linify(Line, Key, Body),
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
store_definition(Kind, Line, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, File, S).
|
||||
assert_no_aliases_name(Line, Name, Args, E),
|
||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, File, E).
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_scope{module=Module} = DS) ->
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_env{module=Module} = ER) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
S = DS#elixir_scope{function=Tuple},
|
||||
elixir_tracker:record_definition(Tuple, Kind, Module),
|
||||
E = ER#elixir_env{function=Tuple},
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
Location = retrieve_file(Line, MetaFile, Module, S),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S),
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
|
||||
Location = retrieve_location(Line, MetaFile, Module),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
{ Function, Defaults, Super } = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
File = E#elixir_env.file,
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
compile_super(Module, TS),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
|
||||
compile_super(Module, Super, E),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
|
||||
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
@@ -111,83 +108,85 @@ store_definition(Kind, Line, CheckClauses, Name, Args, Guards, Body, MetaFile, #
|
||||
|
||||
%% @on_definition
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S) ->
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true ->
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_env:scope_to_ex({ Line, S }),
|
||||
Env = elixir_env:env_to_ex({ Line, E }),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
end.
|
||||
|
||||
%% Retrieve meta file, @file or fallback to default
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_file(Line, File, Module, S) ->
|
||||
retrieve_location(Line, File, Module) ->
|
||||
case not(elixir_compiler:get_opt(internal)) andalso
|
||||
'Elixir.Module':get_attribute(Module, file) of
|
||||
X when X == nil; X == false ->
|
||||
{ elixir_utils:characters_to_list(retrieve_file(File, S)), Line };
|
||||
if
|
||||
is_binary(File) ->
|
||||
{ elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line };
|
||||
true ->
|
||||
nil
|
||||
end;
|
||||
X ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{ X, 0 }
|
||||
end.
|
||||
|
||||
retrieve_file(nil, S) -> S#elixir_scope.file;
|
||||
retrieve_file(File, _S) -> File.
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
|
||||
compile_super(Module, true, #elixir_env{function=Function}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
|
||||
compile_super(_Module, _S) -> ok.
|
||||
compile_super(_Module, _, _E) -> ok.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Body, S) when is_integer(Line) ->
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
Arity = length(Args),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
ExtendedArgs = case is_macro(Kind) of
|
||||
AllArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
|
||||
false -> Args
|
||||
end,
|
||||
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
|
||||
{ Clauses, TS } = translate_clause(Line, Kind, Unpacked, Guards, Body, S),
|
||||
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
AllArgs, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
{ Clauses, Super } = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, TS }.
|
||||
{ Function, Defaults, Super }.
|
||||
|
||||
translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
|
||||
{ [], S };
|
||||
|
||||
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
|
||||
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
|
||||
|
||||
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
Expr = expr_from_body(Line, Body),
|
||||
|
||||
{ TClause, TS } = elixir_clauses:assigns_block(Line,
|
||||
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
|
||||
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
||||
{ [], false };
|
||||
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
|
||||
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{ TClause, TS } = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_many/2, Args, Body, Guards, S),
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = { 'match', Line,
|
||||
{ 'var', Line, '__CALLER__' },
|
||||
?wrap_call(Line, elixir_env, scope_to_ex, [{ var, Line, '_@CALLER' }])
|
||||
?wrap_call(Line, elixir_env, env_to_ex, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
{ [FClause], TS }.
|
||||
{ [FClause], TS#elixir_scope.super }.
|
||||
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
|
||||
|
||||
@@ -198,13 +197,13 @@ is_macro(_) -> false.
|
||||
% Unwrap the functions stored in the functions table.
|
||||
% It returns a list of all functions to be exported, plus the macros,
|
||||
% and the body of all functions.
|
||||
unwrap_definitions(File, Module) ->
|
||||
unwrap_definitions(Module) ->
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
ets:delete(Table, last),
|
||||
unwrap_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
|
||||
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
|
||||
|
||||
unwrap_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
|
||||
|
||||
@@ -220,28 +219,24 @@ unwrap_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, F
|
||||
{ Functions, Tail };
|
||||
_ ->
|
||||
NewAll = [Tuple|All],
|
||||
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
|
||||
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
|
||||
end,
|
||||
|
||||
unwrap_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
|
||||
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
|
||||
unwrap_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{ All, Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
|
||||
|
||||
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
|
||||
|
||||
unwrap_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
Macro = { ?elixir_macro(Name), Arity + 1 },
|
||||
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
|
||||
|
||||
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ Fun, Exports, [Info|Private], Def, Defmacro };
|
||||
|
||||
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
@@ -249,10 +244,10 @@ unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, {File, _}, _}, Clauses, File, Functions, Tail) ->
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
|
||||
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, _File, Functions, Tail) ->
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
|
||||
{ Functions, [
|
||||
{ function, Line, Name, Arity, Clauses },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
@@ -312,10 +307,10 @@ check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, S) ->
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE,
|
||||
elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE,
|
||||
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
|
||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||
|
||||
@@ -323,9 +318,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
elixir_errors:syntax_error(Line, File, Message, [Atom]);
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_env{file=File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_alias, Atom });
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
@@ -355,4 +349,13 @@ format_error({ungrouped_clause,{Kind,Name,Arity,OrigLine,OrigFile}}) ->
|
||||
[Kind, Kind, Name, Arity, OrigFile, OrigLine]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]);
|
||||
|
||||
format_error({no_alias, Atom}) ->
|
||||
io_lib:format("function names should start with lowercase characters or underscore, invalid name ~ts", [Atom]);
|
||||
|
||||
format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
% Handle default clauses for function definitions.
|
||||
-module(elixir_def_defaults).
|
||||
-export([unpack/4]).
|
||||
-export([expand/2, unpack/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
expand(Args, E) ->
|
||||
lists:mapfoldl(fun
|
||||
({ '//', Meta, [Left, Right] }, Acc) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '//', Meta, [ELeft, ERight] }, EL };
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
end, E, Args).
|
||||
|
||||
unpack(Kind, Name, Args, S) ->
|
||||
unpack_each(Kind, Name, Args, [], [], S).
|
||||
|
||||
@@ -15,12 +25,12 @@ unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, S),
|
||||
{ InvokeArgs, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_many/2, Base ++ Args, S),
|
||||
{ DefInvoke, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Call = { call, Line,
|
||||
{ atom, Line, name_for_kind(Kind, Name) },
|
||||
InvokeArgs
|
||||
DefInvoke
|
||||
},
|
||||
|
||||
Clause = { clause, Line, DefArgs, [], [Call] },
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
%% Module responsible for local invocation of macros and functions.
|
||||
-module(elixir_def_local).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro, Clauses /= [];
|
||||
All, Kind == defmacrop, Clauses /= [] ->
|
||||
fun() -> get_function(Line, Module, Clauses) end;
|
||||
_ ->
|
||||
false
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
{ atom, Line, function_for }
|
||||
},
|
||||
|
||||
%% If we have a macro, its arity in the table is
|
||||
%% actually one less than in the function call
|
||||
{ Name, Arity } = case atom_to_list(RawName) of
|
||||
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
|
||||
_ -> { RawName, length(Args) }
|
||||
end,
|
||||
|
||||
FunCall = { call, Line, Remote, [
|
||||
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
|
||||
] },
|
||||
{ call, Line, FunCall, Args };
|
||||
|
||||
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
|
||||
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
@@ -43,9 +43,9 @@ store(Module, Function, GenerateName) ->
|
||||
false -> { Kind, Name }
|
||||
end,
|
||||
|
||||
case code:is_loaded('Elixir.Module.DispatchTracker') of
|
||||
case code:is_loaded('Elixir.Module.LocalsTracker') of
|
||||
{ _, _ } ->
|
||||
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
|
||||
'Elixir.Module.LocalsTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
@@ -67,7 +67,7 @@ format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
[Name, Arity, elixir_aliases:inspect(Module), Joined]).
|
||||
|
||||
format_fa({ Name, Arity }) ->
|
||||
A = atom_to_binary(Name, utf8),
|
||||
|
||||
+144
-176
@@ -2,33 +2,33 @@
|
||||
%% This module access the information stored on the scope
|
||||
%% by elixir_import and therefore assumes it is normalized (ordsets)
|
||||
-module(elixir_dispatch).
|
||||
-export([default_macros/0, default_functions/0, default_requires/0,
|
||||
dispatch_require/6, dispatch_import/5,
|
||||
-export([dispatch_import/5, dispatch_require/6,
|
||||
require_function/5, import_function/4,
|
||||
expand_import/6, expand_require/6, find_import/4, format_error/1]).
|
||||
expand_import/5, expand_require/5,
|
||||
default_functions/0, default_macros/0, default_requires/0,
|
||||
find_import/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-import(ordsets, [is_element/2]).
|
||||
-define(builtin, 'Elixir.Kernel').
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?builtin, elixir_imported_functions() } ].
|
||||
[ { ?kernel, elixir_imported_functions() } ].
|
||||
default_macros() ->
|
||||
[ { ?builtin, elixir_imported_macros() } ].
|
||||
[ { ?kernel, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
@@ -36,227 +36,200 @@ find_import(Meta, Name, Arity, S) ->
|
||||
|
||||
%% Function retrieval
|
||||
|
||||
import_function(Meta, Name, Arity, S) ->
|
||||
import_function(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{ function, Receiver } ->
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
remote_function(Receiver, Name, Arity, E);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
require_function(Meta, Receiver, Name, Arity, E);
|
||||
false ->
|
||||
case elixir_import:special_form(Name, Arity) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
elixir_locals:record_local(Tuple, E#elixir_env.module, E#elixir_env.function),
|
||||
{ local, Name, Arity }
|
||||
end
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
require_function(_Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({ Name, Arity }, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Meta, Receiver, Name, Arity, S)
|
||||
false -> remote_function(Receiver, Name, Arity, E)
|
||||
end.
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
remote_function(Receiver, Name, Arity, E) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
Tuple = { Name, Arity },
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
|
||||
Final =
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
{ remote, Final, Name, Arity }.
|
||||
|
||||
Line = ?line(Meta),
|
||||
%% Dispatches
|
||||
|
||||
{ { 'fun', Line, { function,
|
||||
{ atom, Line, Final },
|
||||
{ atom, Line, Name },
|
||||
{ integer, Line, Arity}
|
||||
} }, S }.
|
||||
|
||||
%% Function dispatch
|
||||
|
||||
dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
Module = S#elixir_scope.module,
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
Arity = length(Args),
|
||||
case expand_import(Meta, { Name, Arity }, Args, E, []) of
|
||||
{ ok, Receiver, Quoted } ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ ok, Receiver } ->
|
||||
elixir_exp:expand({ { '.', [], [Receiver, Name] }, Meta, Args }, E);
|
||||
error ->
|
||||
Callback()
|
||||
end.
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
Module = S#elixir_scope.module,
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> Callback(erlang);
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
case expand_require(Meta, Receiver, Tuple, Args, E) of
|
||||
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
error -> Callback(Receiver)
|
||||
end
|
||||
end.
|
||||
end;
|
||||
|
||||
dispatch_require(_Meta, Receiver, _Name, _Args, _E, Callback) ->
|
||||
Callback(Receiver).
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
Result = find_dispatch(Meta, Tuple, Extra, S),
|
||||
do_expand_import(Meta, Tuple, Args, Module, S, Result).
|
||||
expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
|
||||
Module = E#elixir_env.module,
|
||||
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
|
||||
Function = E#elixir_env.function,
|
||||
Local = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_locals:macro_for(Module, Name, Arity),
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
Function = S#elixir_scope.function,
|
||||
case Dispatch of
|
||||
%% In case it is an import, or the receive is the same as the
|
||||
%% current module (happens in bootstraping), we dispatch the import.
|
||||
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
%% There is a local and an import. This is a conflict.
|
||||
{ _, Receiver } when Local /= false ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
||||
%% There is no local. Dispatch the import.
|
||||
_ when Local == false ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% Dispatch to the local.
|
||||
_ ->
|
||||
case Result of
|
||||
%% In case it is an import or the receive is the same as the
|
||||
%% current module (happens in bootstraping/conflicts), resolve it.
|
||||
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
||||
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
||||
|
||||
%% There is a function and a match. This is a conflict.
|
||||
{ _, Receiver } ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
|
||||
|
||||
%% There is a function and no match, invoke the local function.
|
||||
false ->
|
||||
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
|
||||
end
|
||||
elixir_locals:record_local(Tuple, Module, Function),
|
||||
{ ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E) }
|
||||
end.
|
||||
|
||||
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
case Result of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
Endpoint = case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
{ ok, Endpoint };
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
_ ->
|
||||
{ error, noexpansion }
|
||||
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
|
||||
{ ok, _, _ } = Response -> Response;
|
||||
error -> { ok, Receiver }
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
Function = S#elixir_scope.function,
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, E) ->
|
||||
Module = E#elixir_env.module,
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
true ->
|
||||
Requires = E#elixir_env.requires,
|
||||
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
|
||||
false ->
|
||||
Info = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Info)
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
Requires = S#elixir_scope.requires,
|
||||
case (Receiver == Module) or is_element(Receiver, Requires) or skip_require(Meta) of
|
||||
true -> ok;
|
||||
false ->
|
||||
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
end,
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E) ->
|
||||
Line = ?line(Meta),
|
||||
SArg = {Line,S},
|
||||
EArg = { Line, E },
|
||||
|
||||
try
|
||||
apply(Fun, [SArg|Args])
|
||||
apply(Fun, [EArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), location(Line, S) },
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
Info = [{ Receiver, Name, length(Args), location(Line, E) }, mfa(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), EArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, E) ->
|
||||
ProperName = ?elixir_macro(Name),
|
||||
ProperArity = Arity + 1,
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E).
|
||||
|
||||
translate_expansion(Meta, Receiver, Tree, S) ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
||||
Line = ?line(Meta),
|
||||
New = S#elixir_scope.macro_counter + 1,
|
||||
Next = elixir_counter:next(),
|
||||
|
||||
try
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify_with_context_counter(Line, { Receiver, New }, Tree),
|
||||
S#elixir_scope{macro_counter=New}
|
||||
)
|
||||
elixir_exp:expand(
|
||||
elixir_quote:linify_with_context_counter(Line, { Receiver, Next }, Quoted),
|
||||
E)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
|
||||
Info = [{ Receiver, Name, Arity, location(Line, E) }, mfa(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), nil))
|
||||
end.
|
||||
|
||||
mfa(Line, #elixir_scope{module=nil} = S) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
|
||||
mfa(Line, #elixir_env{module=nil} = E) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, E) };
|
||||
mfa(Line, #elixir_env{module=Module, function=nil} = E) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, E) };
|
||||
mfa(Line, #elixir_env{module=Module, function={ Name, Arity }} = E) ->
|
||||
{ Module, Name, Arity, location(Line, E) }.
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, S) };
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
|
||||
{ Module, Name, Arity, location(Line, S) }.
|
||||
|
||||
location(Line, S) ->
|
||||
[{ file, S#elixir_scope.file }, { line, Line }].
|
||||
location(Line, E) ->
|
||||
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
|
||||
|
||||
%% Helpers
|
||||
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == { require, false }.
|
||||
|
||||
find_dispatch(Meta, Tuple, S) ->
|
||||
find_dispatch(Meta, Tuple, [], S).
|
||||
|
||||
find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
find_dispatch(Meta, Tuple, Extra, E) ->
|
||||
case is_import(Meta) of
|
||||
{ import, _ } = Import ->
|
||||
Import;
|
||||
false ->
|
||||
Functions = S#elixir_scope.functions,
|
||||
Macros = Extra ++ S#elixir_scope.macros,
|
||||
File = S#elixir_scope.file,
|
||||
FunMatch = find_dispatch(Tuple, Functions),
|
||||
MacMatch = find_dispatch(Tuple, Macros),
|
||||
Funs = E#elixir_env.functions,
|
||||
Macs = Extra ++ E#elixir_env.macros,
|
||||
FunMatch = find_dispatch(Tuple, Funs),
|
||||
MacMatch = find_dispatch(Tuple, Macs),
|
||||
|
||||
case { FunMatch, MacMatch } of
|
||||
{ [], [Receiver] } -> { macro, Receiver };
|
||||
@@ -266,7 +239,7 @@ find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
{ Name, Arity } = Tuple,
|
||||
[First, Second|_] = FunMatch ++ MacMatch,
|
||||
Error = { ambiguous_call, { First, Second, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Error)
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -286,34 +259,29 @@ is_import(Meta) ->
|
||||
end.
|
||||
|
||||
%% We've reached the invoked macro, skip it with the rest
|
||||
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [{ _, _, [E|_], _ }|_], E) ->
|
||||
Info;
|
||||
%% We've reached the elixir_dispatch internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [H|T], S) ->
|
||||
[H|prune_stacktrace(Info, T, S)];
|
||||
|
||||
prune_stacktrace(Info, [{ Mod, _, _, _ }|_], _) when Mod == elixir_dispatch; Mod == elixir_exp ->
|
||||
Info;
|
||||
prune_stacktrace(Info, [H|T], E) ->
|
||||
[H|prune_stacktrace(Info, T, E)];
|
||||
prune_stacktrace(Info, [], _) ->
|
||||
[Info].
|
||||
Info.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
|
||||
Module = elixir_errors:inspect(Receiver),
|
||||
Module = elixir_aliases:inspect(Receiver),
|
||||
io_lib:format("you must require ~ts before invoking the macro ~ts.~ts/~B",
|
||||
[Module, Module, Name, Arity]);
|
||||
|
||||
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
|
||||
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B, "
|
||||
"please rename the local macro or remove the conflicting import",
|
||||
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
[Name, Arity, elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
|
||||
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
|
||||
[Name, Arity, elixir_aliases:inspect(Mod1), elixir_aliases:inspect(Mod2)]).
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
@@ -333,14 +301,14 @@ get_optional_macros(Receiver) ->
|
||||
|
||||
elixir_imported_functions() ->
|
||||
try
|
||||
?builtin:'__info__'(functions)
|
||||
?kernel:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> in_erlang_functions()
|
||||
end.
|
||||
|
||||
elixir_imported_macros() ->
|
||||
try
|
||||
?builtin:'__info__'(macros)
|
||||
?kernel:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
@@ -377,7 +345,6 @@ in_erlang_functions() ->
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
{ iolist_to_binary, 1 },
|
||||
{ is_alive, 0 },
|
||||
{ is_atom, 1 },
|
||||
{ is_binary, 1 },
|
||||
{ is_bitstring, 1 },
|
||||
@@ -408,6 +375,7 @@ in_erlang_functions() ->
|
||||
% { now, 0 },
|
||||
{ round, 1 },
|
||||
{ self, 0 },
|
||||
{ send, 2 },
|
||||
{ size, 1 },
|
||||
{ spawn, 1 },
|
||||
{ spawn, 3 },
|
||||
|
||||
@@ -1,38 +1,49 @@
|
||||
-module(elixir_env).
|
||||
-include("elixir.hrl").
|
||||
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2,
|
||||
ex_to_scope/1, scope_to_ex/1]).
|
||||
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2, env_to_ex/1]).
|
||||
-export([mergea/2, mergev/2, merge_vars/2, merge_opt_vars/2]).
|
||||
|
||||
%% Conversion in between #elixir_env, #elixir_scope and Macro.Env
|
||||
|
||||
env_to_ex({ Line, #elixir_env{} = Env }) ->
|
||||
erlang:setelement(1, Env#elixir_env{line=Line}, 'Elixir.Macro.Env').
|
||||
|
||||
ex_to_env(Env) when element(1, Env) == 'Elixir.Macro.Env' ->
|
||||
erlang:setelement(1, Env, elixir_env).
|
||||
|
||||
env_to_scope(#elixir_env{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
macro_counter=MacroCounter,lexical_tracker=Lexical}) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
macro_counter=MacroCounter,lexical_tracker=Lexical}.
|
||||
env_to_scope(#elixir_env{module=Module,file=File,function=Function,context=Context}) ->
|
||||
#elixir_scope{module=Module,file=File,function=Function,context=Context}.
|
||||
|
||||
env_to_scope_with_vars(#elixir_env{} = Env, Vars) ->
|
||||
(env_to_scope(Env))#elixir_scope{
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=[{'',length(Vars)}]
|
||||
counter=[{'_',length(Vars)}]
|
||||
}.
|
||||
|
||||
scope_to_ex({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
macro_counter=MacroCounter,vars=Vars,lexical_tracker=Lexical} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Context, Requires, Aliases,
|
||||
Functions, Macros, MacroAliases, MacroCounter, ContextModules,
|
||||
[Pair || { Pair, _ } <- Vars], Lexical }.
|
||||
%% SCOPE MERGING
|
||||
|
||||
ex_to_scope(Env) ->
|
||||
env_to_scope(ex_to_env(Env)).
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
mergev(E1, E2) when is_list(E1) ->
|
||||
E2#elixir_env{
|
||||
vars=merge_vars(E1, E2#elixir_env.vars),
|
||||
export_vars=merge_opt_vars(E1, E2#elixir_env.export_vars)
|
||||
};
|
||||
mergev(E1, E2) ->
|
||||
E2#elixir_env{
|
||||
vars=merge_vars(E1#elixir_env.vars, E2#elixir_env.vars),
|
||||
export_vars=merge_opt_vars(E1#elixir_env.export_vars, E2#elixir_env.export_vars)
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
|
||||
mergea(E1, E2) ->
|
||||
E2#elixir_env{vars=E1#elixir_env.vars}.
|
||||
|
||||
merge_vars(V1, V2) -> ordsets:union(V1, V2).
|
||||
|
||||
merge_opt_vars(_V1, nil) -> nil;
|
||||
merge_opt_vars(nil, _V2) -> nil;
|
||||
merge_opt_vars(V1, V2) -> ordsets:union(V1, V2).
|
||||
|
||||
@@ -1,59 +1,37 @@
|
||||
% A bunch of helpers to help to deal with errors in Elixir source code.
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4, inspect/1,
|
||||
form_error/4, parse_error/4, assert_module_scope/3,
|
||||
assert_function_scope/3, assert_no_match_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3, warn/1,
|
||||
-export([compile_error/3, compile_error/4,
|
||||
form_error/4, parse_error/4, warn/1,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
warn(Warning) ->
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
|
||||
if
|
||||
CompilerPid =/= undefined ->
|
||||
elixir_code_server:cast({ register_warning, CompilerPid });
|
||||
true -> false
|
||||
end,
|
||||
|
||||
io:put_chars(standard_error, Warning).
|
||||
|
||||
%% Handle inspecting for exceptions modules
|
||||
%% Raised during expansion/translation/compilation.
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
false -> 'Elixir.Inspect.Atom':inspect(Atom)
|
||||
end.
|
||||
-spec form_error(list(), binary(), module(), any()) -> no_return().
|
||||
|
||||
%% Raised during macros translation.
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
compile_error(Meta, File, format_error(Module, Desc)).
|
||||
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string(), list()) -> no_return().
|
||||
|
||||
syntax_error(Meta, File, Message) when is_list(Message) ->
|
||||
syntax_error(Meta, File, iolist_to_binary(Message));
|
||||
|
||||
syntax_error(Meta, File, Message) when is_binary(Message) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
syntax_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.SyntaxError', elixir_utils:characters_to_binary(Message)).
|
||||
-spec compile_error(list(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
compile_error(Meta, File, iolist_to_binary(Message));
|
||||
|
||||
compile_error(Meta, File, Message) when is_binary(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', Message).
|
||||
|
||||
compile_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
|
||||
compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
||||
compile_error(Meta, File, io_lib:format(Format, Args)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
@@ -82,14 +60,6 @@ parse_error(Meta, File, Error, Token) ->
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
%% Raised during compilation
|
||||
|
||||
-spec form_error(non_neg_integer() | list(), file:filename_all(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
Message = iolist_to_binary(format_error(Module, Desc)),
|
||||
raise(Meta, File, 'Elixir.CompileError', Message).
|
||||
|
||||
%% Shows a deprecation message
|
||||
|
||||
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
|
||||
@@ -135,14 +105,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, inspect(Module)]),
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, elixir_aliases:inspect(Module)]),
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
|
||||
warn(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
@@ -179,34 +149,12 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [inspect(M), F, A]),
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
|
||||
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_scope{context=match} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in match clause", []);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_scope{context=guard} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in guard", []);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
%% Helpers
|
||||
|
||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
@@ -215,7 +163,7 @@ raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
raise(none, File, Kind, Message) ->
|
||||
raise(0, File, Kind, Message);
|
||||
|
||||
raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
|
||||
%% Populate the stacktrace so we can raise it
|
||||
try
|
||||
throw(ok)
|
||||
@@ -223,13 +171,13 @@ raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
ok -> ok
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
|
||||
Exception = Kind:new([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
file_format(0, File, Message) ->
|
||||
file_format(0, File, Message) when is_binary(File) ->
|
||||
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
|
||||
|
||||
file_format(Line, File, Message) ->
|
||||
file_format(Line, File, Message) when is_binary(File) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
|
||||
|
||||
format_var(Var) ->
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
-module(elixir_exp).
|
||||
-export([expand/2, expand_many/2, expand_args/2, expand_arg/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% =
|
||||
|
||||
expand({ '=', Meta, [Left, Right] }, E) ->
|
||||
assert_no_guard_scope(Meta, '=', E),
|
||||
{ ERight, ER } = expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{ { '=', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
|
||||
%% Literal operators
|
||||
|
||||
expand({ '{}', Meta, Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '{}', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '<<>>', Meta, Args }, E) ->
|
||||
elixir_bitstring:expand(Meta, Args, E);
|
||||
|
||||
%% Other operators
|
||||
|
||||
expand({ '__op__', Meta, [_, _] = Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '__op__', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '__op__', Meta, [_, _, _] = Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '__op__', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '->', Meta, _Args }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unhandled operator ->");
|
||||
|
||||
%% __block__
|
||||
|
||||
expand({ '__block__', _Meta, [] }, E) ->
|
||||
{ nil, E };
|
||||
expand({ '__block__', _Meta, [Arg] }, E) ->
|
||||
expand(Arg, E);
|
||||
expand({ '__block__', Meta, Args }, E) when is_list(Args) ->
|
||||
{ EArgs, EA } = expand_many(Args, E),
|
||||
{ { '__block__', Meta, EArgs }, EA };
|
||||
|
||||
%% __aliases__
|
||||
|
||||
expand({ '__aliases__', _, _ } = Alias, E) ->
|
||||
case elixir_aliases:expand(Alias, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
|
||||
Receiver when is_atom(Receiver) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ Receiver, E };
|
||||
Aliases ->
|
||||
{ EAliases, EA } = expand_args(Aliases, E),
|
||||
|
||||
case lists:all(fun is_atom/1, EAliases) of
|
||||
true ->
|
||||
Receiver = elixir_aliases:concat(EAliases),
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ Receiver, EA };
|
||||
false ->
|
||||
{ { { '.', [], [elixir_aliases, concat] }, [], [EAliases] }, EA }
|
||||
end
|
||||
end;
|
||||
|
||||
%% alias
|
||||
|
||||
expand({ alias, Meta, [Ref] }, E) ->
|
||||
expand({ alias, Meta, [Ref,[]] }, E);
|
||||
expand({ alias, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, E),
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
{ { alias, Meta, [ERef, EKV] },
|
||||
expand_alias(Meta, true, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for alias, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
expand({ require, Meta, [Ref] }, E) ->
|
||||
expand({ require, Meta, [Ref, []] }, E);
|
||||
expand({ require, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, require, E),
|
||||
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
{ { require, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for require, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
expand({ import, Meta, [Left] }, E) ->
|
||||
expand({ import, Meta, [Left, []]}, E);
|
||||
|
||||
expand({ import, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, import, E),
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, import, [only, except, warn], KV, ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
{ Functions, Macros } = elixir_import:import(Meta, ERef, EKV, ET),
|
||||
{ { import, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid name for import, expected a compile time atom or alias")
|
||||
end;
|
||||
|
||||
%% Pseudo vars
|
||||
|
||||
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ E#elixir_env.module, E };
|
||||
expand({ '__FILE__', Meta, Atom }, E) when is_atom(Atom) ->
|
||||
elixir_errors:deprecation(Meta, E#elixir_env.file, "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead"),
|
||||
{ E#elixir_env.file, E };
|
||||
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ filename:dirname(E#elixir_env.file), E };
|
||||
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
|
||||
{ Caller, E };
|
||||
expand({ '__ENV__', Meta, Atom }, E) when is_atom(Atom) ->
|
||||
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
||||
{ { '{}', [], tuple_to_list(Env) }, E };
|
||||
expand({ { '.', DotMeta, [{ '__ENV__', Meta, Atom }, Field] }, CallMeta, [] }, E) when is_atom(Atom), is_atom(Field) ->
|
||||
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
||||
case erlang:function_exported('Elixir.Macro.Env', Field, 1) of
|
||||
true -> { Env:Field(), E };
|
||||
false -> { { { '.', DotMeta, [{ '{}', [], tuple_to_list(Env) }, Field] }, CallMeta, [] }, E }
|
||||
end;
|
||||
|
||||
%% Quote
|
||||
|
||||
expand({ Unquote, Meta, [_] }, E) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
compile_error(Meta, E#elixir_env.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
expand({ quote, Meta, [Opts] }, E) when is_list(Opts) ->
|
||||
case lists:keyfind(do, 1, Opts) of
|
||||
{ do, Do } ->
|
||||
expand({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, E);
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in quote")
|
||||
end;
|
||||
|
||||
expand({ quote, Meta, [_] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
||||
|
||||
expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, Do) of
|
||||
{ do, Expr } -> Expr;
|
||||
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
|
||||
{ EKV, ET } = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, expected a compile time atom or an alias");
|
||||
false ->
|
||||
case E#elixir_env.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
|
||||
|
||||
Keep = lists:keyfind(location, 1, EKV) == { location, keep },
|
||||
Line = proplists:get_value(line, EKV, false),
|
||||
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, EKV) of
|
||||
{ bind_quoted, BQ } -> { BQ, false };
|
||||
false -> { nil, true }
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, EKV) of
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ET),
|
||||
expand(Quoted, ET);
|
||||
|
||||
expand({ quote, Meta, [_, _] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
||||
|
||||
%% Functions
|
||||
|
||||
expand({ '&', _, [Arg] } = Original, E) when is_integer(Arg) ->
|
||||
{ Original, E };
|
||||
expand({ '&', Meta, [Arg] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, '&', E),
|
||||
case elixir_fn:capture(Meta, Arg, E) of
|
||||
{ local, Fun, Arity } ->
|
||||
{ { '&', Meta, [{ '/', [], [{ Fun, [], nil }, Arity] }] }, E };
|
||||
{ expanded, Expr, EE } ->
|
||||
expand(Expr, EE)
|
||||
end;
|
||||
|
||||
expand({ fn, Meta, Pairs }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, fn, E),
|
||||
elixir_fn:expand(Meta, Pairs, E);
|
||||
|
||||
%% Case/Receive/Try
|
||||
|
||||
expand({'case', Meta, [Expr, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', E),
|
||||
{ EExpr, EE } = expand(Expr, E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'case'(Meta, KV, EE),
|
||||
{ { 'case', Meta, [EExpr, EClauses] }, EC };
|
||||
|
||||
expand({'receive', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'receive'(Meta, KV, E),
|
||||
{ { 'receive', Meta, [EClauses] }, EC };
|
||||
|
||||
expand({'try', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'try'(Meta, KV, E),
|
||||
{ { 'try', Meta, [EClauses] }, EC };
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
expand_comprehension(Meta, Kind, Args, E);
|
||||
|
||||
%% Super
|
||||
|
||||
expand({ super, Meta, Args }, E) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, super, E),
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { super, Meta, EArgs }, EA };
|
||||
|
||||
%% Vars
|
||||
|
||||
expand({ '^', Meta, [Arg] }, #elixir_env{context=match} = E) ->
|
||||
case expand(Arg, E) of
|
||||
{ { Name, _, Kind } = EArg, EA } when is_atom(Name), is_atom(Kind) ->
|
||||
{ { '^', Meta, [EArg] }, EA };
|
||||
_ ->
|
||||
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
|
||||
compile_error(Meta, E#elixir_env.file, Msg, ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
expand({ '^', Meta, [Arg] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"cannot use ^~ts outside of match clauses", ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
expand({ '_', _, Kind } = Var, E) when is_atom(Kind) ->
|
||||
{ Var, E };
|
||||
expand({ Name, Meta, Kind } = Var, #elixir_env{context=match, export_vars=Export} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
Pair = { Name, var_kind(Meta, Kind) },
|
||||
NewVars = ordsets:add_element(Pair, E#elixir_env.vars),
|
||||
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= { export, false }) of
|
||||
true -> ordsets:add_element(Pair, Export);
|
||||
false -> Export
|
||||
end,
|
||||
{ Var, E#elixir_env{vars=NewVars, export_vars=NewExport} };
|
||||
expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
case lists:member({ Name, var_kind(Meta, Kind) }, Vars) of
|
||||
true ->
|
||||
{ Var, E };
|
||||
false ->
|
||||
VarMeta = lists:keyfind(var, 1, Meta),
|
||||
if
|
||||
VarMeta == { var, true } ->
|
||||
Extra = case Kind of
|
||||
nil -> "";
|
||||
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
|
||||
end,
|
||||
|
||||
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
|
||||
"variable or be a part of a match", [Name, Extra]);
|
||||
E#elixir_env.context == guard ->
|
||||
compile_error(Meta, E#elixir_env.file, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/0 inside guard", [Name, Name]);
|
||||
true ->
|
||||
expand({ Name, Meta, [] }, E)
|
||||
end
|
||||
end;
|
||||
|
||||
%% Local calls
|
||||
|
||||
expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
||||
assert_no_ambiguous_op(Atom, Meta, Args, E),
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, E, fun() ->
|
||||
expand_local(Meta, Atom, Args, E)
|
||||
end);
|
||||
|
||||
%% Remote calls
|
||||
|
||||
expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ ELeft, EL } = expand(Left, E),
|
||||
|
||||
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(Receiver) ->
|
||||
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL)
|
||||
end);
|
||||
|
||||
%% Anonymous calls
|
||||
|
||||
expand({ { '.', DotMeta, [Expr] }, Meta, Args }, E) when is_list(Args) ->
|
||||
{ EExpr, EE } = expand(Expr, E),
|
||||
if
|
||||
is_atom(EExpr) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid function call :~ts.()", [EExpr]);
|
||||
true ->
|
||||
{ EArgs, EA } = expand_args(Args, elixir_env:mergea(E, EE)),
|
||||
{ { { '.', DotMeta, [EExpr] }, Meta, EArgs }, elixir_env:mergev(EE, EA) }
|
||||
end;
|
||||
|
||||
%% Invalid calls
|
||||
|
||||
expand({ _, Meta, Args } = Invalid, E) when is_list(Meta) and is_list(Args) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid call ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
expand({ _, _, _ } = Tuple, E) ->
|
||||
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
|
||||
|
||||
%% Literals
|
||||
|
||||
expand({ Left, Right }, E) ->
|
||||
{ [ELeft, ERight], EE } = expand_args([Left, Right], E),
|
||||
{ { ELeft, ERight }, EE };
|
||||
|
||||
expand(List, #elixir_env{context=match} = E) when is_list(List) ->
|
||||
expand_list(List, fun expand/2, E, []);
|
||||
|
||||
expand(List, E) when is_list(List) ->
|
||||
{ EArgs, { EC, EV } } = expand_list(List, fun expand_arg/2, {E, E}, []),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) };
|
||||
|
||||
expand(Function, E) when is_function(Function) ->
|
||||
case (erlang:fun_info(Function, type) == { type, external }) andalso
|
||||
(erlang:fun_info(Function, env) == { env, [] }) of
|
||||
true ->
|
||||
{ Function, E };
|
||||
false ->
|
||||
compile_error([{line,0}], E#elixir_env.file,
|
||||
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Function)])
|
||||
end;
|
||||
|
||||
expand(Other, E) when is_number(Other); is_atom(Other); is_binary(Other); is_pid(Other) ->
|
||||
{ Other, E };
|
||||
|
||||
expand(Other, E) ->
|
||||
compile_error([{line,0}], E#elixir_env.file,
|
||||
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Other)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
expand_list([{ '|', Meta, [_, _] = Args }], Fun, Acc, List) ->
|
||||
{ EArgs, EAcc } = lists:mapfoldl(Fun, Acc, Args),
|
||||
expand_list([], Fun, EAcc, [{ '|', Meta, EArgs }|List]);
|
||||
expand_list([H|T], Fun, Acc, List) ->
|
||||
{ EArg, EAcc } = Fun(H, Acc),
|
||||
expand_list(T, Fun, EAcc, [EArg|List]);
|
||||
expand_list([], _Fun, Acc, List) ->
|
||||
{ lists:reverse(List), Acc }.
|
||||
|
||||
expand_many(Args, E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args).
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
%% argument to the other. For instance:
|
||||
%%
|
||||
%% x = 1
|
||||
%% foo(x = x + 2, x)
|
||||
%% x
|
||||
%%
|
||||
%% Should be the same as:
|
||||
%%
|
||||
%% foo(3, 1)
|
||||
%% 3
|
||||
%%
|
||||
%% However, lexical information is.
|
||||
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
|
||||
{ Arg, Acc };
|
||||
expand_arg(Arg, { Acc1, Acc2 }) ->
|
||||
{ EArg, EAcc } = expand(Arg, Acc1),
|
||||
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
|
||||
|
||||
expand_args(Args, #elixir_env{context=match} = E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args);
|
||||
expand_args(Args, E) ->
|
||||
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) }.
|
||||
|
||||
%% Match/var helpers
|
||||
|
||||
var_kind(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
%% Locals
|
||||
|
||||
assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
|
||||
case lists:keyfind(ambiguous_op, 1, Meta) of
|
||||
{ ambiguous_op, Kind } ->
|
||||
case lists:member({ Name, Kind }, E#elixir_env.vars) of
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "\"~ts ~ts\" looks like a function call but "
|
||||
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
|
||||
[Name, 'Elixir.Macro':to_string(Arg), Name]);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
|
||||
ok.
|
||||
|
||||
expand_local(Meta, Name, Args, #elixir_env{local=nil, function=nil} = E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { Name, Meta, EArgs }, EA };
|
||||
expand_local(Meta, Name, Args, #elixir_env{local=nil, module=Module, function=Function} = E) ->
|
||||
elixir_locals:record_local({ Name, length(Args) }, Module, Function),
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { Name, Meta, EArgs }, EA };
|
||||
expand_local(Meta, Name, Args, E) ->
|
||||
expand({ { '.', Meta, [E#elixir_env.local, Name] }, Meta, Args }, E).
|
||||
|
||||
%% Remote
|
||||
|
||||
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
|
||||
if
|
||||
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker);
|
||||
true -> ok
|
||||
end,
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { { '.', DotMeta, [Receiver, Right] }, Meta, EArgs }, elixir_env:mergev(EL, EA) }.
|
||||
|
||||
%% Lexical helpers
|
||||
|
||||
expand_opts(Meta, Kind, Allowed, Opts, E) ->
|
||||
{ EOpts, EE } = expand(Opts, E),
|
||||
validate_opts(Meta, Kind, Allowed, EOpts, EE),
|
||||
{ EOpts, EE }.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
|
||||
[begin
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"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]).
|
||||
|
||||
no_alias_opts(KV) when is_list(KV) ->
|
||||
case lists:keyfind(as, 1, KV) of
|
||||
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
|
||||
false -> KV
|
||||
end;
|
||||
no_alias_opts(KV) -> KV.
|
||||
|
||||
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
|
||||
{ '__aliases__', Meta, ['Elixir',H|T] };
|
||||
no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
expand_require(Meta, Ref, KV, E) ->
|
||||
RE = E#elixir_env{requires=ordsets:add_element(Ref, E#elixir_env.requires)},
|
||||
expand_alias(Meta, false, Ref, KV, RE).
|
||||
|
||||
expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Context} = E) ->
|
||||
New = expand_as(lists:keyfind(as, 1, KV), Meta, IncludeByDefault, Ref, E),
|
||||
|
||||
%% Add the alias to context_modules if defined is true.
|
||||
%% This is used by defmodule in order to store the defined
|
||||
%% module in context modules.
|
||||
NewContext =
|
||||
case lists:keyfind(defined, 1, Meta) of
|
||||
{ defined, Mod } when is_atom(Mod) -> [Mod|Context];
|
||||
false -> Context
|
||||
end,
|
||||
|
||||
{ Aliases, MacroAliases } = elixir_aliases:store(Meta, New, Ref, KV, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker),
|
||||
|
||||
E#elixir_env{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext}.
|
||||
|
||||
expand_as({ as, true }, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
elixir_aliases:last(Ref);
|
||||
expand_as({ as, false }, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
Ref;
|
||||
expand_as({ as, Atom }, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
|
||||
case length(string:tokens(atom_to_list(Atom), ".")) of
|
||||
1 -> compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got atom: ~ts", [elixir_aliases:inspect(Atom)]);
|
||||
2 -> Atom;
|
||||
_ -> compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got nested alias: ~ts", [elixir_aliases:inspect(Atom)])
|
||||
end;
|
||||
expand_as(false, _Meta, IncludeByDefault, Ref, _E) ->
|
||||
if IncludeByDefault -> elixir_aliases:last(Ref);
|
||||
true -> Ref
|
||||
end;
|
||||
expand_as({ as, Other }, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand_comprehension(Meta, Kind, Args, E) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ Cases, [{do,Expr}] } ->
|
||||
{ ECases, EC } = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
|
||||
{ EExpr, _ } = expand(Expr, EC),
|
||||
{ { Kind, Meta, ECases ++ [[{do,EExpr}]] }, E };
|
||||
_ ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in comprehension ~ts", [Kind])
|
||||
end.
|
||||
|
||||
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
|
||||
{ ERight, ER } = expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{ { Gen, Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
expand_comprehension_clause(X, E) ->
|
||||
expand(X, E).
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
||||
assert_no_match_scope(Meta, Kind, E),
|
||||
assert_no_guard_scope(Meta, Kind, E).
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_env{context=match,file=File}) ->
|
||||
compile_error(Meta, File, "invalid expression in match");
|
||||
assert_no_match_scope(_Meta, _Kind, _E) -> [].
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
|
||||
compile_error(Meta, File, "invalid expression in guard");
|
||||
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
|
||||
@@ -0,0 +1,200 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_exp_clauses).
|
||||
-export([match/3, clause/5, 'case'/3, 'receive'/3, 'try'/3, def/5]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
match(Fun, Expr, #elixir_env{context=Context} = E) ->
|
||||
{ EExpr, EE } = Fun(Expr, E#elixir_env{context=match}),
|
||||
{ EExpr, EE#elixir_env{context=Context} }.
|
||||
|
||||
def(Fun, Args, Guards, Body, E) ->
|
||||
{ EArgs, EA } = match(Fun, Args, E),
|
||||
{ EGuards, EG } = guard(Guards, EA#elixir_env{context=guard}),
|
||||
{ EBody, EB } = elixir_exp:expand(Body, EG#elixir_env{context=E#elixir_env.context}),
|
||||
{ EArgs, EGuards, EBody, EB }.
|
||||
|
||||
clause(Meta, Kind, Fun, { '->', ClauseMeta, [_, _] } = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
|
||||
clause(_Meta, _Kind, Fun, { '->', Meta, [Left, Right] }, E) ->
|
||||
{ ELeft, EL } = head(Fun, Left, E),
|
||||
{ ERight, ER } = elixir_exp:expand(Right, EL),
|
||||
{ { '->', Meta, [ELeft, ERight] }, ER };
|
||||
clause(Meta, Kind, _Fun, _, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses in ~ts", [Kind]).
|
||||
|
||||
head(Fun, [{ 'when', Meta, [_,_|_] = All }], E) ->
|
||||
{ Args, Guard } = elixir_utils:split_last(All),
|
||||
{ EArgs, EA } = match(Fun, Args, E),
|
||||
{ EGuard, EG } = guard(Guard, EA#elixir_env{context=guard}),
|
||||
{ [{ 'when', Meta, EArgs ++ [EGuard] }], EG#elixir_env{context=E#elixir_env.context} };
|
||||
head(Fun, Args, E) ->
|
||||
match(Fun, Args, E).
|
||||
|
||||
guard({ 'when', Meta, [Left, Right] }, E) ->
|
||||
{ ELeft, EL } = guard(Left, E),
|
||||
{ ERight, ER } = guard(Right, EL),
|
||||
{ { 'when', Meta, [ELeft, ERight] }, ER };
|
||||
guard(Other, E) ->
|
||||
elixir_exp:expand(Other, E).
|
||||
|
||||
%% Case
|
||||
|
||||
'case'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in case");
|
||||
'case'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for case");
|
||||
'case'(Meta, KV, E) ->
|
||||
EE = E#elixir_env{export_vars=[]},
|
||||
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, EE) end, [], KV),
|
||||
{ EClauses, elixir_env:mergev(EVars, E) }.
|
||||
|
||||
do_case(Meta, { 'do', _ } = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
|
||||
do_case(Meta, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in case", [Key]).
|
||||
|
||||
%% Receive
|
||||
|
||||
'receive'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do or after keyword in receive");
|
||||
'receive'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for receive");
|
||||
'receive'(Meta, KV, E) ->
|
||||
EE = E#elixir_env{export_vars=[]},
|
||||
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, EE) end, [], KV),
|
||||
{ EClauses, elixir_env:mergev(EVars, E) }.
|
||||
|
||||
do_receive(_Meta, { 'do', nil } = Do, Acc, _E) ->
|
||||
{ Do, Acc };
|
||||
do_receive(Meta, { 'do', _ } = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
|
||||
do_receive(_Meta, { 'after', [{ '->', Meta, [[Left], Right] }] }, Acc, E) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, E),
|
||||
{ ERight, ER } = elixir_exp:expand(Right, EL),
|
||||
EClause = { 'after', [{ '->', Meta, [[ELeft], ERight] }] },
|
||||
{ EClause, elixir_env:merge_vars(Acc, ER#elixir_env.export_vars) };
|
||||
do_receive(Meta, { 'after', _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected a single -> clause for after in receive");
|
||||
do_receive(Meta, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in receive", [Key]).
|
||||
|
||||
%% Try
|
||||
|
||||
'try'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keywords in try");
|
||||
'try'(Meta, KV, E) when not is_list(KV) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file, "invalid arguments for try");
|
||||
'try'(Meta, KV, E) ->
|
||||
{ lists:map(fun(X) -> do_try(Meta, X, E) end, KV), E }.
|
||||
|
||||
do_try(_Meta, { 'do', Expr }, E) ->
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, E),
|
||||
{ 'do', EExpr };
|
||||
do_try(_Meta, { 'after', Expr }, E) ->
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, E),
|
||||
{ 'after', EExpr };
|
||||
do_try(Meta, { 'else', _ } = Else, E) ->
|
||||
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
|
||||
do_try(Meta, { 'catch', _ } = Catch, E) ->
|
||||
expand_without_export(Meta, 'try', fun elixir_exp:expand_many/2, Catch, E);
|
||||
do_try(Meta, { 'rescue', _ } = Rescue, E) ->
|
||||
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
|
||||
do_try(Meta, { Key, _ }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in try", [Key]).
|
||||
|
||||
expand_rescue(Meta, [Arg], E) ->
|
||||
case expand_rescue(Arg, E) of
|
||||
{ EArg, EA } ->
|
||||
{ [EArg], EA };
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid rescue clause. The clause should "
|
||||
"match on an alias, a variable or be in the `var in [alias]` format")
|
||||
end;
|
||||
expand_rescue(Meta, _, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected one arg for rescue clauses (->) in try").
|
||||
|
||||
%% rescue var => var in _
|
||||
expand_rescue({ Name, _, Atom } = Var, E) when is_atom(Name), is_atom(Atom) ->
|
||||
expand_rescue({ in, [], [Var, { '_', [], E#elixir_env.module }] }, E);
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
expand_rescue({ in, Meta, [Left, Right] }, E) ->
|
||||
{ ERight, ER } = elixir_exp:expand(Right, E#elixir_env{context=nil}),
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, ER#elixir_env{context=match}),
|
||||
|
||||
case ELeft of
|
||||
{ Name, _, Atom } when is_atom(Name), is_atom(Atom) ->
|
||||
case normalize_rescue(ERight) of
|
||||
false -> false;
|
||||
Other -> { { in, Meta, [ELeft, Other] }, EL }
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
|
||||
%% rescue Error => _ in [Error]
|
||||
expand_rescue(Arg, E) ->
|
||||
expand_rescue({ in, [], [{ '_', [], E#elixir_env.module }, Arg] }, E).
|
||||
|
||||
normalize_rescue({ '_', _, Atom } = N) when is_atom(Atom) -> N;
|
||||
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
|
||||
normalize_rescue(Other) ->
|
||||
is_list(Other)
|
||||
andalso lists:all(fun is_var_or_atom/1, Other)
|
||||
andalso Other.
|
||||
|
||||
is_var_or_atom({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var_or_atom(Atom) when is_atom(Atom) -> true;
|
||||
is_var_or_atom(_) -> false.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
export_vars({ Left, Meta, Right }) when is_atom(Left), is_list(Meta), is_atom(Right) ->
|
||||
{ Left, [{export,false}|Meta], Right };
|
||||
export_vars({ Left, Meta, Right }) ->
|
||||
{ export_vars(Left), Meta, export_vars(Right) };
|
||||
export_vars({ Left, Right }) ->
|
||||
{ export_vars(Left), export_vars(Right) };
|
||||
export_vars(List) when is_list(List) ->
|
||||
[export_vars(X) || X <- List];
|
||||
export_vars(Other) ->
|
||||
Other.
|
||||
|
||||
%% Returns a function that expands arguments
|
||||
%% considering we have at maximum one entry.
|
||||
expand_arg(Meta, Kind, Key) ->
|
||||
fun
|
||||
([Arg], E) ->
|
||||
{ EArg, EA } = elixir_exp:expand(Arg, E),
|
||||
{ [EArg], EA };
|
||||
(_, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
end.
|
||||
|
||||
%% Expands all -> pairs in a given key keeping the overall vars.
|
||||
expand_with_export(Meta, Kind, Fun, { Key, Clauses }, Acc, E) when is_list(Clauses) ->
|
||||
EFun =
|
||||
case lists:keyfind(export_all, 1, Meta) of
|
||||
{ export_all, true } -> Fun;
|
||||
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
{ EClause, EC } = clause(Meta, Kind, EFun, Clause, E),
|
||||
{ EClause, elixir_env:merge_vars(Vars, EC#elixir_env.export_vars) }
|
||||
end,
|
||||
{ EClauses, EVars } = lists:mapfoldl(Transformer, Acc, Clauses),
|
||||
{ { Key, EClauses }, EVars };
|
||||
expand_with_export(Meta, Kind, _Fun, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
|
||||
%% Expands all -> pairs in a given key but do not keep the overall vars.
|
||||
expand_without_export(Meta, Kind, Fun, { Key, Clauses }, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{ EClause, _ } = clause(Meta, Kind, Fun, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ Key, lists:map(Transformer, Clauses) };
|
||||
expand_without_export(Meta, Kind, _Fun, { Key, _ }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
@@ -1,13 +1,13 @@
|
||||
-module(elixir_fn).
|
||||
-export([fn/3, capture/3]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4, compile_error/4]).
|
||||
-export([translate/3, capture/3, expand/3]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
fn(Meta, Clauses, S) ->
|
||||
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
|
||||
translate(Meta, Clauses, S) ->
|
||||
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(?line(CMeta), fun translate_fn_match/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2, Args, Expr, Guards, Acc),
|
||||
{ TClause, elixir_scope:mergef(S, TS) }
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
@@ -15,21 +15,32 @@ fn(Meta, Clauses, S) ->
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, NS };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_fn_match(Arg, S) ->
|
||||
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
|
||||
{ TArg, TS } = elixir_translator:translate_many(Arg, S#elixir_scope{extra=fn_match}),
|
||||
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
|
||||
|
||||
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, S) when is_atom(F), is_integer(A) ->
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, { Dot, RequireMeta, Args }, S, true);
|
||||
%% Expansion
|
||||
|
||||
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
expand(Meta, Clauses, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_many/2, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ { fn, Meta, lists:map(Transformer, Clauses) }, E }.
|
||||
|
||||
%% Capture
|
||||
|
||||
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, E) when is_atom(F), is_integer(A) ->
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, { Dot, RequireMeta, Args }, E, true);
|
||||
|
||||
capture(Meta, { '/', _, [{ F, _, C }, A] }, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
ImportMeta =
|
||||
case lists:keyfind(import_fa, 1, Meta) of
|
||||
{ import_fa, { Receiver, Context } } ->
|
||||
@@ -40,115 +51,102 @@ capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), i
|
||||
false -> Meta
|
||||
end,
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_import(Meta, { F, ImportMeta, Args }, S, true);
|
||||
capture_import(Meta, { F, ImportMeta, Args }, E, true);
|
||||
|
||||
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is_list(Args) ->
|
||||
capture_require(Meta, Expr, S, is_sequential_and_not_empty(Args));
|
||||
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, E) when is_atom(Fun), is_list(Args) ->
|
||||
capture_require(Meta, Expr, E, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, S) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, S, false);
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, E) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, E, false);
|
||||
|
||||
capture(Meta, { '__block__', _, [Expr] }, S) ->
|
||||
capture(Meta, Expr, S);
|
||||
capture(Meta, { '__block__', _, [Expr] }, E) ->
|
||||
capture(Meta, Expr, E);
|
||||
|
||||
capture(Meta, { '__block__', _, _ } = Expr, S) ->
|
||||
capture(Meta, { '__block__', _, _ } = Expr, E) ->
|
||||
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
capture(Meta, { Atom, _, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
|
||||
capture_import(Meta, Expr, S, is_sequential_and_not_empty(Args));
|
||||
capture(Meta, { Atom, _, Args } = Expr, E) when is_atom(Atom), is_list(Args) ->
|
||||
capture_import(Meta, Expr, E, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { Left, Right }, S) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, S);
|
||||
capture(Meta, { Left, Right }, E) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, E);
|
||||
|
||||
capture(Meta, List, S) when is_list(List) ->
|
||||
do_capture(Meta, List, S, is_sequential_and_not_empty(List));
|
||||
capture(Meta, List, E) when is_list(List) ->
|
||||
do_capture(Meta, List, E, is_sequential_and_not_empty(List));
|
||||
|
||||
capture(Meta, Arg, S) when is_integer(Arg) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
|
||||
capture(Meta, Arg, E) ->
|
||||
invalid_capture(Meta, Arg, E).
|
||||
|
||||
capture(Meta, Arg, S) ->
|
||||
invalid_capture(Meta, Arg, S).
|
||||
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, E, Sequential) ->
|
||||
Res = Sequential andalso
|
||||
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), E),
|
||||
handle_capture(Res, Meta, Expr, E, Sequential).
|
||||
|
||||
%% Helpers
|
||||
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, E, Sequential) ->
|
||||
{ Mod, EE } = elixir_exp:expand(Left, E),
|
||||
Res = Sequential andalso is_atom(Mod) andalso
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), EE),
|
||||
handle_capture(Res, Meta, Expr, EE, Sequential).
|
||||
|
||||
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, S, Sequential) ->
|
||||
case Sequential andalso
|
||||
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
|
||||
false -> do_capture(Meta, Expr, S, Sequential);
|
||||
Else -> Else
|
||||
handle_capture({ local, Fun, Arity }, _Meta, _Expr, _E, _Sequential) ->
|
||||
{ local, Fun, Arity };
|
||||
handle_capture({ remote, Receiver, Fun, Arity }, Meta, _Expr, E, _Sequential) ->
|
||||
Tree = { { '.', [], [erlang, make_fun] }, Meta, [Receiver, Fun, Arity] },
|
||||
{ expanded, Tree, E };
|
||||
handle_capture(false, Meta, Expr, E, Sequential) ->
|
||||
do_capture(Meta, Expr, E, Sequential).
|
||||
|
||||
do_capture(Meta, Expr, E, Sequential) ->
|
||||
case do_escape(Expr, elixir_counter:next(), E, []) of
|
||||
{ _, [] } when not Sequential ->
|
||||
invalid_capture(Meta, Expr, E);
|
||||
{ EExpr, EDict } ->
|
||||
EVars = validate(Meta, EDict, 1, E),
|
||||
Fn = { fn, Meta, [{ '->', Meta, [EVars, EExpr]}]},
|
||||
{ expanded, Fn, E }
|
||||
end.
|
||||
|
||||
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, S, Sequential) ->
|
||||
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_env:scope_to_ex({ ?line(Meta), S }), S),
|
||||
|
||||
case Sequential andalso is_atom(Mod) andalso
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), SE) of
|
||||
false -> do_capture(Meta, Expr, S, Sequential);
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
do_capture(Meta, Expr, S, Sequential) ->
|
||||
case do_escape(Expr, S, []) of
|
||||
{ _, _, [] } when not Sequential ->
|
||||
invalid_capture(Meta, Expr, S);
|
||||
{ TExpr, TS, TDict } ->
|
||||
TVars = validate(Meta, TDict, 1, S),
|
||||
fn(Meta, [{ TVars, Meta, TExpr }], TS)
|
||||
end.
|
||||
|
||||
invalid_capture(Meta, Arg, S) ->
|
||||
invalid_capture(Meta, Arg, E) ->
|
||||
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
|
||||
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
|
||||
validate(Meta, [{ Pos, Var }|T], Pos, S) ->
|
||||
[Var|validate(Meta, T, Pos + 1, S)];
|
||||
validate(Meta, [{ Pos, Var }|T], Pos, E) ->
|
||||
[Var|validate(Meta, T, Pos + 1, E)];
|
||||
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
|
||||
validate(_Meta, [], _Pos, _S) ->
|
||||
validate(_Meta, [], _Pos, _E) ->
|
||||
[].
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, S, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
case orddict:find(Pos, Dict) of
|
||||
{ ok, Var } ->
|
||||
{ Var, S, Dict };
|
||||
error ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Var, SC, orddict:store(Pos, Var, Dict) }
|
||||
end;
|
||||
do_escape({ '&', _, [Pos] }, Counter, _E, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
Var = { list_to_atom([$x, $@+Pos]), [{ counter, Counter }], elixir_fn },
|
||||
{ Var, orddict:store(Pos, Var, Dict) };
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, S, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "capture &~B is not allowed", [Pos]);
|
||||
do_escape({ '&', Meta, [Pos] }, _Counter, E, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, E#elixir_env.file, "capture &~B is not allowed", [Pos]);
|
||||
|
||||
do_escape({ '&', Meta, _ } = Arg, S, _Dict) ->
|
||||
do_escape({ '&', Meta, _ } = Arg, _Counter, E, _Dict) ->
|
||||
Message = "nested captures via & are not allowed: ~ts",
|
||||
compile_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
do_escape({ Left, Meta, Right }, S0, Dict0) ->
|
||||
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
|
||||
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
|
||||
{ { TLeft, Meta, TRight }, S2, Dict2 };
|
||||
do_escape({ Left, Meta, Right }, Counter, E, Dict0) ->
|
||||
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
|
||||
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
|
||||
{ { TLeft, Meta, TRight }, Dict2 };
|
||||
|
||||
do_escape({ Left, Right }, S0, Dict0) ->
|
||||
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
|
||||
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
|
||||
{ { TLeft, TRight }, S2, Dict2 };
|
||||
do_escape({ Left, Right }, Counter, E, Dict0) ->
|
||||
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
|
||||
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
|
||||
{ { TLeft, TRight }, Dict2 };
|
||||
|
||||
do_escape(List, S, Dict) when is_list(List) ->
|
||||
do_escape_list(List, S, Dict, []);
|
||||
do_escape(List, Counter, E, Dict) when is_list(List) ->
|
||||
lists:mapfoldl(fun(X, Acc) -> do_escape(X, Counter, E, Acc) end, Dict, List);
|
||||
|
||||
do_escape(Other, S, Dict) ->
|
||||
{ Other, S, Dict }.
|
||||
|
||||
do_escape_list([H|T], S, Dict, Acc) ->
|
||||
{ TH, TS, TDict } = do_escape(H, S, Dict),
|
||||
do_escape_list(T, TS, TDict, [TH|Acc]);
|
||||
|
||||
do_escape_list([], S, Dict, Acc) ->
|
||||
{ lists:reverse(Acc), S, Dict }.
|
||||
do_escape(Other, _Counter, _E, Dict) ->
|
||||
{ Other, Dict }.
|
||||
|
||||
is_sequential_and_not_empty([]) -> false;
|
||||
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
|
||||
|
||||
@@ -7,54 +7,55 @@
|
||||
|
||||
%% IMPORT
|
||||
|
||||
import(Meta, Ref, Opts, S) ->
|
||||
SI =
|
||||
import(Meta, Ref, Opts, E) ->
|
||||
Res =
|
||||
case keyfind(only, Opts) of
|
||||
{ only, functions } ->
|
||||
import_functions(Meta, Ref, Opts, S);
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
E#elixir_env.macros };
|
||||
{ only, macros } ->
|
||||
import_macros(true, Meta, Ref, Opts, S);
|
||||
{ E#elixir_env.functions,
|
||||
import_macros(true, Meta, Ref, Opts, E) };
|
||||
{ only, List } when is_list(List) ->
|
||||
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S));
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E) };
|
||||
false ->
|
||||
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S))
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E) }
|
||||
end,
|
||||
record_warn(Meta, Ref, Opts, SI),
|
||||
SI.
|
||||
|
||||
import_functions(Meta, Ref, Opts, S) ->
|
||||
Functions = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
|
||||
fun() -> get_functions(Ref) end, S),
|
||||
S#elixir_scope{functions=Functions}.
|
||||
record_warn(Meta, Ref, Opts, E),
|
||||
Res.
|
||||
|
||||
import_macros(Force, Meta, Ref, Opts, S) ->
|
||||
Existing = fun() ->
|
||||
import_functions(Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, E#elixir_env.functions, E, fun() -> get_functions(Ref) end).
|
||||
|
||||
import_macros(Force, Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, E#elixir_env.macros, E, fun() ->
|
||||
case Force of
|
||||
true -> get_macros(Meta, Ref, S);
|
||||
true -> get_macros(Meta, Ref, E);
|
||||
false -> get_optional_macros(Ref)
|
||||
end
|
||||
end,
|
||||
Macros = calculate(Meta, Ref, Opts, S#elixir_scope.macros, Existing, S),
|
||||
S#elixir_scope{macros=Macros}.
|
||||
end).
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
record_warn(Meta, Ref, Opts, E) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, E#elixir_env.lexical_tracker).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Meta, Key, Opts, Old, Existing, S) ->
|
||||
calculate(Meta, Key, Opts, Old, E, Existing) ->
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, Only } when is_list(Only) ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple);
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple);
|
||||
_ ->
|
||||
intersection(Only, Existing())
|
||||
end;
|
||||
@@ -75,10 +76,9 @@ calculate(Meta, Key, Opts, Old, Existing, S) ->
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] ->
|
||||
keydelete(Key, Old);
|
||||
[] -> keydelete(Key, Old);
|
||||
_ ->
|
||||
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
|
||||
ensure_no_special_form_conflict(Meta, E#elixir_env.file, Key, Final),
|
||||
[{ Key, Final }|keydelete(Key, Old)]
|
||||
end.
|
||||
|
||||
@@ -98,13 +98,13 @@ get_functions(Module) ->
|
||||
error:undef -> Module:module_info(exports)
|
||||
end.
|
||||
|
||||
get_macros(Meta, Module, S) ->
|
||||
get_macros(Meta, Module, E) ->
|
||||
try
|
||||
Module:'__info__'(macros)
|
||||
catch
|
||||
error:undef ->
|
||||
Tuple = { no_macros, Module },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple)
|
||||
end.
|
||||
|
||||
get_optional_macros(Module) ->
|
||||
@@ -135,14 +135,14 @@ ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
|
||||
|
||||
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({special_form_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]).
|
||||
|
||||
%% LIST HELPERS
|
||||
|
||||
@@ -194,7 +194,6 @@ special_form('import',2) -> true;
|
||||
special_form('__ENV__',0) -> true;
|
||||
special_form('__CALLER__',0) -> true;
|
||||
special_form('__MODULE__',0) -> true;
|
||||
special_form('__FILE__',0) -> true;
|
||||
special_form('__DIR__',0) -> true;
|
||||
special_form('__aliases__',_) -> true;
|
||||
special_form('quote',1) -> true;
|
||||
|
||||
@@ -138,7 +138,13 @@ unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
|
||||
|
||||
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
|
||||
Codepoint = list_to_integer(List, Base),
|
||||
unescape_chars(Rest, Map, Octal, Hex, <<Acc/binary, Codepoint/utf8>>).
|
||||
try <<Acc/binary, Codepoint/utf8>> of
|
||||
Binary -> unescape_chars(Rest, Map, Octal, Hex, Binary)
|
||||
catch
|
||||
error:badarg ->
|
||||
Msg = <<"invalid or reserved unicode codepoint ", (integer_to_binary(Codepoint))/binary>>,
|
||||
error('Elixir.ArgumentError':exception([{message,Msg}]))
|
||||
end.
|
||||
|
||||
% Unescape Helpers
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ run(File, Callback) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{ file, _ } ->
|
||||
Pid = ?tracker:start_link(),
|
||||
|
||||
try
|
||||
Callback(Pid)
|
||||
after
|
||||
@@ -75,6 +74,6 @@ warn_unused_aliases(File, Pid) ->
|
||||
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
|
||||
|
||||
format_error({unused_alias, Module}) ->
|
||||
io_lib:format("unused alias ~ts", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
|
||||
format_error({unused_import, Module}) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]).
|
||||
io_lib:format("unused import ~ts", [elixir_aliases:inspect(Module)]).
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
|
||||
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
translate({ '{}', [], [Left, Right]}, S);
|
||||
|
||||
translate([], S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate([_|_] = Args, S) ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{ '|', _, [Left,Right] } ->
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, 0 }, ST } end
|
||||
end,
|
||||
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_utils:list_to_cons(0, Exprs, Tail), ST };
|
||||
|
||||
translate(Tuple, S) when is_tuple(Tuple) ->
|
||||
elixir_errors:compile_error(0, S#elixir_scope.file,
|
||||
"tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple)]);
|
||||
|
||||
translate(Other, S) ->
|
||||
{ elixir_utils:elixir_to_erl(0, Other, S), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
% Build a bitstring taking into accounts the following types:
|
||||
%
|
||||
% * If a bitstring or a list is given, we just append its items
|
||||
% * If '::' is given, extract the bitstring information
|
||||
% * All the other types are simply translated and handled with Erlang's default
|
||||
%
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{ Acc, S };
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case S of
|
||||
{ ES, _ } -> []; %% translate_arg, no assigns
|
||||
_ -> ES = S#elixir_scope{context=nil} %% translate_each, revert assigns
|
||||
end,
|
||||
|
||||
{ Size, Types } = extract_bit_values(Meta, V, ES),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
AllowString = types_allow_string(Types),
|
||||
AllowSplice = types_allow_splice(Types),
|
||||
AllowAny = (AllowString orelse AllowSplice) andalso (Size == default),
|
||||
|
||||
case AllowAny andalso Expr of
|
||||
{ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] } when AllowString ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), { string, 0, String }, Size, Types }|Acc]);
|
||||
{ bin, _, Elements } when AllowSplice ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
|
||||
{ cons, _, _, _ } = Cons ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
|
||||
{ nil, _ } ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, Acc);
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
end.
|
||||
|
||||
rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
|
||||
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
|
||||
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
|
||||
|
||||
types_allow_string([End|T]) when End == little; End == big -> types_allow_string(T);
|
||||
types_allow_string([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_allow_string(T);
|
||||
types_allow_string([]) -> true;
|
||||
types_allow_string(_) -> false.
|
||||
|
||||
types_allow_splice(default) -> true;
|
||||
types_allow_splice([bytes]) -> true;
|
||||
types_allow_splice([binary]) -> true;
|
||||
types_allow_splice([bits]) -> true;
|
||||
types_allow_splice([bitstring]) -> true;
|
||||
types_allow_splice(_) -> false.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_values(Meta, V, S) when is_list(V) ->
|
||||
extract_bit_values(Meta, V, default, [], S);
|
||||
|
||||
extract_bit_values(Meta, V, S) ->
|
||||
extract_bit_values(Meta, [V], S).
|
||||
|
||||
extract_bit_values(Meta, [{ Value, _Meta, Atom }|T] = All, Size, Types, S)
|
||||
when is_atom(Value) andalso (is_atom(Atom) orelse Atom == []) ->
|
||||
case extract_bit_type(Value, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
NewTypes when is_list(NewTypes) ->
|
||||
extract_bit_values(Meta, T, Size, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Meta, [{ Value, CallMeta, [_] = Args }|T] = All, Size, Types, S) when is_atom(Value) ->
|
||||
{ TArgs, _ } = elixir_translator:translate_args(Args, S),
|
||||
case extract_bit_type_or_size(Value, TArgs, Size, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
{ error, Msg } ->
|
||||
elixir_errors:syntax_error(CallMeta, S#elixir_scope.file, Msg);
|
||||
{ NewSize, NewTypes } ->
|
||||
extract_bit_values(Meta, T, NewSize, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) andalso Size >= 0 ->
|
||||
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
|
||||
|
||||
extract_bit_values(Meta, [{ '|', _, [Left, Right] }], Size, Types, S) ->
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_env:scope_to_ex({ ?line(Meta), S }), S),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, ES);
|
||||
|
||||
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
|
||||
extract_bit_values(_Meta, [], Size, [], _S) ->
|
||||
{ Size, default };
|
||||
|
||||
extract_bit_values(_Meta, [], Size, Types, _S) ->
|
||||
{ Size, lists:reverse(Types) }.
|
||||
|
||||
extract_bit_type(Value, Types) when
|
||||
Value == binary; Value == integer; Value == float; Value == bitstring;
|
||||
Value == bytes; Value == bits;
|
||||
Value == utf8; Value == utf16; Value == utf32;
|
||||
Value == signed; Value == unsigned;
|
||||
Value == big; Value == little; Value == native ->
|
||||
[Value|Types];
|
||||
|
||||
extract_bit_type(_Value, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
extract_bit_type_or_size(size, [{ Kind, _, _ } = Arg], default, Types) when Kind == var; Kind == integer ->
|
||||
{ Arg, Types };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, default, _Types) ->
|
||||
{ error, "size in bitstring expects an integer or a variable as argument" };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, _Other, _Types) ->
|
||||
{ error, "duplicated size definition for bitstring" };
|
||||
|
||||
extract_bit_type_or_size(unit, [{ integer, _, Arg }], Size, Types) ->
|
||||
{ Size, [{ unit, Arg }|Types] };
|
||||
|
||||
extract_bit_type_or_size(unit, _Args, _Other, _Types) ->
|
||||
{ error, "unit in bitstring expects an integer as argument" };
|
||||
|
||||
extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand_all(H, elixir_env:scope_to_ex({ ?line(Meta), S }), S) of
|
||||
{ H, ES } ->
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(H)]);
|
||||
{ E, ES } ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, ES)
|
||||
end.
|
||||
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
|
||||
join_expansion(Expanded, Tail) -> [Expanded|Tail].
|
||||
@@ -1,19 +1,76 @@
|
||||
%% Module responsible for tracking invocations of module calls.
|
||||
-module(elixir_tracker).
|
||||
-module(elixir_locals).
|
||||
-export([
|
||||
setup/1, cleanup/1,
|
||||
record_local/2, record_local/3,
|
||||
record_import/4, record_remote/4,
|
||||
setup/1, cleanup/1, cache_env/1, get_cached_env/1,
|
||||
record_local/2, record_local/3, record_import/4,
|
||||
record_definition/3, record_defaults/4,
|
||||
ensure_no_function_conflict/4, warn_unused_local/3,
|
||||
format_error/1
|
||||
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1
|
||||
]).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(attr, '__locals_tracker').
|
||||
-define(tracker, 'Elixir.Module.LocalsTracker').
|
||||
|
||||
-define(attr, '__dispatch_tracker').
|
||||
-define(tracker, 'Elixir.Module.DispatchTracker').
|
||||
macro_for(nil, _Name, _Arity) -> false;
|
||||
macro_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, [_|_] = Clauses }
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
fun() -> get_function(Line, Module, Clauses) end;
|
||||
_ ->
|
||||
false
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% RECORD
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
{ atom, Line, function_for }
|
||||
},
|
||||
|
||||
%% If we have a macro, its arity in the table is
|
||||
%% actually one less than in the function call
|
||||
{ Name, Arity } = case atom_to_list(RawName) of
|
||||
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
|
||||
_ -> { RawName, length(Args) }
|
||||
end,
|
||||
|
||||
FunCall = { call, Line, Remote, [
|
||||
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
|
||||
] },
|
||||
{ call, Line, FunCall, Args };
|
||||
|
||||
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
|
||||
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
|
||||
%% TRACKING
|
||||
|
||||
setup(Module) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
@@ -45,14 +102,6 @@ record_import(Tuple, Receiver, Module, Function) ->
|
||||
true
|
||||
end).
|
||||
|
||||
record_remote(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_remote(Tuple, Receiver, Module, Function) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_remote(Pid, Function, Receiver, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_definition(Tuple, Kind, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_definition(Pid, Kind, Tuple),
|
||||
@@ -67,8 +116,6 @@ record_defaults(Tuple, Kind, Module, Defaults) ->
|
||||
true
|
||||
end).
|
||||
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(Module, Callback) ->
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
Pid -> Callback(Pid)
|
||||
@@ -76,6 +123,24 @@ if_tracker(Module, Callback) ->
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% CACHING
|
||||
|
||||
cache_env(#elixir_env{module=Module} = RE) ->
|
||||
E = RE#elixir_env{line=nil,vars=[]},
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
Pid ->
|
||||
{ Pid, ?tracker:cache_env(Pid, E) }
|
||||
catch
|
||||
error:badarg ->
|
||||
{ Escaped, _ } = elixir_quote:escape(E, false),
|
||||
Escaped
|
||||
end;
|
||||
cache_env(ExEnv) ->
|
||||
cache_env(elixir_env:ex_to_env(ExEnv)).
|
||||
|
||||
get_cached_env({Pid,Ref}) -> ?tracker:get_cached_env(Pid, Ref);
|
||||
get_cached_env(Env) -> Env.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
|
||||
@@ -101,7 +166,7 @@ warn_unused_local(File, Module, Private) ->
|
||||
|
||||
format_error({function_conflict,{Receivers, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
|
||||
[elixir_aliases:inspect(hd(Receivers)), Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_module).
|
||||
-export([compile/4, compile/5, data_table/1, docs_table/1,
|
||||
-export([compile/4, data_table/1, docs_table/1,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -19,31 +19,38 @@ docs_table(Module) ->
|
||||
|
||||
%% Compilation hook
|
||||
|
||||
compile(Module, Block, Vars, ExEnv) ->
|
||||
#elixir_env{line=Line} = Env = elixir_env:ex_to_env(ExEnv),
|
||||
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
|
||||
|
||||
compile(Module, Block, Vars, #elixir_env{line=Line} = Env) when is_atom(Module) ->
|
||||
%% In case we are generating a module from inside a function,
|
||||
%% we get rid of the lexical tracker information as, at this
|
||||
%% point, the lexical tracker process is long gone.
|
||||
LexEnv = case Env#elixir_env.function of
|
||||
nil -> Env;
|
||||
_ -> Env#elixir_env{lexical_tracker=nil, function=nil}
|
||||
nil -> Env#elixir_env{module=Module, local=nil};
|
||||
_ -> Env#elixir_env{lexical_tracker=nil, function=nil, module=Module, local=nil}
|
||||
end,
|
||||
|
||||
compile(Line, Module, Block, Vars, elixir_env:env_to_scope_with_vars(LexEnv, Dict)).
|
||||
case LexEnv#elixir_env.lexical_tracker of
|
||||
nil ->
|
||||
elixir_lexical:run(LexEnv#elixir_env.file, fun(Pid) ->
|
||||
do_compile(Line, Module, Block, Vars, LexEnv#elixir_env{lexical_tracker=Pid})
|
||||
end);
|
||||
_ ->
|
||||
do_compile(Line, Module, Block, Vars, LexEnv)
|
||||
end;
|
||||
|
||||
compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
|
||||
S = RawS#elixir_scope{module=Module},
|
||||
File = S#elixir_scope.file,
|
||||
FileList = elixir_utils:characters_to_list(File),
|
||||
compile(Module, _Block, _Vars, #elixir_env{line=Line,file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Module });
|
||||
|
||||
compile(Module, Block, Vars, ExEnv) ->
|
||||
compile(Module, Block, Vars, elixir_env:ex_to_env(ExEnv)).
|
||||
|
||||
do_compile(Line, Module, Block, Vars, E) ->
|
||||
File = E#elixir_env.file,
|
||||
check_module_availability(Line, File, Module),
|
||||
build(Line, File, Module, S#elixir_scope.lexical_tracker),
|
||||
build(Line, File, Module, E#elixir_env.lexical_tracker),
|
||||
|
||||
try
|
||||
Result = eval_form(Line, Module, Block, Vars, S),
|
||||
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_definitions(FileList, Module),
|
||||
{ Result, NE } = eval_form(Line, Module, Block, Vars, E),
|
||||
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_definitions(Module),
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
|
||||
@@ -53,31 +60,30 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
[{on_load,OnLoad}] ->
|
||||
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
end,
|
||||
|
||||
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
|
||||
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
elixir_locals:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_locals:warn_unused_local(File, Module, Private),
|
||||
warn_invalid_clauses(Line, File, Module, All),
|
||||
warn_unused_docs(Line, File, Module),
|
||||
|
||||
Location = { elixir_utils:relative_to_cwd(elixir_utils:characters_to_list(File)), Line },
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
{ attribute, Line, file, Location },
|
||||
{ attribute, Line, module, Module } | Forms3
|
||||
],
|
||||
|
||||
Binary = load_form(Line, Final, compile_opts(Module), S),
|
||||
Binary = load_form(Line, Final, compile_opts(Module), NE),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
elixir_tracker:cleanup(Module),
|
||||
elixir_locals:cleanup(Module),
|
||||
elixir_def:cleanup(Module),
|
||||
ets:delete(docs_table(Module)),
|
||||
ets:delete(data_table(Module))
|
||||
end;
|
||||
|
||||
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other }).
|
||||
end.
|
||||
|
||||
%% Hook that builds both attribute and functions and set up common hooks.
|
||||
|
||||
@@ -108,18 +114,16 @@ build(Line, File, Module, Lexical) ->
|
||||
|
||||
%% Setup other modules
|
||||
elixir_def:setup(Module),
|
||||
elixir_tracker:setup(Module).
|
||||
elixir_locals:setup(Module).
|
||||
|
||||
%% Receives the module representation and evaluates it.
|
||||
|
||||
eval_form(Line, Module, Block, Vars, S) ->
|
||||
KV = [{ K, V } || { _, _, K, V } <- Vars],
|
||||
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, KV, S),
|
||||
eval_form(Line, Module, Block, Vars, E) ->
|
||||
{ Value, EE } = elixir_compiler:eval_forms(Block, Vars, E),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Env = elixir_env:scope_to_ex({ Line, S }),
|
||||
eval_callbacks(Line, Module, before_compile, [Env], NewS),
|
||||
EC = eval_callbacks(Line, Module, before_compile, [elixir_env:env_to_ex({ Line, EE })], EE),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Value.
|
||||
{ Value, EC }.
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
|
||||
@@ -240,10 +244,10 @@ compile_opts(Module) ->
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
|
||||
load_form(Line, Forms, Opts, #elixir_env{file=File} = E) ->
|
||||
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
|
||||
Env = elixir_env:scope_to_ex({ Line, S }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], S),
|
||||
Env = elixir_env:env_to_ex({ Line, E }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], E),
|
||||
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
@@ -351,41 +355,41 @@ else_clause() ->
|
||||
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
{ Binding, S } = elixir_scope:load_binding([], RawS),
|
||||
eval_callbacks(Line, Module, Name, Args, E) ->
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
lists:foreach(fun({M,F}) ->
|
||||
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
|
||||
lists:foldl(fun({M,F}, Acc) ->
|
||||
{ Expr, ET } = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(_) ->
|
||||
apply(M, F, Args),
|
||||
{ { atom, 0, nil }, S }
|
||||
{ nil, Acc }
|
||||
end),
|
||||
|
||||
case Tree of
|
||||
{ atom, _, Atom } ->
|
||||
Atom;
|
||||
_ ->
|
||||
if
|
||||
is_atom(Expr) ->
|
||||
ET;
|
||||
true ->
|
||||
try
|
||||
erl_eval:exprs([Tree], Binding)
|
||||
{ _Value, _Binding, EE, _S } = elixir:eval_forms(Expr, [], ET),
|
||||
EE
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
|
||||
Info = { M, F, length(Args), location(Line, E) },
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
|
||||
end
|
||||
end
|
||||
end, Callbacks).
|
||||
end, E, Callbacks).
|
||||
|
||||
%% We've reached the elixir_module or erl_eval internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ erl_eval, _, _, _ }|_]) ->
|
||||
location(Line, E) ->
|
||||
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
|
||||
|
||||
%% We've reached the elixir_module or eval internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ elixir, eval_forms, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [H|T]) ->
|
||||
[H|prune_stacktrace(Info, T)];
|
||||
|
||||
prune_stacktrace(Info, []) ->
|
||||
[Info].
|
||||
|
||||
@@ -393,22 +397,16 @@ prune_stacktrace(Info, []) ->
|
||||
|
||||
format_error({ invalid_clause, { Name, Arity } }) ->
|
||||
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
|
||||
format_error({ unused_doc, typedoc }) ->
|
||||
"@typedoc provided but no type follows it";
|
||||
|
||||
format_error({ unused_doc, doc }) ->
|
||||
"@doc provided but no definition follows it";
|
||||
|
||||
format_error({ internal_function_overridden, { Name, Arity } }) ->
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
|
||||
format_error({ invalid_module, Module}) ->
|
||||
io_lib:format("invalid module name: ~p", [Module]);
|
||||
|
||||
format_error({ module_defined, Module }) ->
|
||||
io_lib:format("redefining module ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
io_lib:format("redefining module ~ts", [elixir_aliases:inspect(Module)]);
|
||||
format_error({ module_in_definition, Module }) ->
|
||||
io_lib:format("cannot define module ~ts because it is currently being defined",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
@@ -3,10 +3,9 @@ Nonterminals
|
||||
expr container_expr block_expr no_parens_expr identifier_expr max_expr
|
||||
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
|
||||
op_expr matched_op_expr no_parens_op_expr
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
|
||||
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
|
||||
when_op_eol in_op_eol inc_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
@@ -30,9 +29,8 @@ Terminals
|
||||
fn 'end' aliases
|
||||
number signed_number atom atom_string bin_string list_string sigil
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op
|
||||
range_op in_op inc_op when_op than_op default_op tail_op
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op when_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.' '&'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>'
|
||||
.
|
||||
@@ -46,22 +44,18 @@ Expect 2.
|
||||
Left 5 do.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Right 30 when_op_eol. %% when
|
||||
Right 40 type_op_eol. %% ::
|
||||
Left 50 inc_op_eol. %% inlist, inbits
|
||||
Right 60 default_op_eol. %% //
|
||||
Left 70 tail_op_eol. %% |
|
||||
Right 50 in_match_op_eol. %% inlist, inbits, //, | (allowed in matches, lower precedence than =)
|
||||
Right 70 when_op_eol. %% when
|
||||
Right 80 match_op_eol. %% =
|
||||
Left 130 or_op_eol. %% ||, |||, or, xor
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
|
||||
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
|
||||
Left 170 in_op_eol. %% in
|
||||
Left 200 range_op_eol. %% ..
|
||||
Right 200 two_op_eol. %% ++, --, **, .., <>
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Right 230 than_op_eol. %% < (op) > (e.g <>)
|
||||
Right 240 two_op_eol. %% ++, --, **
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
|
||||
Left 310 dot_call_op.
|
||||
@@ -71,12 +65,12 @@ Nonassoc 330 var.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
|
||||
grammar -> eol : [nil].
|
||||
grammar -> expr_list : lists:reverse('$1').
|
||||
grammar -> eol expr_list : lists:reverse('$2').
|
||||
grammar -> expr_list eol : lists:reverse('$1').
|
||||
grammar -> eol expr_list eol : lists:reverse('$2').
|
||||
grammar -> '$empty' : [nil].
|
||||
grammar -> eol : nil.
|
||||
grammar -> expr_list : to_block('$1').
|
||||
grammar -> eol expr_list : to_block('$2').
|
||||
grammar -> expr_list eol : to_block('$1').
|
||||
grammar -> eol expr_list eol : to_block('$2').
|
||||
grammar -> '$empty' : nil.
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
@@ -140,13 +134,9 @@ op_expr -> exp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> two_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> and_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> or_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> tail_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> than_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> inc_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> when_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> range_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> default_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> type_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> comp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
|
||||
@@ -158,12 +148,8 @@ no_parens_op_expr -> exp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> two_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> tail_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> than_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> inc_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> range_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> default_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
@@ -179,13 +165,9 @@ matched_op_expr -> exp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> tail_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> than_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> inc_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> range_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> default_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
|
||||
@@ -315,9 +297,6 @@ exp_op_eol -> exp_op eol : '$1'.
|
||||
two_op_eol -> two_op : '$1'.
|
||||
two_op_eol -> two_op eol : '$1'.
|
||||
|
||||
default_op_eol -> default_op : '$1'.
|
||||
default_op_eol -> default_op eol : '$1'.
|
||||
|
||||
type_op_eol -> type_op : '$1'.
|
||||
type_op_eol -> type_op eol : '$1'.
|
||||
|
||||
@@ -335,17 +314,11 @@ and_op_eol -> and_op eol : '$1'.
|
||||
or_op_eol -> or_op : '$1'.
|
||||
or_op_eol -> or_op eol : '$1'.
|
||||
|
||||
tail_op_eol -> tail_op : '$1'.
|
||||
tail_op_eol -> tail_op eol : '$1'.
|
||||
|
||||
than_op_eol -> than_op : '$1'.
|
||||
than_op_eol -> than_op eol : '$1'.
|
||||
|
||||
in_op_eol -> in_op : '$1'.
|
||||
in_op_eol -> in_op eol : '$1'.
|
||||
|
||||
inc_op_eol -> inc_op : '$1'.
|
||||
inc_op_eol -> inc_op eol : '$1'.
|
||||
in_match_op_eol -> in_match_op : '$1'.
|
||||
in_match_op_eol -> in_match_op eol : '$1'.
|
||||
|
||||
when_op_eol -> when_op : '$1'.
|
||||
when_op_eol -> when_op eol : '$1'.
|
||||
@@ -353,9 +326,6 @@ when_op_eol -> when_op eol : '$1'.
|
||||
stab_op_eol -> stab_op : '$1'.
|
||||
stab_op_eol -> stab_op eol : '$1'.
|
||||
|
||||
range_op_eol -> range_op : '$1'.
|
||||
range_op_eol -> range_op eol : '$1'.
|
||||
|
||||
at_op_eol -> at_op : '$1'.
|
||||
at_op_eol -> at_op eol : '$1'.
|
||||
|
||||
@@ -578,7 +548,7 @@ extract_identifier(Other) -> Other.
|
||||
%% Fn
|
||||
|
||||
build_fn(Op, Stab) ->
|
||||
{ fn, meta(Op), [Stab] }.
|
||||
{ fn, meta(Op), Stab }.
|
||||
|
||||
%% Access
|
||||
|
||||
@@ -617,35 +587,33 @@ string_parts(Parts) ->
|
||||
string_part(Binary) when is_binary(Binary) ->
|
||||
Binary;
|
||||
string_part({ Line, Tokens }) ->
|
||||
Form = string_tokens_parse(Line, Tokens),
|
||||
Form = string_tokens_parse(Tokens),
|
||||
Meta = meta(Line),
|
||||
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
|
||||
|
||||
string_tokens_parse(Line, Tokens) ->
|
||||
string_tokens_parse(Tokens) ->
|
||||
case parse(Tokens) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', meta(Line), Forms };
|
||||
{ ok, Forms } -> Forms;
|
||||
{ error, _ } = Error -> throw(Error)
|
||||
end.
|
||||
|
||||
%% Keywords
|
||||
|
||||
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
|
||||
{ '->', Meta, build_stab(Meta, T, Left, [Right], []) };
|
||||
build_stab(Meta, T, Left, [Right], []);
|
||||
|
||||
build_stab(Else) ->
|
||||
build_block(Else).
|
||||
|
||||
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, Old, build_block(lists:reverse(Temp)) },
|
||||
H = { '->', Old, [Marker, build_block(lists:reverse(Temp))] },
|
||||
build_stab(New, T, Left, [Right], [H|Acc]);
|
||||
|
||||
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
|
||||
build_stab(Meta, T, Marker, [H|Temp], Acc);
|
||||
|
||||
build_stab(Meta, [], Marker, Temp, Acc) ->
|
||||
H = { Marker, Meta, build_block(lists:reverse(Temp)) },
|
||||
H = { '->', Meta, [Marker, build_block(lists:reverse(Temp))] },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
%% Every time the parser sees a (unquote_splicing())
|
||||
@@ -668,6 +636,9 @@ unwrap_when(Args) ->
|
||||
Args
|
||||
end.
|
||||
|
||||
to_block([One]) when not is_list(One) -> One;
|
||||
to_block(Other) -> { '__block__', [], lists:reverse(Other) }.
|
||||
|
||||
%% Errors
|
||||
|
||||
throw(Line, Error, Token) ->
|
||||
|
||||
@@ -20,7 +20,7 @@ linify_with_context_counter(Line, Var, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, line, Var, Exprs).
|
||||
|
||||
do_linify(Line, Key, { Receiver, Counter } = Var, { Left, Meta, Receiver })
|
||||
when is_atom(Left), is_list(Meta) ->
|
||||
when is_atom(Left), is_list(Meta), Left /= '_' ->
|
||||
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver);
|
||||
|
||||
do_linify(Line, Key, { _, Counter } = Var, { Lexical, [_|_] = Meta, [_|_] = Args }) when ?lexical(Lexical) ->
|
||||
@@ -42,17 +42,21 @@ do_tuple_linify(Line, Key, Var, Meta, Left, Right) ->
|
||||
do_linify_meta(Line, Key, Meta),
|
||||
do_linify(Line, Key, Var, Right) }.
|
||||
|
||||
do_linify_meta(0, line, Meta) ->
|
||||
Meta;
|
||||
do_linify_meta(Line, line, Meta) ->
|
||||
case keyfind(line, Meta) of
|
||||
{ line, Int } when is_integer(Int), Int /= 0 ->
|
||||
Meta;
|
||||
_ ->
|
||||
keystore(line, Meta, Line)
|
||||
end;
|
||||
do_linify_meta(Line, Key, Meta) ->
|
||||
case keyfind(Key, Meta) of
|
||||
{ Key, Int } when is_integer(Int), Int /= 0 ->
|
||||
keyreplace(Key, Meta, { line, Int });
|
||||
_ when Key == line ->
|
||||
keystore(line, Meta, Line);
|
||||
_ ->
|
||||
case ?line(Meta) of
|
||||
0 -> keystore(line, Meta, Line);
|
||||
_ -> Meta
|
||||
end
|
||||
do_linify_meta(Line, line, Meta)
|
||||
end.
|
||||
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
@@ -145,10 +149,10 @@ escape(Expr, Unquote) ->
|
||||
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
quote(Expr, nil, Q, S) ->
|
||||
do_quote(Expr, Q, S);
|
||||
quote(Expr, nil, Q, E) ->
|
||||
do_quote(Expr, Q, E);
|
||||
|
||||
quote(Expr, Binding, Q, S) ->
|
||||
quote(Expr, Binding, Q, E) ->
|
||||
Context = Q#elixir_quote.context,
|
||||
|
||||
Vars = [ { '{}', [],
|
||||
@@ -158,13 +162,13 @@ quote(Expr, Binding, Q, S) ->
|
||||
] ]
|
||||
} || { K, V } <- Binding],
|
||||
|
||||
{ TExprs, TQ } = do_quote(Expr, Q, S),
|
||||
{ TExprs, TQ } = do_quote(Expr, Q, E),
|
||||
{ { '{}',[], ['__block__',[], Vars ++ [TExprs] ] }, TQ }.
|
||||
|
||||
%% Actual quoting and helpers
|
||||
|
||||
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
|
||||
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S),
|
||||
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, E) when length(Args) == 1; length(Args) == 2 ->
|
||||
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, E),
|
||||
{ TTuple, TQ#elixir_quote{unquote=true} };
|
||||
|
||||
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
@@ -172,42 +176,42 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
|
||||
%% Aliases
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases,
|
||||
S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, E) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
AliasMeta = keystore(alias, keydelete(counter, Meta), Annotation),
|
||||
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
|
||||
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, E);
|
||||
|
||||
%% Vars
|
||||
|
||||
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, S) when is_atom(Left) ->
|
||||
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, S);
|
||||
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, E) when is_atom(Left) ->
|
||||
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, E);
|
||||
|
||||
%% Unquote
|
||||
|
||||
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S);
|
||||
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, E) ->
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, E);
|
||||
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote_call(Left, Meta, Expr, nil, Q, S);
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, E) ->
|
||||
do_quote_call(Left, Meta, Expr, nil, Q, E);
|
||||
|
||||
%% Imports
|
||||
|
||||
do_quote({ '&', Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa('&', Meta, Args, F, A, Q, S);
|
||||
#elixir_quote{imports_hygiene=true} = Q, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa('&', Meta, Args, F, A, Q, E);
|
||||
|
||||
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
false -> length(ArgsOrAtom)
|
||||
end,
|
||||
|
||||
NewMeta = case (keyfind(import, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, S) of
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, E) of
|
||||
false ->
|
||||
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
|
||||
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
|
||||
@@ -217,68 +221,68 @@ do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S)
|
||||
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
|
||||
end,
|
||||
|
||||
Annotated = annotate({ Name, NewMeta, ArgsOrAtom }, Q#elixir_quote.context, file(S, Q)),
|
||||
do_quote_tuple(Annotated, Q, S);
|
||||
Annotated = annotate({ Name, NewMeta, ArgsOrAtom }, Q#elixir_quote.context, file(E, Q)),
|
||||
do_quote_tuple(Annotated, Q, E);
|
||||
|
||||
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, S) ->
|
||||
Annotated = annotate(Tuple, Q#elixir_quote.context, file(S, Q)),
|
||||
do_quote_tuple(Annotated, Q, S);
|
||||
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, E) ->
|
||||
Annotated = annotate(Tuple, Q#elixir_quote.context, file(E, Q)),
|
||||
do_quote_tuple(Annotated, Q, E);
|
||||
|
||||
%% Literals
|
||||
|
||||
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, S) when
|
||||
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, E) when
|
||||
is_tuple(Left) andalso (element(1, Left) == unquote_splicing);
|
||||
is_tuple(Right) andalso (element(1, Right) == unquote_splicing) ->
|
||||
do_quote({ '{}', [], [Left, Right] }, Q, S);
|
||||
do_quote({ '{}', [], [Left, Right] }, Q, E);
|
||||
|
||||
do_quote({ Left, Right }, Q, S) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, S),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
do_quote({ Left, Right }, Q, E) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, E),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, E),
|
||||
{ { TLeft, TRight }, RQ };
|
||||
|
||||
do_quote(Tuple, #elixir_quote{escape=true} = Q, S) when is_tuple(Tuple) ->
|
||||
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
|
||||
do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
|
||||
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, E),
|
||||
{ { '{}', [], TT }, TQ };
|
||||
|
||||
do_quote(List, #elixir_quote{escape=true} = Q, S) when is_list(List) ->
|
||||
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
|
||||
% The improper case is pretty inefficient, but improper lists are hopefully
|
||||
% rare.
|
||||
case reverse_improper(List) of
|
||||
{ L } -> do_splice(L, Q, S);
|
||||
{ L } -> do_splice(L, Q, E);
|
||||
{ L, R } ->
|
||||
{ TL, QL } = do_splice(L, Q, S, [], []),
|
||||
{ TR, QR } = do_quote(R, QL, S),
|
||||
{ TL, QL } = do_splice(L, Q, E, [], []),
|
||||
{ TR, QR } = do_quote(R, QL, E),
|
||||
{ update_last(TL, fun(X) -> { '|', [], [X, TR] } end), QR }
|
||||
end;
|
||||
do_quote(List, Q, S) when is_list(List) ->
|
||||
do_splice(lists:reverse(List), Q, S);
|
||||
do_quote(List, Q, E) when is_list(List) ->
|
||||
do_splice(lists:reverse(List), Q, E);
|
||||
|
||||
do_quote(Other, Q, _) ->
|
||||
{ Other, Q }.
|
||||
|
||||
%% Quote helpers
|
||||
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
|
||||
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args,
|
||||
Q#elixir_quote.context, file(S, Q)],
|
||||
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
|
||||
Q#elixir_quote.context, file(E, Q)],
|
||||
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, E) end, Q, All),
|
||||
{ { { '.', Meta, [elixir_quote, dot] }, Meta, TAll }, TQ }.
|
||||
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, E) ->
|
||||
NewMeta =
|
||||
case (keyfind(import_fa, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, F, A, S) of
|
||||
elixir_dispatch:find_import(Meta, F, A, E) of
|
||||
false -> Meta;
|
||||
Receiver -> keystore(import_fa, Meta, { Receiver, Q#elixir_quote.context })
|
||||
end,
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, E).
|
||||
|
||||
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, S),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
do_quote_tuple({ Left, Meta, Right }, Q, E) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, E),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, E),
|
||||
{ { '{}', [], [TLeft, meta(Meta, Q), TRight] }, RQ }.
|
||||
|
||||
file(#elixir_scope{file=File}, #elixir_quote{keep=true}) -> File;
|
||||
file(#elixir_env{file=File}, #elixir_quote{keep=true}) -> File;
|
||||
file(_, _) -> nil.
|
||||
|
||||
meta(Meta, #elixir_quote{keep=true}) ->
|
||||
@@ -319,30 +323,30 @@ keynew(Key, Meta, Value) ->
|
||||
|
||||
%% Quote splicing
|
||||
|
||||
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, E) ->
|
||||
%% Process the remaining entries on the list.
|
||||
%% For [1, 2, 3, unquote_splicing(arg)|tail], this will quote
|
||||
%% 1, 2 and 3, which could even be unquotes.
|
||||
{ TT, QT } = do_splice(T, Q, S, [], []),
|
||||
{ TR, QR } = do_quote(Right, QT, S),
|
||||
{ TT, QT } = do_splice(T, Q, E, [], []),
|
||||
{ TR, QR } = do_quote(Right, QT, E),
|
||||
{ do_runtime_list(Meta, tail_list, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
|
||||
|
||||
do_splice(List, Q, S) ->
|
||||
do_splice(List, Q, S, [], []).
|
||||
do_splice(List, Q, E) ->
|
||||
do_splice(List, Q, E, [], []).
|
||||
|
||||
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_runtime_list(Meta, list, [Expr, do_join(Buffer, Acc)]));
|
||||
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, E, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, E, [], do_runtime_list(Meta, list, [Expr, do_join(Buffer, Acc)]));
|
||||
|
||||
do_splice([H|T], Q, S, Buffer, Acc) ->
|
||||
{ TH, TQ } = do_quote(H, Q, S),
|
||||
do_splice(T, TQ, S, [TH|Buffer], Acc);
|
||||
do_splice([H|T], Q, E, Buffer, Acc) ->
|
||||
{ TH, TQ } = do_quote(H, Q, E),
|
||||
do_splice(T, TQ, E, [TH|Buffer], Acc);
|
||||
|
||||
do_splice([], Q, _S, Buffer, Acc) ->
|
||||
do_splice([], Q, _E, Buffer, Acc) ->
|
||||
{ do_join(Buffer, Acc), Q }.
|
||||
|
||||
do_join(Left, []) -> Left;
|
||||
do_join([], Right) -> Right;
|
||||
do_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
|
||||
do_join(Left, Right) -> { { '.', [], [erlang, '++'] }, [], [Left, Right] }.
|
||||
|
||||
do_runtime_list(Meta, Fun, Args) ->
|
||||
{ { '.', Meta, [elixir_quote, Fun] }, Meta, Args }.
|
||||
|
||||
+74
-112
@@ -1,146 +1,110 @@
|
||||
%% Convenience functions used to manipulate scope and its variables.
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/5,
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
-export([translate_var/4, build_var/2,
|
||||
load_binding/2, dump_binding/2,
|
||||
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
|
||||
mergev/2, mergec/2, mergef/2,
|
||||
merge_vars/2, merge_opt_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% VAR HANDLING
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) when is_atom(Kind); is_integer(Kind) ->
|
||||
Line = ?line(Meta),
|
||||
Vars = S#elixir_scope.vars,
|
||||
Temp = lists:keyfind(temp, 1, Meta) == { temp, true },
|
||||
translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
|
||||
Line = ?line(Meta),
|
||||
Tuple = { Name, Kind },
|
||||
Vars = S#elixir_scope.vars,
|
||||
|
||||
case Name of
|
||||
'_' ->
|
||||
{ { var, Line, Name }, S };
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key(Tuple, Vars), is_list(TempVars) andalso ordsets:is_element(Tuple, TempVars) } of
|
||||
{ true, true } ->
|
||||
{ { var, Line, orddict:fetch(Tuple, Vars) }, S };
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = if
|
||||
Kind /= nil -> build_erl_var(Line, S);
|
||||
Else -> build_erl_var(Line, Name, Name, S);
|
||||
S#elixir_scope.noname -> build_erl_var(Line, Name, Name, S);
|
||||
true -> { { var, Line, Name }, S }
|
||||
end,
|
||||
RealName = element(3, NewVar),
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=orddict:store(Tuple, RealName, Vars),
|
||||
temp_vars=if
|
||||
TempVars == nil -> TempVars;
|
||||
true -> ordsets:add_element(Tuple, TempVars)
|
||||
end,
|
||||
clause_vars=if
|
||||
ClauseVars == nil; Temp == true -> ClauseVars;
|
||||
true -> orddict:store(Tuple, RealName, ClauseVars)
|
||||
end
|
||||
} }
|
||||
end;
|
||||
_ ->
|
||||
case orddict:find({ Name, Kind }, Vars) of
|
||||
{ ok, VarName } -> { { var, Line, VarName }, S };
|
||||
error -> Callback()
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
% Handle variables translation
|
||||
|
||||
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
|
||||
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
|
||||
|
||||
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
|
||||
New = orddict:update_counter(Key, 1, Counter),
|
||||
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
|
||||
|
||||
build_erl_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
{ Counter, NS } = build_var_counter(Key, S),
|
||||
Var = { var, Line, ?atom_concat([Name, "@", Counter]) },
|
||||
{ Var, NS }.
|
||||
|
||||
build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
Context = case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
case orddict:find({ Name, Kind }, Vars) of
|
||||
{ ok, { Current, _ } } -> Exists = true;
|
||||
error -> Current = nil, Exists = false
|
||||
end,
|
||||
|
||||
{ Counter, NS } = build_var_counter(Key, S),
|
||||
Var = { ?atom_concat([Name, "@", Counter]), [{line,Line}], Context },
|
||||
{ Var, NS }.
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
MatchVars = S#elixir_scope.match_vars,
|
||||
|
||||
case Exists andalso ordsets:is_element(Tuple, MatchVars) of
|
||||
true ->
|
||||
{ { var, Line, Current }, S };
|
||||
false ->
|
||||
%% If the variable is not exported, we use a counter name.
|
||||
%% The same if the variable already exists or we are in a
|
||||
%% noname context.
|
||||
Private = (lists:keyfind(export, 1, Meta) == { export, false }),
|
||||
|
||||
{ NewVar, Counter, NS } =
|
||||
if
|
||||
Kind /= nil ->
|
||||
build_var('_', S);
|
||||
Private orelse Exists orelse S#elixir_scope.noname ->
|
||||
build_var(Name, S);
|
||||
true ->
|
||||
{ Name, 0, S }
|
||||
end,
|
||||
|
||||
FS = NS#elixir_scope{
|
||||
vars=orddict:store(Tuple, { NewVar, Counter }, Vars),
|
||||
match_vars=ordsets:add_element(Tuple, MatchVars),
|
||||
export_vars=case S#elixir_scope.export_vars of
|
||||
EV when Private; EV == nil -> EV;
|
||||
EV -> orddict:store(Tuple, { NewVar, Counter }, EV)
|
||||
end
|
||||
},
|
||||
|
||||
{ { var, Line, NewVar }, FS }
|
||||
end;
|
||||
_ when Exists ->
|
||||
{ { var, Line, Current }, S }
|
||||
end.
|
||||
|
||||
build_var(Key, S) ->
|
||||
New = orddict:update_counter(Key, 1, S#elixir_scope.counter),
|
||||
Cnt = orddict:fetch(Key, New),
|
||||
{ ?atom_concat([Key, "@", Cnt]), Cnt, S#elixir_scope{counter=New} }.
|
||||
|
||||
%% SCOPE MERGING
|
||||
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
%% Receives two scopes and return a new scope based on
|
||||
%% the second with their variables merged.
|
||||
|
||||
umergev(S1, S2) ->
|
||||
V1 = S1#elixir_scope.vars,
|
||||
V2 = S2#elixir_scope.vars,
|
||||
C1 = S1#elixir_scope.clause_vars,
|
||||
C2 = S2#elixir_scope.clause_vars,
|
||||
mergev(S1, S2) ->
|
||||
S2#elixir_scope{
|
||||
vars=merge_vars(V1, V2),
|
||||
clause_vars=merge_clause_vars(C1, C2)
|
||||
vars=merge_vars(S1#elixir_scope.vars, S2#elixir_scope.vars),
|
||||
export_vars=merge_opt_vars(S1#elixir_scope.export_vars, S2#elixir_scope.export_vars)
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the first scope with
|
||||
%% counters and flags from the later.
|
||||
|
||||
umergec(S1, S2) ->
|
||||
mergec(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
macro_counter=S2#elixir_scope.macro_counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S2#elixir_scope.super,
|
||||
caller=S2#elixir_scope.caller
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
%% Similar to mergec but does not merge the user vars counter.
|
||||
|
||||
umergea(S1, S2) ->
|
||||
S2#elixir_scope{
|
||||
vars=S1#elixir_scope.vars,
|
||||
temp_vars=S1#elixir_scope.temp_vars,
|
||||
clause_vars=S1#elixir_scope.clause_vars
|
||||
mergef(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
super=S2#elixir_scope.super,
|
||||
caller=S2#elixir_scope.caller
|
||||
}.
|
||||
|
||||
% Merge variables trying to find the most recently created.
|
||||
%% Mergers.
|
||||
|
||||
merge_vars(V, V) -> V;
|
||||
merge_vars(V1, V2) ->
|
||||
orddict:merge(fun var_merger/3, V1, V2).
|
||||
|
||||
merge_clause_vars(nil, _C2) -> nil;
|
||||
merge_clause_vars(_C1, nil) -> nil;
|
||||
merge_clause_vars(C, C) -> C;
|
||||
merge_clause_vars(C1, C2) ->
|
||||
merge_opt_vars(nil, _C2) -> nil;
|
||||
merge_opt_vars(_C1, nil) -> nil;
|
||||
merge_opt_vars(C, C) -> C;
|
||||
merge_opt_vars(C1, C2) ->
|
||||
orddict:merge(fun var_merger/3, C1, C2).
|
||||
|
||||
var_merger({ Var, _ }, Var, K2) -> K2;
|
||||
var_merger({ Var, _ }, K1, Var) -> K1;
|
||||
var_merger(_Var, K1, K2) ->
|
||||
V1 = var_number(atom_to_list(K1), []),
|
||||
V2 = var_number(atom_to_list(K2), []),
|
||||
case V1 > V2 of
|
||||
true -> K1;
|
||||
false -> K2
|
||||
end.
|
||||
|
||||
var_number([$@|T], _Acc) -> var_number(T, []);
|
||||
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
|
||||
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
|
||||
var_merger(_Var, { _, V1 } = K1, { _, V2 }) when V1 > V2 -> K1;
|
||||
var_merger(_Var, _K1, K2) -> K2.
|
||||
|
||||
%% BINDINGS
|
||||
|
||||
@@ -148,9 +112,7 @@ load_binding(Binding, Scope) ->
|
||||
{ NewBinding, NewVars, NewCounter } = load_binding(Binding, [], [], 0),
|
||||
{ NewBinding, Scope#elixir_scope{
|
||||
vars=NewVars,
|
||||
temp_vars=[],
|
||||
clause_vars=nil,
|
||||
counter=[{'',NewCounter}]
|
||||
counter=[{'_',NewCounter}]
|
||||
} }.
|
||||
|
||||
load_binding([{Key,Value}|T], Binding, Vars, Counter) ->
|
||||
@@ -160,15 +122,15 @@ load_binding([{Key,Value}|T], Binding, Vars, Counter) ->
|
||||
end,
|
||||
InternalName = ?atom_concat(["_@", Counter]),
|
||||
load_binding(T,
|
||||
[{InternalName,Value}|Binding],
|
||||
orddict:store(Actual, InternalName, Vars), Counter + 1);
|
||||
orddict:store(InternalName, Value, Binding),
|
||||
orddict:store(Actual, { InternalName, 0 }, Vars), Counter + 1);
|
||||
load_binding([], Binding, Vars, Counter) ->
|
||||
{ lists:reverse(Binding), Vars, Counter }.
|
||||
{ Binding, Vars, Counter }.
|
||||
|
||||
dump_binding(Binding, #elixir_scope{vars=Vars}) ->
|
||||
dump_binding(Vars, Binding, []).
|
||||
|
||||
dump_binding([{{Var,Kind}=Key,InternalName}|T], Binding, Acc) when is_atom(Kind) ->
|
||||
dump_binding([{{ Var, Kind } = Key, { InternalName,_ }}|T], Binding, Acc) when is_atom(Kind) ->
|
||||
Actual = case Kind of
|
||||
nil -> Var;
|
||||
_ -> Key
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
-module(elixir_sup).
|
||||
-behaviour(supervisor).
|
||||
-export([init/1,start_link/1]).
|
||||
-export([init/1, start_link/0]).
|
||||
|
||||
start_link(Args) ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, Args).
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, ok).
|
||||
|
||||
init(Args) ->
|
||||
Supervisors = [
|
||||
init(ok) ->
|
||||
Workers = [
|
||||
{
|
||||
elixir_code_server_sup,
|
||||
{ elixir_code_server, start_link, Args },
|
||||
elixir_code_server,
|
||||
{ elixir_code_server, start_link, [] },
|
||||
|
||||
permanent, % Restart = permanent | transient | temporary
|
||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
||||
worker, % Type = worker | supervisor
|
||||
[elixir_code_server] % Modules = [Module] | dynamic
|
||||
},
|
||||
|
||||
{
|
||||
elixir_counter,
|
||||
{ elixir_counter, start_link, [] },
|
||||
|
||||
permanent, % Restart = permanent | transient | temporary
|
||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
||||
worker, % Type = worker | supervisor
|
||||
[elixir_counter] % Modules = [Module] | dynamic
|
||||
}
|
||||
],
|
||||
|
||||
{ok, {{one_for_one, 3, 10}, Supervisors}}.
|
||||
{ok, {{one_for_one, 3, 10}, Workers}}.
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
|
||||
|
||||
-define(container(T1, T2),
|
||||
T1 == ${, T2 == $};
|
||||
T1 == $[, T2 == $]
|
||||
T1 == ${, T2 == $}
|
||||
).
|
||||
|
||||
-define(at_op(T),
|
||||
@@ -25,10 +24,9 @@
|
||||
-define(two_op(T1, T2),
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*).
|
||||
|
||||
-define(than_op(T1, T2),
|
||||
T1 == $<, T2 == $>).
|
||||
T1 == $*, T2 == $*;
|
||||
T1 == $<, T2 == $>;
|
||||
T1 == $., T2 == $.).
|
||||
|
||||
-define(mult_op(T),
|
||||
T == $* orelse T == $/).
|
||||
@@ -36,9 +34,6 @@
|
||||
-define(dual_op(T),
|
||||
T == $+ orelse T == $-).
|
||||
|
||||
-define(range_op(T1, T2),
|
||||
T1 == $., T2 == $.).
|
||||
|
||||
-define(arrow_op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>).
|
||||
@@ -77,10 +72,10 @@
|
||||
-define(match_op(T),
|
||||
T == $=).
|
||||
|
||||
-define(tail_op(T),
|
||||
-define(in_match_op(T),
|
||||
T == $|).
|
||||
|
||||
-define(default_op(T1, T2),
|
||||
-define(in_match_op2(T1, T2),
|
||||
T1 == $/, T2 == $/).
|
||||
|
||||
-define(stab_op(T1, T2),
|
||||
@@ -228,13 +223,6 @@ tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
|
||||
end;
|
||||
|
||||
% ## Containers
|
||||
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
|
||||
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
|
||||
@@ -244,14 +232,13 @@ tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
% ## Two Token Operators
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T) ->
|
||||
?match_op(T); ?in_match_op(T) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot call
|
||||
@@ -331,14 +318,13 @@ tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
% ## Two Token Operators
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $. ->
|
||||
?match_op(T); ?in_match_op(T); T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
|
||||
|
||||
% End of line
|
||||
@@ -396,12 +382,6 @@ tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?two_op(T1, T2) ->
|
||||
handle_op(Rest, Line, two_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?than_op(T1, T2) ->
|
||||
handle_op(Rest, Line, than_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?range_op(T1, T2) ->
|
||||
handle_op(Rest, Line, range_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?arrow_op(T1, T2) ->
|
||||
handle_op(Rest, Line, arrow_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
@@ -414,8 +394,8 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?default_op(T1, T2) ->
|
||||
handle_op(Rest, Line, default_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op2(T1, T2) ->
|
||||
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
|
||||
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
@@ -447,8 +427,8 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
|
||||
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?tail_op(T) ->
|
||||
handle_op(Rest, Line, tail_op, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?in_match_op(T) ->
|
||||
handle_op(Rest, Line, in_match_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([$.|Rest], Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
|
||||
@@ -503,6 +483,7 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Toke
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
@@ -594,7 +575,7 @@ collect_modifiers(Rest, Buffer) ->
|
||||
%% Heredocs
|
||||
|
||||
extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
|
||||
case extract_heredoc(Line, T, H) of
|
||||
case extract_heredoc(Line, T, H, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
{ Body, Rest } ->
|
||||
@@ -606,8 +587,8 @@ extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
|
||||
end
|
||||
end.
|
||||
|
||||
extract_heredoc(Line0, Rest0, Marker) ->
|
||||
case extract_heredoc_line(Rest0, []) of
|
||||
extract_heredoc(Line0, Rest0, Marker, Scope) ->
|
||||
case extract_heredoc_header(Rest0, [], {Line0,Scope#elixir_tokenizer.file}) of
|
||||
{ ok, Extra, Rest1 } ->
|
||||
%% We prepend a new line so we can transparently remove
|
||||
%% spaces later. This new line is removed by calling `tl`
|
||||
@@ -615,42 +596,52 @@ extract_heredoc(Line0, Rest0, Marker) ->
|
||||
case extract_heredoc_body(Line0, Marker, [$\n|Rest1], []) of
|
||||
{ Line1, Body, Rest2, Spaces } ->
|
||||
{ tl(remove_heredoc_spaces(Body, Spaces)), merge_heredoc_extra(Line1, Extra, Rest2) };
|
||||
{ error, Line1 } ->
|
||||
heredoc_error(Line1, Line0, Marker)
|
||||
{ error, ErrorLine } ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = "missing terminator: ~ts (for heredoc starting at line ~B)",
|
||||
{ error, { ErrorLine, io_lib:format(Message, [Terminator, Line0]), [] } }
|
||||
end;
|
||||
{ error, eof } ->
|
||||
heredoc_error(Line0, Line0, Marker)
|
||||
error ->
|
||||
%% TODO: Test me once the deprecated continuable heredocs are removed
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = "heredoc start ~ts must be followed by a new line",
|
||||
{ error, { Line0, io_lib:format(Message, [Terminator]), [] } }
|
||||
end.
|
||||
|
||||
heredoc_error(ErrorLine, StartLine, Marker) ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = io_lib:format("missing terminator: ~ts (for heredoc starting at line ~B)", [Terminator, StartLine]),
|
||||
{ error, { ErrorLine, Message, [] } }.
|
||||
|
||||
%% Remove spaces from heredoc based on the position of the final quotes.
|
||||
|
||||
remove_heredoc_spaces(Body, 0) ->
|
||||
lists:reverse([0|Body]);
|
||||
|
||||
remove_heredoc_spaces(Body, Spaces) ->
|
||||
remove_heredoc_spaces([0|Body], [], Spaces, Spaces).
|
||||
|
||||
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, ?is_horizontal_space(H) ->
|
||||
remove_heredoc_spaces([Backtrack,$\n|T], Buffer, Spaces - 1, Original);
|
||||
|
||||
remove_heredoc_spaces([$\n=H|T], Buffer, _Spaces, Original) ->
|
||||
remove_heredoc_spaces(T, [H|Buffer], Original, Original);
|
||||
|
||||
remove_heredoc_spaces([H|T], Buffer, Spaces, Original) ->
|
||||
remove_heredoc_spaces(T, [H|Buffer], Spaces, Original);
|
||||
|
||||
remove_heredoc_spaces([], Buffer, _Spaces, _Original) ->
|
||||
Buffer.
|
||||
|
||||
%% Extract the heredoc header.
|
||||
|
||||
extract_heredoc_header("\r\n" ++ Rest, Buffer, _Scope) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
extract_heredoc_header("\n" ++ Rest, Buffer, _Scope) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
extract_heredoc_header([H|T], Buffer, {Line,File}) when not ?is_horizontal_space(H) ->
|
||||
elixir_errors:deprecation([{line,Line}], File, "continuable heredocs are deprecated, parsing will no longer continue on the same line as the heredoc starts"),
|
||||
extract_heredoc_header(T, [H|Buffer], nil);
|
||||
extract_heredoc_header([H|T], Buffer, _Scope) ->
|
||||
extract_heredoc_header(T, [H|Buffer], _Scope);
|
||||
extract_heredoc_header(_, _, _Scope) ->
|
||||
error.
|
||||
|
||||
%% Extract heredoc body. It returns the heredoc body (in reverse order),
|
||||
%% the remaining of the document and the number of spaces the heredoc
|
||||
%% is aligned.
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
extract_heredoc_body(_Line, Marker, [{line,NewLine}|Rest], Buffer) ->
|
||||
extract_heredoc_body(NewLine, Marker, Rest, Buffer);
|
||||
|
||||
@@ -659,7 +650,7 @@ extract_heredoc_body(Line, Marker, Rest, Buffer) ->
|
||||
{ ok, NewBuffer, NewRest } ->
|
||||
extract_heredoc_body(Line + 1, Marker, NewRest, NewBuffer);
|
||||
{ ok, NewBuffer, NewRest, Spaces } ->
|
||||
{ Line + 1, NewBuffer, NewRest, Spaces };
|
||||
{ Line, NewBuffer, NewRest, Spaces };
|
||||
{ error, eof } ->
|
||||
{ error, Line }
|
||||
end.
|
||||
@@ -668,13 +659,10 @@ extract_heredoc_body(Line, Marker, Rest, Buffer) ->
|
||||
|
||||
extract_heredoc_line("\r\n" ++ Rest, Buffer) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
|
||||
extract_heredoc_line("\n" ++ Rest, Buffer) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
|
||||
extract_heredoc_line([H|T], Buffer) ->
|
||||
extract_heredoc_line(T, [H|Buffer]);
|
||||
|
||||
extract_heredoc_line(_, _) ->
|
||||
{ error, eof }.
|
||||
|
||||
@@ -682,31 +670,19 @@ extract_heredoc_line(_, _) ->
|
||||
|
||||
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when ?is_horizontal_space(H) ->
|
||||
extract_heredoc_line(Marker, T, [H|Buffer], Counter + 1);
|
||||
|
||||
extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) ->
|
||||
case next_is_break(T) of
|
||||
false -> extract_heredoc_line(Rest, Buffer);
|
||||
Final -> { ok, Buffer, Final, Counter }
|
||||
end;
|
||||
|
||||
extract_heredoc_line(Marker, [Marker,Marker,Marker|T], Buffer, Counter) ->
|
||||
{ ok, Buffer, T, Counter };
|
||||
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
|
||||
extract_heredoc_line(Rest, Buffer).
|
||||
|
||||
next_is_break([H|T]) when ?is_horizontal_space(H) -> next_is_break(T);
|
||||
next_is_break("\r\n" ++ T) -> T;
|
||||
next_is_break("\n" ++ T) -> T;
|
||||
next_is_break([]) -> [];
|
||||
next_is_break(_) -> false.
|
||||
|
||||
%% Merge heredoc extra by replying it on the buffer. It also adds
|
||||
%% a special { line, Line } token to force a line change along the way.
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
merge_heredoc_extra(Line, Extra, Buffer) ->
|
||||
merge_heredoc_extra(Extra, [{line,Line}|Buffer]).
|
||||
|
||||
merge_heredoc_extra([H|T], Buffer) ->
|
||||
merge_heredoc_extra(T, [H|Buffer]);
|
||||
|
||||
merge_heredoc_extra([], Buffer) ->
|
||||
Buffer.
|
||||
|
||||
@@ -952,8 +928,8 @@ keyword('or') -> or_op;
|
||||
keyword('xor') -> or_op;
|
||||
keyword('when') -> when_op;
|
||||
keyword('in') -> in_op;
|
||||
keyword('inlist') -> inc_op;
|
||||
keyword('inbits') -> inc_op;
|
||||
keyword('inlist') -> in_match_op;
|
||||
keyword('inbits') -> in_match_op;
|
||||
|
||||
% Block keywords
|
||||
keyword('after') -> block;
|
||||
|
||||
@@ -1,297 +1,112 @@
|
||||
%% Main entry point for translations. All macros that cannot be
|
||||
%% overriden are defined in this file.
|
||||
%% Translate Elixir quoted expressions to Erlang Abstract Format.
|
||||
%% Expects the tree to be expanded.
|
||||
-module(elixir_translator).
|
||||
-export([forms/4, 'forms!'/4]).
|
||||
-export([translate/2, translate_each/2, translate_arg/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4,
|
||||
assert_function_scope/3, assert_module_scope/3,
|
||||
assert_no_guard_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
-export([translate_many/2, translate/2, translate_arg/3, translate_args/2]).
|
||||
-import(elixir_scope, [mergev/2, mergec/2, mergef/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, _Line, Tokens } ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
end;
|
||||
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
end.
|
||||
translate_many(Forms, S) ->
|
||||
lists:mapfoldl(fun translate/2, S, Forms).
|
||||
|
||||
'forms!'(String, StartLine, File, Opts) ->
|
||||
case forms(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } ->
|
||||
Forms;
|
||||
{ error, { Line, Error, Token } } ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
%% =
|
||||
|
||||
translate(Forms, S) ->
|
||||
lists:mapfoldl(fun translate_each/2, S, Forms).
|
||||
|
||||
%% Assignment operator
|
||||
|
||||
translate_each({ '=', Meta, [Left, Right] }, S) ->
|
||||
assert_no_guard_scope(Meta, '=', S),
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
|
||||
translate({ '=', Meta, [Left, Right] }, S) ->
|
||||
{ TRight, SR } = translate(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun translate/2, Left, SR),
|
||||
{ { match, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
%% Containers
|
||||
|
||||
translate_each({ C, _, _ } = Original, S) when C == '{}'; C == '<<>>' ->
|
||||
elixir_literal:translate(Original, S);
|
||||
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
|
||||
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
||||
elixir_bitstring:translate(Meta, Args, S);
|
||||
|
||||
%% Blocks and scope rewriters
|
||||
|
||||
translate_each({ '__block__', Meta, [] }, S) ->
|
||||
{ { atom, ?line(Meta), nil }, S };
|
||||
|
||||
translate_each({ '__block__', _Line, [Arg] }, S) ->
|
||||
translate_each(Arg, S);
|
||||
|
||||
translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate(Args, S),
|
||||
translate({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate_many(Args, S),
|
||||
{ { block, ?line(Meta), TArgs }, NS };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
|
||||
Old = S#elixir_scope.file,
|
||||
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
|
||||
{ TExpr, TS#elixir_scope{file=Old} };
|
||||
|
||||
%% Erlang op
|
||||
|
||||
translate_each({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
translate({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
|
||||
{ TExpr, NS } = translate(Expr, S),
|
||||
{ { op, ?line(Meta), Op, TExpr }, NS };
|
||||
|
||||
translate_each({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
|
||||
translate({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
|
||||
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
|
||||
{ { op, ?line(Meta), Op, TLeft, TRight }, NS };
|
||||
|
||||
%% Lexical
|
||||
|
||||
translate_each({ alias, Meta, [Ref] }, S) ->
|
||||
translate_each({ alias, Meta, [Ref,[]] }, S);
|
||||
|
||||
translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, S),
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, alias, [as, warn], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
translate_alias(Meta, true, Old, TKV, ST);
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for alias, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
translate_each({ require, Meta, [Ref] }, S) ->
|
||||
translate_each({ require, Meta, [Ref, []] }, S);
|
||||
|
||||
translate_each({ require, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, require, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, require, [as, warn], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
elixir_aliases:ensure_loaded(Meta, Old, ST),
|
||||
translate_require(Meta, Old, TKV, ST);
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for require, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
translate_each({ import, Meta, [Left] }, S) ->
|
||||
translate_each({ import, Meta, [Left, []]}, S);
|
||||
|
||||
translate_each({ import, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, import, S),
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, import, [only, except, warn], KV, SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Atom } ->
|
||||
elixir_aliases:ensure_loaded(Meta, Atom, ST),
|
||||
SF = elixir_import:import(Meta, Atom, TKV, ST),
|
||||
translate_require(Meta, Atom, TKV, SF);
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid name for import, expected a compile time atom or alias")
|
||||
end;
|
||||
translate({ Lexical, _, [_, _] }, S) when Lexical == import; Lexical == alias; Lexical == require ->
|
||||
{ { atom, 0, nil }, S };
|
||||
|
||||
%% Pseudo variables
|
||||
|
||||
translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ { atom, ?line(Meta), S#elixir_scope.module }, S };
|
||||
|
||||
translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each(S#elixir_scope.file, S);
|
||||
|
||||
translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each(filename:dirname(S#elixir_scope.file), S);
|
||||
|
||||
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
Env = elixir_env:scope_to_ex({ ?line(Meta), S }),
|
||||
{ ex_env_to_erl(Env), S };
|
||||
|
||||
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
|
||||
|
||||
%% Aliases
|
||||
|
||||
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
|
||||
Receiver when is_atom(Receiver) ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
{ { atom, ?line(Meta), Receiver }, S };
|
||||
Aliases ->
|
||||
{ TAliases, SA } = translate_args(Aliases, S),
|
||||
|
||||
case lists:all(fun is_atom_tuple/1, TAliases) of
|
||||
true ->
|
||||
Atoms = [Atom || { atom, _, Atom } <- TAliases],
|
||||
Receiver = elixir_aliases:concat(Atoms),
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
{ { atom, ?line(Meta), Receiver }, SA };
|
||||
false ->
|
||||
Args = [elixir_utils:list_to_cons(?line(Meta), TAliases)],
|
||||
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
|
||||
end
|
||||
end;
|
||||
|
||||
%% Quoting
|
||||
|
||||
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
compile_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
|
||||
case lists:keyfind(do, 1, Opts) of
|
||||
{ do, Do } ->
|
||||
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
|
||||
end;
|
||||
|
||||
translate_each({ quote, Meta, [_] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, Do) of
|
||||
{ do, E } -> E;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
|
||||
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, TKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected a compile time atom or an alias");
|
||||
false ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
|
||||
|
||||
Keep = lists:keyfind(location, 1, TKV) == { location, keep },
|
||||
Line = proplists:get_value(line, TKV, false),
|
||||
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, TKV) of
|
||||
{ bind_quoted, BQ } -> { BQ, false };
|
||||
false -> { nil, true }
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, TKV) of
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ST),
|
||||
translate_each(Quoted, ST);
|
||||
|
||||
translate_each({ quote, Meta, [_, _] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
%% Functions
|
||||
|
||||
translate_each({ '&', Meta, [Arg] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, '&', S),
|
||||
elixir_fn:capture(Meta, Arg, S);
|
||||
translate({ '&', Meta, [{ '/', [], [{ Fun, [], Atom }, Arity] }] }, S)
|
||||
when is_atom(Fun), is_atom(Atom), is_integer(Arity) ->
|
||||
{ { 'fun', ?line(Meta), { function, Fun, Arity } }, S };
|
||||
translate({ '&', Meta, [Arg] }, S) when is_integer(Arg) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
|
||||
|
||||
translate_each({ fn, Meta, [{ '->', _, Pairs }] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'fn', S),
|
||||
elixir_fn:fn(Meta, Pairs, S);
|
||||
translate({ fn, Meta, Clauses }, S) ->
|
||||
elixir_fn:translate(Meta, Clauses, S);
|
||||
|
||||
%% Case
|
||||
|
||||
translate_each({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', S),
|
||||
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
Clauses = elixir_clauses:get_pairs(do, KV),
|
||||
{ TExpr, NS } = translate(Expr, S),
|
||||
|
||||
RClauses = case elixir_utils:returns_boolean(TExpr) of
|
||||
true -> rewrite_case_clauses(Clauses);
|
||||
false -> Clauses
|
||||
end,
|
||||
|
||||
{ TClauses, TS } = elixir_clauses:match(Meta, RClauses, NS),
|
||||
{ TClauses, TS } = elixir_clauses:clauses(Meta, RClauses, NS),
|
||||
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
|
||||
|
||||
%% Try
|
||||
|
||||
translate_each({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
|
||||
S = RS#elixir_scope{noname=true},
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
|
||||
{ TDo, SB } = elixir_translator:translate(Do, S),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, mergec(S, SB)),
|
||||
|
||||
After = proplists:get_value('after', Clauses, nil),
|
||||
{ TAfter, SA } = translate_each(After, umergea(S, SC)),
|
||||
{ TAfter, SA } = translate(After, mergec(S, SC)),
|
||||
|
||||
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
|
||||
Else = elixir_clauses:get_pairs(else, Clauses),
|
||||
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
|
||||
|
||||
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
|
||||
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, unblock(TAfter) }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate_each({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', S),
|
||||
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
|
||||
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
Do = elixir_clauses:get_pairs(do, KV, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
false ->
|
||||
{ TClauses, SC } = elixir_clauses:match(Meta, Do, S),
|
||||
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do, S),
|
||||
{ { 'receive', ?line(Meta), TClauses }, SC };
|
||||
_ ->
|
||||
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
|
||||
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
|
||||
After = elixir_clauses:get_pairs('after', KV),
|
||||
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do ++ After, S),
|
||||
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
|
||||
{ _, _, [FExpr], _, FAfter } = TAfter,
|
||||
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
|
||||
@@ -299,13 +114,12 @@ translate_each({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate_comprehension(Meta, Kind, Args, S);
|
||||
|
||||
%% Super
|
||||
|
||||
translate_each({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
translate({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
Module = assert_module_scope(Meta, super, S),
|
||||
Function = assert_function_scope(Meta, super, S),
|
||||
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
|
||||
@@ -316,8 +130,8 @@ translate_each({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
|
||||
"arguments as the current function")
|
||||
compile_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
|
||||
"arguments as the current function")
|
||||
end,
|
||||
|
||||
Super = elixir_def_overridable:name(Module, Function),
|
||||
@@ -325,174 +139,131 @@ translate_each({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
|
||||
translate({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
|
||||
#elixir_scope{extra=fn_match, extra_guards=Extra} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
Tuple = { Name, var_kind(VarMeta, Kind) },
|
||||
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
|
||||
{ ok, { Value, _Counter } } ->
|
||||
Line = ?line(Meta),
|
||||
{ TVar, TS } = translate_each(Var, S),
|
||||
{ TVar, TS } = translate(Var, S),
|
||||
Guard = { op, Line, '=:=', { var, ?line(Meta), Value }, TVar },
|
||||
{ TVar, TS#elixir_scope{extra_guards=[Guard|Extra]} };
|
||||
error ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
translate({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
Tuple = { Name, var_kind(VarMeta, Kind) },
|
||||
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
|
||||
{ ok, { Value, _Counter } } ->
|
||||
{ { var, ?line(Meta), Value }, S };
|
||||
error ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot use ^~ts outside of match clauses", [Name]);
|
||||
translate({ '_', Meta, Kind }, #elixir_scope{context=match} = S) when is_atom(Kind) ->
|
||||
{ { var, ?line(Meta), '_' }, S };
|
||||
|
||||
translate_each({ '^', Meta, [ Expr ] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
translate({ '_', Meta, Kind }, S) when is_atom(Kind) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable _");
|
||||
|
||||
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S, fun() ->
|
||||
case lists:keyfind(var, 1, Meta) of
|
||||
{ var, true } ->
|
||||
compile_error(Meta, S#elixir_scope.file, "expected var ~ts to expand to an existing "
|
||||
"variable or be a part of a match", [Name]);
|
||||
_ ->
|
||||
translate_each({ Name, Meta, [] }, S)
|
||||
end
|
||||
end);
|
||||
translate({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S);
|
||||
|
||||
%% Local calls
|
||||
|
||||
translate_each({ '->', Meta, _Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unhandled operator ->");
|
||||
|
||||
translate_each({ Atom, Meta, Args }, S) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
||||
assert_no_ambiguous_op(Atom, Meta, Args, S),
|
||||
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
||||
[Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
|
||||
translate({ Name, Meta, Args }, S) when is_atom(Name), is_list(Meta), is_list(Args) ->
|
||||
if
|
||||
S#elixir_scope.context == match ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot invoke function ~ts/~B inside match", [Name, length(Args)]);
|
||||
S#elixir_scope.context == guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/~B inside guard", [Name, Name, Arity]);
|
||||
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
||||
[Name, Arity])
|
||||
end;
|
||||
S#elixir_scope.function == nil ->
|
||||
compile_error(Meta, S#elixir_scope.file, "undefined function ~ts/~B", [Name, length(Args)]);
|
||||
true ->
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS }
|
||||
end;
|
||||
|
||||
%% Remote calls
|
||||
|
||||
translate_each({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
||||
translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
||||
{ TLeft, SL } = translate(Left, S),
|
||||
{ TRight, SR } = translate(Right, mergec(S, SL)),
|
||||
|
||||
Callback = fun() ->
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
Tuple = { element(3, TRight), length(Args) },
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SR)),
|
||||
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, umergev(SL, umergev(SR, SA)) }
|
||||
end,
|
||||
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end);
|
||||
case S#elixir_scope.context of
|
||||
Context when Left /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
['Elixir.Macro':to_string(Left), Right, length(Args), Context]);
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Context == match; Context == guard ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
|
||||
[Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, SA } = translate_args(Args, mergec(S, SR)),
|
||||
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, mergev(SL, mergev(SR, SA)) }
|
||||
end;
|
||||
|
||||
%% Anonymous function calls
|
||||
|
||||
translate_each({ { '.', _, [Expr] }, Meta, Args }, S) when is_list(Args) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
case TExpr of
|
||||
{ atom, _, Atom } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
|
||||
_ ->
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
|
||||
{ {call, ?line(Meta), TExpr, TArgs}, umergev(SE, SA) }
|
||||
end;
|
||||
|
||||
%% Invalid calls
|
||||
|
||||
translate_each({ { '.', _, [Invalid, _] }, Meta, Args }, S) when is_list(Meta) and is_list(Args) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid remote call on ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
translate_each({ _, Meta, Args } = Invalid, S) when is_list(Meta) and is_list(Args) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid call ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
translate_each({ _, _, _ } = Tuple, S) ->
|
||||
syntax_error([{line,0}], S#elixir_scope.file, "expected a valid quoted expression, got: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple, [{raw,true}])]);
|
||||
translate({ { '.', _, [Expr] }, Meta, Args }, S) when is_list(Args) ->
|
||||
{ TExpr, SE } = translate(Expr, S),
|
||||
{ TArgs, SA } = translate_args(Args, mergec(S, SE)),
|
||||
{ { call, ?line(Meta), TExpr, TArgs }, mergev(SE, SA) };
|
||||
|
||||
%% Literals
|
||||
|
||||
translate_each(Literal, S) ->
|
||||
elixir_literal:translate(Literal, S).
|
||||
translate(List, S) when is_list(List) ->
|
||||
Fun = case S#elixir_scope.context of
|
||||
match -> fun translate/2;
|
||||
_ -> fun(X, Acc) -> translate_arg(X, Acc, S) end
|
||||
end,
|
||||
translate_list(List, Fun, S, []);
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
{ TArgs, SE } = translate_args([Left, Right], S),
|
||||
{ { tuple, 0, TArgs }, SE };
|
||||
|
||||
translate(Other, S) ->
|
||||
{ elixir_utils:elixir_to_erl(Other), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
translate_list([{ '|', _, [_, _]=Args}], Fun, Acc, List) ->
|
||||
{ [TLeft,TRight], TAcc } = lists:mapfoldl(Fun, Acc, Args),
|
||||
{ build_list([TLeft|List], TRight), TAcc };
|
||||
translate_list([H|T], Fun, Acc, List) ->
|
||||
{ TH, TAcc } = Fun(H, Acc),
|
||||
translate_list(T, Fun, TAcc, [TH|List]);
|
||||
translate_list([], _Fun, Acc, List) ->
|
||||
{ build_list(List, { nil, 0 }), Acc }.
|
||||
|
||||
build_list([H|T], Acc) ->
|
||||
build_list(T, { cons, 0, H, Acc });
|
||||
build_list([], Acc) ->
|
||||
Acc.
|
||||
|
||||
var_kind(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
ex_env_to_erl(Structure) ->
|
||||
elixir_utils:elixir_to_erl(Structure, fun
|
||||
(X) when is_pid(X) ->
|
||||
?wrap_call(0, erlang, binary_to_term, [elixir_utils:elixir_to_erl(term_to_binary(X))]);
|
||||
(Other) ->
|
||||
error({ badarg, Other })
|
||||
end).
|
||||
|
||||
%% Case
|
||||
|
||||
%% TODO: Once we have elixir_exp, we can move this
|
||||
%% clause to Elixir code and out of case.
|
||||
rewrite_case_clauses([
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{in,_,[{V,M,C},[false,nil]]}]}],False},
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{'__op__',_,['orelse',_,_]}]}],False},
|
||||
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
||||
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
||||
case lists:keyfind('cond', 1, M) of
|
||||
{ 'cond', true } ->
|
||||
{'cond',true} ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
_ ->
|
||||
Clauses
|
||||
@@ -500,197 +271,66 @@ rewrite_case_clauses([
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
% Pack a list of expressions from a block.
|
||||
pack({ 'block', _, Exprs }) -> Exprs;
|
||||
pack(Expr) -> [Expr].
|
||||
|
||||
%% Opts
|
||||
|
||||
translate_opts(Meta, Kind, Allowed, Opts, S) ->
|
||||
{ Expanded, TS } = case literal_opts(Opts) orelse skip_expansion(S#elixir_scope.module) of
|
||||
true -> { Opts, S };
|
||||
false -> 'Elixir.Macro':expand_all(Opts, elixir_env:scope_to_ex({ ?line(Meta), S }), S)
|
||||
end,
|
||||
validate_opts(Meta, Kind, Allowed, Expanded, TS),
|
||||
{ Expanded, TS }.
|
||||
|
||||
skip_expansion('Elixir.Kernel') -> true;
|
||||
skip_expansion('Elixir.Kernel.Typespec') -> true;
|
||||
skip_expansion(_) -> false.
|
||||
|
||||
literal_opts({ X, Y }) -> literal_opts(X) andalso literal_opts(Y);
|
||||
literal_opts(Opts) when is_list(Opts) -> lists:all(fun literal_opts/1, Opts);
|
||||
literal_opts(Opts) when is_atom(Opts); is_number(Opts); is_bitstring(Opts) -> true;
|
||||
literal_opts(_) -> false.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
[begin
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"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]).
|
||||
|
||||
%% Require
|
||||
|
||||
translate_require(Meta, Old, TKV, S) ->
|
||||
SF = S#elixir_scope{
|
||||
requires=ordsets:add_element(Old, S#elixir_scope.requires)
|
||||
},
|
||||
translate_alias(Meta, false, Old, TKV, SF).
|
||||
|
||||
%% Aliases
|
||||
|
||||
translate_alias(Meta, IncludeByDefault, Old, TKV, #elixir_scope{context_modules=Context} = S) ->
|
||||
New = case lists:keyfind(as, 1, TKV) of
|
||||
{ as, true } ->
|
||||
elixir_aliases:last(Old);
|
||||
{ as, false } ->
|
||||
Old;
|
||||
{ as, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
false ->
|
||||
if
|
||||
IncludeByDefault -> elixir_aliases:last(Old);
|
||||
true -> Old
|
||||
end;
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"invalid :as for alias, expected a compile time atom or alias")
|
||||
end,
|
||||
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
|
||||
true -> ok;
|
||||
false -> compile_error(Meta, S#elixir_scope.file,
|
||||
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
|
||||
end,
|
||||
|
||||
%% Add the alias to context_modules if defined is true.
|
||||
%% This is used by defmodule.
|
||||
NewContext =
|
||||
case lists:keyfind(defined, 1, Meta) of
|
||||
{ defined, Mod } when is_atom(Mod) -> [Mod|Context];
|
||||
false -> Context
|
||||
end,
|
||||
|
||||
{ Aliases, MacroAliases } = elixir_aliases:store(Meta, New, Old, TKV, S#elixir_scope.aliases,
|
||||
S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker),
|
||||
|
||||
{ { atom, ?line(Meta), nil },
|
||||
S#elixir_scope{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext} }.
|
||||
|
||||
no_alias_opts(KV) when is_list(KV) ->
|
||||
case lists:keyfind(as, 1, KV) of
|
||||
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
|
||||
false -> KV
|
||||
end;
|
||||
no_alias_opts(KV) -> KV.
|
||||
|
||||
%% Do not allow expansion of the alias itself.
|
||||
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
|
||||
{ '__aliases__', Meta, ['Elixir',H|T] };
|
||||
no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
is_atom_tuple({ atom, _, _ }) -> true;
|
||||
is_atom_tuple(_) -> false.
|
||||
|
||||
%% Locals
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,function=nil} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "function ~ts/~B undefined", [Name, length(Args)]);
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
|
||||
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
|
||||
|
||||
translate_local(Meta, Name, Args, S) ->
|
||||
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
|
||||
%% Pack a list of expressions from a block.
|
||||
unblock({ 'block', _, Exprs }) -> Exprs;
|
||||
unblock(Expr) -> [Expr].
|
||||
|
||||
%% Translate args
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
%% argument to the other. For instance:
|
||||
%%
|
||||
%% x = 1
|
||||
%% foo(x = x + 2, x)
|
||||
%% x
|
||||
%%
|
||||
%% Should be the same as:
|
||||
%%
|
||||
%% foo(3, 1)
|
||||
%% 3
|
||||
%%
|
||||
%% However, notice that if we are doing an assignment,
|
||||
%% it behaves the same as translate.
|
||||
|
||||
translate_arg(Arg, { Acc, S }) ->
|
||||
{ TArg, TAcc } = translate_each(Arg, Acc),
|
||||
{ TArg, { umergea(Acc, TAcc), umergev(S, TAcc) } }.
|
||||
translate_arg(Arg, Acc, S) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
|
||||
{ TArg, _ } = translate(Arg, S),
|
||||
{ TArg, Acc };
|
||||
translate_arg(Arg, Acc, S) ->
|
||||
{ TArg, TAcc } = translate(Arg, mergec(S, Acc)),
|
||||
{ TArg, mergev(Acc, TAcc) }.
|
||||
|
||||
translate_args(Args, #elixir_scope{context=match} = S) ->
|
||||
translate(Args, S);
|
||||
translate_many(Args, S);
|
||||
|
||||
translate_args(Args, S) ->
|
||||
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
|
||||
{ TArgs, umergea(SV, SC) }.
|
||||
|
||||
%% __op__ helpers
|
||||
|
||||
assert_no_ambiguous_op(Name, Meta, [Arg], S) ->
|
||||
case lists:keyfind(ambiguous_op, 1, Meta) of
|
||||
{ ambiguous_op, Kind } ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
error -> ok;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "\"~ts ~ts\" looks like a function call but "
|
||||
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
|
||||
[Name, 'Elixir.Macro':to_string(Arg), Name])
|
||||
end;
|
||||
_ -> ok
|
||||
end;
|
||||
|
||||
assert_no_ambiguous_op(_Atom, _Meta, _Args, _S) -> ok.
|
||||
lists:mapfoldl(fun(X, Acc) -> translate_arg(X, Acc, S) end, S, Args).
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate_comprehension(Meta, Kind, Args, S) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ Cases, [{do,Expr}] } ->
|
||||
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
|
||||
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
|
||||
{ { Kind, ?line(Meta), TExpr, TCases }, umergec(S, SE) };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in comprehension ~s", [Kind])
|
||||
end.
|
||||
{ Cases, [{do,Expr}] } = elixir_utils:split_last(Args),
|
||||
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
|
||||
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
|
||||
{ { Kind, ?line(Meta), TExpr, TCases }, mergef(S, SE) }.
|
||||
|
||||
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->
|
||||
translate_each(Expr, S);
|
||||
translate(Expr, S);
|
||||
|
||||
translate_comprehension_do(Meta, bc, _Expr, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
|
||||
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
|
||||
|
||||
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
|
||||
translate_each(Expr, S).
|
||||
translate(Expr, S).
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [{ '<<>>', _, _} = Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
{ TRight, SR } = translate(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
|
||||
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
|
||||
|
||||
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
{ TRight, SR } = translate(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{ { generate, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause(Meta, X, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ TX, TS } = translate_each(X, S),
|
||||
{ TX, TS } = translate(X, S),
|
||||
{ BX, BS } = elixir_utils:convert_to_boolean(Line, TX, true, TS),
|
||||
{ { match, Line, { var, Line, '_' }, BX }, BS }.
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
compile_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
|
||||
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
compile_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
-module(elixir_try).
|
||||
-export([clauses/3, format_error/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
clauses(Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
|
||||
Rescue = elixir_clauses:get_pairs(Meta, rescue, Clauses, S),
|
||||
Transformer = fun(X, Acc) -> each_clause(X, umergec(S, Acc)) end,
|
||||
clauses(_Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs('catch', Clauses),
|
||||
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
|
||||
Transformer = fun(X, SAcc) ->
|
||||
{ TX, TS } = each_clause(X, SAcc),
|
||||
{ TX, elixir_scope:mergec(S, TS) }
|
||||
end,
|
||||
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
|
||||
|
||||
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
|
||||
@@ -17,87 +19,46 @@ each_clause({ 'catch', Meta, Raw, Expr }, S) ->
|
||||
[X,Y] -> [X, Y, { '_', Meta, nil }];
|
||||
[_,_,_] -> Args;
|
||||
_ ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
|
||||
end,
|
||||
|
||||
Condition = { '{}', Meta, Final },
|
||||
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
|
||||
Condition = [{ '{}', Meta, Final }],
|
||||
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_many/2, Condition, Expr, Guards, S);
|
||||
|
||||
each_clause({ rescue, Meta, [Condition], Expr }, S) ->
|
||||
case normalize_rescue(Meta, Condition, S) of
|
||||
{ Left, Right } ->
|
||||
case Left of
|
||||
{ '_', _, _ } ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, CS);
|
||||
_ ->
|
||||
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, S);
|
||||
false ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
Match = { '=', Meta, [
|
||||
Left,
|
||||
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
|
||||
] },
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
|
||||
end
|
||||
end;
|
||||
each_clause({ rescue, Meta, [{ in, _, [Left, Right]}], Expr }, S) ->
|
||||
case Left of
|
||||
{ '_', _, LAtom } when is_atom(LAtom) ->
|
||||
{ VarName, _, CS } = elixir_scope:build_var('_', S),
|
||||
{ Clause, _ } = rescue_guards(Meta, { VarName, Meta, nil }, Right, S),
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, CS);
|
||||
_ ->
|
||||
each_clause({ 'catch', Meta, [error, Condition], Expr }, S)
|
||||
end;
|
||||
|
||||
each_clause({rescue,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
|
||||
|
||||
each_clause({Key,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
%% rescue [Error] -> _ in [Error]
|
||||
normalize_rescue(Meta, List, S) when is_list(List) ->
|
||||
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, List] }, S);
|
||||
|
||||
%% rescue _
|
||||
normalize_rescue(_, { '_', _, Atom }, _S) when is_atom(Atom) ->
|
||||
false;
|
||||
|
||||
%% rescue var -> var in _
|
||||
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom) ->
|
||||
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
|
||||
case Right of
|
||||
{ '_', _, _ } ->
|
||||
{ Left, nil };
|
||||
_ when is_list(Right) ->
|
||||
is_valid_rescue_list(Right, S) andalso { Left, Right };
|
||||
_ ->
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_env:scope_to_ex({ ?line(Meta), S }), S),
|
||||
case is_valid_rescue_list(Expanded, ES) of
|
||||
true -> { Left, Expanded };
|
||||
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, S);
|
||||
false ->
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
||||
{ VarName, _, CS } = elixir_scope:build_var('_', S),
|
||||
ClauseVar = { VarName, Meta, nil },
|
||||
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
Match = { '=', Meta, [
|
||||
Left,
|
||||
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
|
||||
] },
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
|
||||
end
|
||||
end;
|
||||
|
||||
normalize_rescue(Meta, Condition, S) ->
|
||||
case elixir_translator:translate_each(Condition, S#elixir_scope{context=match}) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
|
||||
_ ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should "
|
||||
"match on an alias, a variable or be in the `var in [alias]` format")
|
||||
end.
|
||||
each_clause({rescue,Meta,_,_}, S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid arguments for rescue in try");
|
||||
|
||||
each_clause({Key,Meta,_,_}, S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
%% Convert rescue clauses into guards.
|
||||
rescue_guards(_, Var, nil, _) -> { [error, Var], false };
|
||||
rescue_guards(_, Var, { '_', _, _ }, _) -> { [error, Var], false };
|
||||
|
||||
rescue_guards(Meta, Var, Guards, S) ->
|
||||
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
|
||||
@@ -160,7 +121,7 @@ rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
|
||||
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
|
||||
case elixir_translator:translate_each(H, S) of
|
||||
case elixir_translator:translate(H, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
|
||||
_ ->
|
||||
@@ -244,13 +205,6 @@ format_error({ rescue_no_match, Var, Alias }) ->
|
||||
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var(_) -> false.
|
||||
|
||||
is_erl_var_or_atom({ Kind, Line, Name }) when Kind == var orelse Kind == atom, is_integer(Line), is_atom(Name) -> true;
|
||||
is_erl_var_or_atom(_) -> false.
|
||||
|
||||
is_valid_rescue_list(Right, S) when is_list(Right) ->
|
||||
{ TRight, _ } = elixir_translator:translate(Right, S),
|
||||
lists:all(fun is_erl_var_or_atom/1, TRight).
|
||||
|
||||
exception_compare(Meta, Var, Expr) ->
|
||||
{ erl(Meta, '=='), Meta, [
|
||||
{ erl(Meta, element), Meta, [1, Var] },
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
%% Convenience functions used throughout elixir source code
|
||||
%% for ast manipulation and querying.
|
||||
-module(elixir_utils).
|
||||
-export([elixir_to_erl/1, elixir_to_erl/2,
|
||||
get_line/1, split_last/1, elixir_to_erl/3,
|
||||
-export([elixir_to_erl/1, get_line/1, split_last/1,
|
||||
characters_to_list/1, characters_to_binary/1,
|
||||
cons_to_list/1, list_to_cons/2, list_to_cons/3,
|
||||
convert_to_boolean/4, returns_boolean/1,
|
||||
file_type/1, file_type/2, relative_to_cwd/1]).
|
||||
-include("elixir.hrl").
|
||||
@@ -52,58 +50,34 @@ characters_to_binary(Data) ->
|
||||
false -> 'Elixir.String':'from_char_list!'(Data)
|
||||
end.
|
||||
|
||||
%% List conversion
|
||||
|
||||
cons_to_list({ nil, _ }) -> [];
|
||||
cons_to_list({ cons, _, Left, Right }) -> [Left|cons_to_list(Right)].
|
||||
|
||||
list_to_cons(Line, List) ->
|
||||
list_to_cons(Line, List, { nil, Line }).
|
||||
|
||||
list_to_cons(Line, List, Tail) ->
|
||||
lists:foldr(fun(X, Acc) ->
|
||||
{ cons, Line, X, Acc }
|
||||
end, Tail, List).
|
||||
|
||||
%% erl <-> elixir
|
||||
|
||||
elixir_to_erl(Tree) ->
|
||||
elixir_to_erl(Tree, fun(X) -> error({ badarg, X }) end).
|
||||
elixir_to_erl(Tree) when is_tuple(Tree) ->
|
||||
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
|
||||
|
||||
elixir_to_erl(Line, Tree, S) ->
|
||||
elixir_to_erl(Tree, fun
|
||||
(X) when is_function(X) ->
|
||||
elixir_errors:compile_error(Line, S#elixir_scope.file,
|
||||
"anonymous functions cannot be translated into a quoted expression, got: ~ts",
|
||||
['Elixir.Kernel':inspect(X)]);
|
||||
(X) ->
|
||||
elixir_errors:compile_error(Line, S#elixir_scope.file,
|
||||
"cannot translate ~ts into a quoted expression",
|
||||
['Elixir.Kernel':inspect(X)])
|
||||
end).
|
||||
|
||||
elixir_to_erl(Tree, Fun) when is_tuple(Tree) ->
|
||||
{ tuple, 0, [elixir_to_erl(X, Fun) || X <- tuple_to_list(Tree)] };
|
||||
|
||||
elixir_to_erl([], _Fun) ->
|
||||
elixir_to_erl([]) ->
|
||||
{ nil, 0 };
|
||||
|
||||
elixir_to_erl(Tree, Fun) when is_list(Tree) ->
|
||||
elixir_to_erl_cons_1(Tree, [], Fun);
|
||||
elixir_to_erl(Tree) when is_list(Tree) ->
|
||||
elixir_to_erl_cons_1(Tree, []);
|
||||
|
||||
elixir_to_erl(Tree, _Fun) when is_atom(Tree) ->
|
||||
elixir_to_erl(Tree) when is_atom(Tree) ->
|
||||
{ atom, 0, Tree };
|
||||
|
||||
elixir_to_erl(Tree, _Fun) when is_integer(Tree) ->
|
||||
elixir_to_erl(Tree) when is_integer(Tree) ->
|
||||
{ integer, 0, Tree };
|
||||
|
||||
elixir_to_erl(Tree, _Fun) when is_float(Tree) ->
|
||||
elixir_to_erl(Tree) when is_float(Tree) ->
|
||||
{ float, 0, Tree };
|
||||
|
||||
elixir_to_erl(Tree, _Fun) when is_binary(Tree) ->
|
||||
elixir_to_erl(Tree) when is_binary(Tree) ->
|
||||
%% Note that our binaries are utf-8 encoded and we are converting
|
||||
%% to a list using binary_to_list. The reason for this is that Erlang
|
||||
%% considers a string in a binary to be encoded in latin1, so the bytes
|
||||
%% are not changed in any fashion.
|
||||
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] };
|
||||
|
||||
elixir_to_erl(Function, Fun) when is_function(Function) ->
|
||||
elixir_to_erl(Function) when is_function(Function) ->
|
||||
case (erlang:fun_info(Function, type) == { type, external }) andalso
|
||||
(erlang:fun_info(Function, env) == { env, [] }) of
|
||||
true ->
|
||||
@@ -116,18 +90,21 @@ elixir_to_erl(Function, Fun) when is_function(Function) ->
|
||||
{ atom, 0, Name },
|
||||
{ integer, 0, Arity } } };
|
||||
false ->
|
||||
Fun(Function)
|
||||
error(badarg)
|
||||
end;
|
||||
|
||||
elixir_to_erl(Other, Fun) ->
|
||||
Fun(Other).
|
||||
elixir_to_erl(Pid) when is_pid(Pid) ->
|
||||
?wrap_call(0, erlang, binary_to_term, [elixir_utils:elixir_to_erl(term_to_binary(Pid))]);
|
||||
|
||||
elixir_to_erl_cons_1([H|T], Acc, Fun) -> elixir_to_erl_cons_1(T, [H|Acc], Fun);
|
||||
elixir_to_erl_cons_1(Other, Acc, Fun) -> elixir_to_erl_cons_2(Acc, elixir_to_erl(Other, Fun), Fun).
|
||||
elixir_to_erl(_Other) ->
|
||||
error(badarg).
|
||||
|
||||
elixir_to_erl_cons_2([H|T], Acc, Fun) ->
|
||||
elixir_to_erl_cons_2(T, { cons, 0, elixir_to_erl(H, Fun), Acc }, Fun);
|
||||
elixir_to_erl_cons_2([], Acc, _Fun) ->
|
||||
elixir_to_erl_cons_1([H|T], Acc) -> elixir_to_erl_cons_1(T, [H|Acc]);
|
||||
elixir_to_erl_cons_1(Other, Acc) -> elixir_to_erl_cons_2(Acc, elixir_to_erl(Other)).
|
||||
|
||||
elixir_to_erl_cons_2([H|T], Acc) ->
|
||||
elixir_to_erl_cons_2(T, { cons, 0, elixir_to_erl(H), Acc });
|
||||
elixir_to_erl_cons_2([], Acc) ->
|
||||
Acc.
|
||||
|
||||
%% Boolean checks
|
||||
@@ -175,9 +152,10 @@ convert_to_boolean(Line, Expr, Bool, S) when is_integer(Line) ->
|
||||
%% and false checks in the same clause since
|
||||
%% it makes dialyzer happy.
|
||||
do_convert_to_boolean(Line, Expr, Bool, S) ->
|
||||
Any = { var, Line, '_' },
|
||||
{ Var, TS } = elixir_scope:build_erl_var(Line, S),
|
||||
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
|
||||
{ Name, _, TS } = elixir_scope:build_var('_', S),
|
||||
Var = { var, Line, Name },
|
||||
Any = { var, Line, '_' },
|
||||
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
|
||||
|
||||
FalseResult = { atom,Line,not Bool },
|
||||
TrueResult = { atom,Line,Bool },
|
||||
|
||||
@@ -10,31 +10,48 @@ defmodule BehaviourTest do
|
||||
defcallback first(integer) :: integer
|
||||
|
||||
@doc "Foo"
|
||||
defcallback foo(atom, binary) :: binary
|
||||
defcallback foo(atom(), binary) :: binary
|
||||
|
||||
@doc "Bar"
|
||||
defcallback bar(External.hello, my_var :: binary) :: binary
|
||||
|
||||
defcallback guarded(my_var) :: my_var when my_var: binary
|
||||
|
||||
defcallback orr(atom | integer) :: atom
|
||||
|
||||
defcallback literal(123, { atom }, :atom, [integer], true) :: atom
|
||||
|
||||
@doc "I should be last."
|
||||
defmacrocallback last(integer) :: Macro.t
|
||||
end
|
||||
|
||||
test :docs do
|
||||
docs = Sample.__behaviour__(:docs)
|
||||
assert docs == [
|
||||
{{:first, 1}, 10, :def, "I should be first."},
|
||||
{{:foo, 2}, 13, :def, "Foo"},
|
||||
{{:bar, 2}, 16, :def, "Bar"},
|
||||
{{:last, 1}, 19, :defmacro, "I should be last."}
|
||||
]
|
||||
assert [
|
||||
{{:first, 1}, 10, :def, _, "I should be first."},
|
||||
{{:foo, 2}, 13, :def, _, "Foo"},
|
||||
{{:bar, 2}, 16, :def, _, "Bar"},
|
||||
{{:guarded, 1}, 18, :def, _, nil},
|
||||
{{:orr, 1}, 20, :def, _, nil},
|
||||
{{:literal, 5}, 22, :def, _, nil},
|
||||
{{:last, 1}, 25, :defmacro, _, "I should be last."}
|
||||
] = docs
|
||||
|
||||
assert [{ :integer, _, nil }] = List.keyfind(docs, { :first, 1 }, 0) |> elem(3)
|
||||
assert [{ :atom, _, nil }, { :binary, _, nil }] = List.keyfind(docs, { :foo, 2 }, 0) |> elem(3)
|
||||
assert [{ :hello, _, nil }, { :my_var, _, nil }] = List.keyfind(docs, { :bar, 2 }, 0) |> elem(3)
|
||||
assert [{ :my_var, _, nil }] = List.keyfind(docs, { :guarded, 1 }, 0) |> elem(3)
|
||||
assert [{ :atom, _, nil }] = List.keyfind(docs, { :orr, 1 }, 0) |> elem(3)
|
||||
assert [{ :int1, _, nil }, { :tuple2, _, nil }, { :atom3, _, nil }, { :list4, _, nil }, { :bool5, _, nil} ] = List.keyfind(docs, { :literal, 5 }, 0) |> elem(3)
|
||||
assert [{ :integer, _, nil }] = List.keyfind(docs, { :last, 1 }, 0) |> elem(3)
|
||||
end
|
||||
|
||||
test :callbacks do
|
||||
assert Sample.__behaviour__(:callbacks) == [first: 1, foo: 2, bar: 2, "MACRO-last": 2]
|
||||
assert Sample.__behaviour__(:callbacks) == [first: 1, foo: 2, bar: 2, guarded: 1, orr: 1, literal: 5, "MACRO-last": 2]
|
||||
end
|
||||
|
||||
test :specs do
|
||||
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 4
|
||||
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 7
|
||||
end
|
||||
|
||||
test :default_is_not_supported do
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule CodeTest do
|
||||
|
||||
contents = quote do
|
||||
defmodule CodeTest.Sample do
|
||||
def eval_quoted_info, do: { __MODULE__, __FILE__, __ENV__.line }
|
||||
def eval_quoted_info, do: { __MODULE__, __ENV__.file, __ENV__.line }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,10 +69,6 @@ defmodule CodeTest do
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) != nil
|
||||
end
|
||||
|
||||
test :file do
|
||||
assert :filename.absname(__FILE__) == __FILE__
|
||||
end
|
||||
|
||||
test :string_to_quoted do
|
||||
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
|
||||
assert { :error, _ } = Code.string_to_quoted("a.1")
|
||||
@@ -96,7 +92,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :compile_source do
|
||||
assert __MODULE__.__info__(:compile)[:source] == String.to_char_list!(__FILE__)
|
||||
assert __MODULE__.__info__(:compile)[:source] == String.to_char_list!(__ENV__.file)
|
||||
end
|
||||
|
||||
test :compile_info_returned_with_source_accessible_through_keyword_module do
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule DictTest.Common do
|
||||
defmacro __using__(module) do
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
# Most underlying Dict implementations have no key
|
||||
# order guarantees, sort them before we compare:
|
||||
@@ -12,18 +12,18 @@ defmodule DictTest.Common do
|
||||
end
|
||||
end
|
||||
|
||||
defp empty_dict, do: unquote(module).new
|
||||
defp empty_dict, do: dict_impl.new
|
||||
|
||||
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
|
||||
unquote(module).new list
|
||||
dict_impl.new list
|
||||
end
|
||||
|
||||
defp new_dict(list, transform) do
|
||||
unquote(module).new list, transform
|
||||
dict_impl.new list, transform
|
||||
end
|
||||
|
||||
defp int_dict do
|
||||
unquote(module).new [{1,1}]
|
||||
dict_impl.new [{1,1}]
|
||||
end
|
||||
|
||||
test :access do
|
||||
@@ -333,19 +333,13 @@ defmodule DictTest.Common do
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1, 1.0 })
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1.0 })
|
||||
end
|
||||
|
||||
test "unsupported dict" do
|
||||
assert_raise ArgumentError, "unsupported dict: :bad_dict", fn ->
|
||||
Dict.to_list :bad_dict
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dict.HashDictTest do
|
||||
use ExUnit.Case, async: true
|
||||
use DictTest.Common, HashDict
|
||||
use DictTest.Common
|
||||
|
||||
doctest Dict
|
||||
defp dict_impl, do: HashDict
|
||||
@@ -353,7 +347,7 @@ end
|
||||
|
||||
defmodule Dict.ListDictTest do
|
||||
use ExUnit.Case, async: true
|
||||
use DictTest.Common, ListDict
|
||||
use DictTest.Common
|
||||
|
||||
doctest Dict
|
||||
defp dict_impl, do: ListDict
|
||||
|
||||
@@ -143,11 +143,6 @@ defmodule EnumTest.List do
|
||||
assert Enum.fetch([2, 4, 6], -4) == :error
|
||||
end
|
||||
|
||||
test :first do
|
||||
assert Enum.first([]) == nil
|
||||
assert Enum.first([1, 2, 3]) == 1
|
||||
end
|
||||
|
||||
test :filter do
|
||||
assert Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == [2]
|
||||
assert Enum.filter([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
|
||||
@@ -170,13 +165,22 @@ defmodule EnumTest.List do
|
||||
assert Enum.flat_map([1, 2, 3], fn(x) -> x..x+1 end) == [1, 2, 2, 3, 3, 4]
|
||||
end
|
||||
|
||||
test :flat_map_reduce do
|
||||
assert Enum.flat_map_reduce([1, 2, 3], 0, &{ [&1, &2], &1 + &2 }) ==
|
||||
{ [1, 0, 2, 1, 3, 3], 6 }
|
||||
|
||||
assert Enum.flat_map_reduce(1..100, 0, fn i, acc ->
|
||||
if acc < 3, do: { [i], acc + 1 }, else: { :halt, acc }
|
||||
end) == { [1,2,3], 3 }
|
||||
end
|
||||
|
||||
test :reduce do
|
||||
assert Enum.reduce([], 1, fn(x, acc) -> x + acc end) == 1
|
||||
assert Enum.reduce([1, 2, 3], 1, fn(x, acc) -> x + acc end) == 7
|
||||
|
||||
assert Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end) == 6
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.reduce([], fn(x, acc) -> x + acc end)
|
||||
Enum.reduce([], fn(x, acc) -> x + acc end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -327,7 +331,7 @@ defmodule EnumTest.List do
|
||||
assert Enum.max([1, 2, 3]) == 3
|
||||
assert Enum.max([1, [], :a, {}]) == []
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.max([])
|
||||
Enum.max([])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -343,7 +347,7 @@ defmodule EnumTest.List do
|
||||
assert Enum.min([1, 2, 3]) == 1
|
||||
assert Enum.min([[], :a, {}]) == :a
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.min([])
|
||||
Enum.min([])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -442,15 +446,15 @@ defmodule EnumTest.Range do
|
||||
assert Enum.fetch!(-2..-6, 4) == -6
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.fetch!(2..6, 8)
|
||||
Enum.fetch!(2..6, 8)
|
||||
end
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.fetch!(-2..-6, 8)
|
||||
Enum.fetch!(-2..-6, 8)
|
||||
end
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.fetch!(2..6, -8)
|
||||
Enum.fetch!(2..6, -8)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -521,14 +525,6 @@ defmodule EnumTest.Range do
|
||||
refute Enum.empty?(range)
|
||||
end
|
||||
|
||||
test :first do
|
||||
range = 1..0
|
||||
assert Enum.first(range) == 1
|
||||
|
||||
range = 1..2
|
||||
assert Enum.first(range) == 1
|
||||
end
|
||||
|
||||
test :each do
|
||||
try do
|
||||
range = 1..0
|
||||
|
||||
@@ -6,14 +6,14 @@ defmodule Kernel.ExceptionTest do
|
||||
# Ensure fields passed through an expression are valid
|
||||
defexception Custom, %w(message)a
|
||||
|
||||
test :is_exception do
|
||||
test "is_exception" do
|
||||
assert is_exception(RuntimeError.new)
|
||||
refute is_exception(empty_tuple)
|
||||
refute is_exception(a_tuple)
|
||||
refute is_exception(a_list)
|
||||
end
|
||||
|
||||
test :normalize do
|
||||
test "normalize" do
|
||||
assert is_record Exception.normalize(:badarg), ArgumentError
|
||||
assert is_record Exception.normalize(ArgumentError[]), ArgumentError
|
||||
|
||||
@@ -22,35 +22,36 @@ defmodule Kernel.ExceptionTest do
|
||||
assert is_record Exception.normalize(:error, ArgumentError[]), ArgumentError
|
||||
end
|
||||
|
||||
test :format_stacktrace_entry_with_no_file_or_line do
|
||||
test "format_stacktrace_entry with no file or line" do
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], []}) == "Foo.bar()"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, 1, []}) == "Foo.bar/1"
|
||||
end
|
||||
|
||||
test :format_stacktrace_entry_with_file_and_line do
|
||||
test "format_stacktrace_entry with file and line" do
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar()"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, 1, [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar/1"
|
||||
end
|
||||
|
||||
test :format_stacktrace_entry_with_file_and_line_and_cwd do
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: '/foo/file.ex', line: 10]}, "/foo") == "file.ex:10: Foo.bar()"
|
||||
end
|
||||
|
||||
test :format_stacktrace_entry_with_file_no_line do
|
||||
test "format_stacktrace_entry with file no line" do
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex']}) == "file.ex: Foo.bar()"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex', line: 0]}) == "file.ex: Foo.bar()"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex']}) == "file.ex: Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, 1, [file: 'file.ex']}) == "file.ex: Foo.bar/1"
|
||||
end
|
||||
|
||||
test :format_stacktrace_entry_with_fun do
|
||||
test "format_stacktrace_entry with application" do
|
||||
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex']}) == "(elixir) file.ex: Exception.bar()"
|
||||
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex', line: 10]}) == "(elixir) file.ex:10: Exception.bar()"
|
||||
assert Exception.format_stacktrace_entry({:lists, :bar, [1, 2, 3], []}) == "(stdlib) :lists.bar(1, 2, 3)" end
|
||||
|
||||
test "format_stacktrace_entry with fun" do
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
|
||||
end
|
||||
|
||||
test :format_mfa do
|
||||
test "format_mfa" do
|
||||
assert Exception.format_mfa(Foo, nil, 1) == "Foo.nil/1"
|
||||
assert Exception.format_mfa(Foo, :bar, 1) == "Foo.bar/1"
|
||||
assert Exception.format_mfa(Foo, :bar, []) == "Foo.bar()"
|
||||
@@ -59,37 +60,37 @@ defmodule Kernel.ExceptionTest do
|
||||
assert Exception.format_mfa(Foo, :"bar baz", 1) == "Foo.\"bar baz\"/1"
|
||||
end
|
||||
|
||||
test :format_fa do
|
||||
test "format_fa" do
|
||||
assert Exception.format_fa(fn -> end, 1) =~
|
||||
%r"#Function<\d\.\d+ in Kernel\.ExceptionTest\.test_format_fa/1>/1"
|
||||
%r"#Function<\d\.\d+ in Kernel\.ExceptionTest\.test format_fa/1>/1"
|
||||
end
|
||||
|
||||
test :runtime_error_message do
|
||||
test "runtime error message" do
|
||||
assert RuntimeError.new.message == "runtime error"
|
||||
assert RuntimeError.new(message: "exception").message == "exception"
|
||||
end
|
||||
|
||||
test :argument_error_message do
|
||||
test "argument error message" do
|
||||
assert ArgumentError.new.message == "argument error"
|
||||
assert ArgumentError.new(message: "exception").message == "exception"
|
||||
end
|
||||
|
||||
test :undefined_function_message do
|
||||
test "undefined function message" do
|
||||
assert UndefinedFunctionError.new.message == "undefined function"
|
||||
assert UndefinedFunctionError.new(module: Foo, function: :bar, arity: 1).message == "undefined function: Foo.bar/1"
|
||||
assert UndefinedFunctionError.new(module: nil, function: :bar, arity: 0).message == "undefined function: nil.bar/0"
|
||||
end
|
||||
|
||||
test :function_clause_message do
|
||||
test "function clause message" do
|
||||
assert FunctionClauseError.new.message == "no function clause matches"
|
||||
assert FunctionClauseError.new(module: Foo, function: :bar, arity: 1).message == "no function clause matching in Foo.bar/1"
|
||||
end
|
||||
|
||||
test :erlang_error_message do
|
||||
test "erlang error message" do
|
||||
assert ErlangError.new(original: :sample).message == "erlang error: :sample"
|
||||
end
|
||||
|
||||
test :raise_preserves_the_stacktrace do
|
||||
test "raise preserves the stacktrace" do
|
||||
stacktrace =
|
||||
try do
|
||||
raise "a"
|
||||
@@ -97,12 +98,13 @@ defmodule Kernel.ExceptionTest do
|
||||
[top|_] = System.stacktrace
|
||||
top
|
||||
end
|
||||
file = to_char_list(__FILE__)
|
||||
assert {Kernel.ExceptionTest, :test_raise_preserves_the_stacktrace, _,
|
||||
[file: ^file, line: 95]} = stacktrace # line is sensitive
|
||||
|
||||
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
|
||||
assert {Kernel.ExceptionTest, :"test raise preserves the stacktrace", _,
|
||||
[file: ^file, line: 96]} = stacktrace
|
||||
end
|
||||
|
||||
test :defexception do
|
||||
test "defexception" do
|
||||
defexception SampleError, message: nil do
|
||||
# Check do block is properly inline
|
||||
def exception(_), do: SampleError[message: "hello"]
|
||||
|
||||
@@ -232,7 +232,7 @@ defmodule FileTest do
|
||||
|
||||
try do
|
||||
File.touch!(dest)
|
||||
assert (File.cp_r(src, dest) |> is_io_error?)
|
||||
assert (File.cp_r(src, dest) |> io_error?)
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
@@ -271,7 +271,7 @@ defmodule FileTest do
|
||||
try do
|
||||
File.mkdir(dest)
|
||||
File.write!(Path.join(dest, "a"), "hello")
|
||||
assert (File.cp_r(src, dest) |> is_io_error?)
|
||||
assert (File.cp_r(src, dest) |> io_error?)
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
@@ -382,7 +382,7 @@ defmodule FileTest do
|
||||
assert src_mode == dest_mode
|
||||
end
|
||||
|
||||
defp is_io_error?(result) do
|
||||
defp io_error?(result) do
|
||||
elem(result, 1) in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
end
|
||||
@@ -391,13 +391,13 @@ defmodule FileTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test :regular do
|
||||
assert File.regular?(__FILE__)
|
||||
assert File.regular?(String.to_char_list!(__FILE__))
|
||||
refute File.regular?("#{__FILE__}.unknown")
|
||||
assert File.regular?(__ENV__.file)
|
||||
assert File.regular?(String.to_char_list!(__ENV__.file))
|
||||
refute File.regular?("#{__ENV__.file}.unknown")
|
||||
end
|
||||
|
||||
test :exists do
|
||||
assert File.exists?(__FILE__)
|
||||
assert File.exists?(__ENV__.file)
|
||||
assert File.exists?(fixture_path)
|
||||
assert File.exists?(fixture_path("file.txt"))
|
||||
|
||||
@@ -433,12 +433,12 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :read_with_list do
|
||||
assert { :ok, "FOO\n" } = File.read(Path.expand('../fixtures/file.txt', __FILE__))
|
||||
assert { :error, :enoent } = File.read(Path.expand('../fixtures/missing.txt', __FILE__))
|
||||
assert { :ok, "FOO\n" } = File.read(Path.expand('fixtures/file.txt', __DIR__))
|
||||
assert { :error, :enoent } = File.read(Path.expand('fixtures/missing.txt', __DIR__))
|
||||
end
|
||||
|
||||
test :read_with_utf8 do
|
||||
assert { :ok, "Русский\n日\n" } = File.read(Path.expand('../fixtures/utf8.txt', __FILE__))
|
||||
assert { :ok, "Русский\n日\n" } = File.read(Path.expand('fixtures/utf8.txt', __DIR__))
|
||||
end
|
||||
|
||||
test :read! do
|
||||
@@ -595,7 +595,7 @@ defmodule FileTest do
|
||||
fixture = fixture_path("file.txt")
|
||||
invalid = Path.join fixture, "test"
|
||||
assert File.exists?(fixture)
|
||||
assert (File.mkdir(invalid) |> is_io_error?)
|
||||
assert (File.mkdir(invalid) |> io_error?)
|
||||
refute File.exists?(invalid)
|
||||
end
|
||||
|
||||
@@ -676,7 +676,7 @@ defmodule FileTest do
|
||||
test :mkdir_p_with_invalid_path do
|
||||
assert File.exists?(fixture_path("file.txt"))
|
||||
invalid = Path.join fixture_path("file.txt"), "test/foo"
|
||||
assert (File.mkdir(invalid) |> is_io_error?)
|
||||
assert (File.mkdir(invalid) |> io_error?)
|
||||
refute File.exists?(invalid)
|
||||
end
|
||||
|
||||
@@ -700,7 +700,7 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
defp is_io_error?(result) do
|
||||
defp io_error?(result) do
|
||||
{:error,errorcode} = result
|
||||
errorcode in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
@@ -749,7 +749,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :rmdir_with_file do
|
||||
assert (File.rmdir(fixture_path("file.txt")) |> is_io_error?)
|
||||
assert (File.rmdir(fixture_path("file.txt")) |> io_error?)
|
||||
end
|
||||
|
||||
test :rmdir! do
|
||||
@@ -869,18 +869,18 @@ defmodule FileTest do
|
||||
assert File.rm_rf!(fixture) == []
|
||||
end
|
||||
|
||||
defp is_io_error?(result) do
|
||||
defp io_error?(result) do
|
||||
elem(result, 1) in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
end
|
||||
|
||||
test :stat do
|
||||
{:ok, info} = File.stat(__FILE__)
|
||||
{:ok, info} = File.stat(__ENV__.file)
|
||||
assert info.mtime
|
||||
end
|
||||
|
||||
test :stat! do
|
||||
assert File.stat!(__FILE__).mtime
|
||||
assert File.stat!(__ENV__.file).mtime
|
||||
end
|
||||
|
||||
test :stat_with_invalid_file do
|
||||
@@ -1131,7 +1131,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :invalid_cd do
|
||||
assert(File.cd(fixture_path("file.txt")) |> is_io_error?)
|
||||
assert(File.cd(fixture_path("file.txt")) |> io_error?)
|
||||
end
|
||||
|
||||
test :invalid_cd! do
|
||||
@@ -1182,7 +1182,7 @@ defmodule FileTest do
|
||||
|
||||
test :touch_with_failure do
|
||||
fixture = fixture_path("file.txt/bar")
|
||||
assert (File.touch(fixture) |> is_io_error?)
|
||||
assert (File.touch(fixture) |> io_error?)
|
||||
end
|
||||
|
||||
test :touch_with_success! do
|
||||
@@ -1293,7 +1293,7 @@ defmodule FileTest do
|
||||
{ { year - 1, month, day }, time }
|
||||
end
|
||||
|
||||
defp is_io_error?(result) do
|
||||
defp io_error?(result) do
|
||||
{:error,errorcode} = result
|
||||
errorcode in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
defmodule Bat do
|
||||
ThisModuleWillNeverBeAvailable[]
|
||||
end
|
||||
@@ -6,6 +6,7 @@ defmodule FloatTest do
|
||||
test :parse do
|
||||
assert Float.parse("12") === {12.0, ""}
|
||||
assert Float.parse("-12") === {-12.0, ""}
|
||||
assert Float.parse("-0.1") === {-0.1, ""}
|
||||
assert Float.parse("123456789") === {123456789.0, ""}
|
||||
assert Float.parse("12.5") === {12.5, ""}
|
||||
assert Float.parse("12.524235") === {12.524235, ""}
|
||||
@@ -60,14 +61,10 @@ defmodule FloatTest do
|
||||
|
||||
test :round do
|
||||
assert Float.round(5.5675, 3) === 5.568
|
||||
assert Float.round(5.5675, 3, :down) === 5.567
|
||||
assert Float.round(-5.5674, 3) === -5.567
|
||||
assert Float.round(5.5, 3) === 5.5
|
||||
assert Float.round(5.5e-10, 10) === 6.0e-10
|
||||
assert Float.round(5.5e-10, 10, :down) === 5.0e-10
|
||||
assert Float.round(5.5e-10, 8) === 1.0e-8
|
||||
assert Float.round(5.5e-10, 8, :down) === 0.0
|
||||
assert Float.round(5.5e-10, 8) === 0.0
|
||||
assert Float.round(5.0, 0) === 5.0
|
||||
assert Float.round(-1.3456, 3) === -1.345
|
||||
assert Float.round(-1.3456, 3, :down) === -1.346
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,10 +19,9 @@ defmodule GenEvent.BehaviourTest do
|
||||
def handle_call(:notifications, notifications) do
|
||||
{:ok, Enum.reverse(notifications), []}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
test :using do
|
||||
test "using defines callbacks" do
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
|
||||
@@ -32,5 +31,4 @@ defmodule GenEvent.BehaviourTest do
|
||||
assert :gen_event.call(pid, MyEventHandler, :notifications) == [1, 2]
|
||||
assert :gen_event.call(pid, MyEventHandler, :notifications) == []
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenFSM.BehaviourTest do
|
||||
use ExUnit.Case
|
||||
|
||||
setup_all do
|
||||
:error_logger.tty(false)
|
||||
:ok
|
||||
end
|
||||
|
||||
teardown_all do
|
||||
:error_logger.tty(true)
|
||||
:ok
|
||||
end
|
||||
|
||||
defmodule Sample do
|
||||
use GenFSM.Behaviour
|
||||
|
||||
def init(args) do
|
||||
{ :ok, :sample, args }
|
||||
end
|
||||
end
|
||||
|
||||
test "sync event stops server on unknown requests" do
|
||||
Process.flag(:trap_exit, true)
|
||||
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
|
||||
|
||||
catch_exit(:gen_fsm.sync_send_all_state_event(pid, :unknown_request))
|
||||
assert_receive { :EXIT, ^pid, {:bad_sync_event, :sample, :unknown_request} }
|
||||
after
|
||||
Process.flag(:trap_exit, false)
|
||||
end
|
||||
|
||||
test "event stops server on unknown requests" do
|
||||
Process.flag(:trap_exit, true)
|
||||
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
|
||||
|
||||
:gen_fsm.send_all_state_event(pid, :unknown_request)
|
||||
assert_receive { :EXIT, ^pid, {:bad_event, :sample, :unknown_request} }
|
||||
after
|
||||
Process.flag(:trap_exit, false)
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,17 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenServer.BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case
|
||||
|
||||
setup_all do
|
||||
:error_logger.tty(false)
|
||||
:ok
|
||||
end
|
||||
|
||||
teardown_all do
|
||||
:error_logger.tty(true)
|
||||
:ok
|
||||
end
|
||||
|
||||
defmodule Sample do
|
||||
use GenServer.Behaviour
|
||||
@@ -29,10 +39,30 @@ defmodule GenServer.BehaviourTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :using do
|
||||
test "using defines callbacks" do
|
||||
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
|
||||
assert :gen_server.call(pid, :pop) == :hello
|
||||
assert :gen_server.cast(pid, { :push, :world }) == :ok
|
||||
assert :gen_server.call(pid, :pop) == :world
|
||||
end
|
||||
|
||||
test "call stops server on unknown requests" do
|
||||
Process.flag(:trap_exit, true)
|
||||
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
|
||||
|
||||
catch_exit(:gen_server.call(pid, :unknown_request))
|
||||
assert_receive { :EXIT, ^pid, {:bad_call, :unknown_request} }
|
||||
after
|
||||
Process.flag(:trap_exit, false)
|
||||
end
|
||||
|
||||
test "cast stops server on unknown requests" do
|
||||
Process.flag(:trap_exit, true)
|
||||
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
|
||||
|
||||
:gen_server.cast(pid, :unknown_request)
|
||||
assert_receive { :EXIT, ^pid, {:bad_cast, :unknown_request} }
|
||||
after
|
||||
Process.flag(:trap_exit, false)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,7 +44,6 @@ defmodule Inspect.AtomTest do
|
||||
|
||||
test :container do
|
||||
assert inspect(:<<>>) == ":<<>>"
|
||||
assert inspect(:[]) == ":[]"
|
||||
assert inspect(:{}) == ":{}"
|
||||
end
|
||||
end
|
||||
@@ -71,8 +70,22 @@ defmodule Inspect.BitStringTest do
|
||||
assert inspect(" ゆんゆん") == "\" ゆんゆん\""
|
||||
end
|
||||
|
||||
test :unprintable do
|
||||
assert inspect(<<193>>) == "<<193>>"
|
||||
test :opt_infer do
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :infer) == %s(<<101, 114, 105, 99, 193, 109, 106>>)
|
||||
assert inspect(<<"eric">>, binaries: :infer) == %s("eric")
|
||||
assert inspect(<<193>>, binaries: :infer) == %s(<<193>>)
|
||||
end
|
||||
|
||||
test :opt_as_strings do
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :as_strings) == %s("eric\\301mj")
|
||||
assert inspect(<<"eric">>, binaries: :as_strings) == %s("eric")
|
||||
assert inspect(<<193>>, binaries: :as_strings) == %s("\\301")
|
||||
end
|
||||
|
||||
test :opt_as_binaries do
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :as_binaries) == "<<101, 114, 105, 99, 193, 109, 106>>"
|
||||
assert inspect(<<"eric">>, binaries: :as_binaries) == "<<101, 114, 105, 99>>"
|
||||
assert inspect(<<193>>, binaries: :as_binaries) == "<<193>>"
|
||||
end
|
||||
|
||||
test :unprintable_with_opts do
|
||||
@@ -154,8 +167,8 @@ defmodule Inspect.TupleTest do
|
||||
assert inspect({ 1, 2, 3, 4 }, limit: 3) == "{1, 2, 3, ...}"
|
||||
end
|
||||
|
||||
test :with_raw do
|
||||
assert inspect(Config.new, raw: true) == "{Inspect.TupleTest.Config, 1, []}"
|
||||
test :with_records_false do
|
||||
assert inspect(Config.new, records: false) == "{Inspect.TupleTest.Config, 1, []}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -181,16 +194,28 @@ defmodule Inspect.ListTest do
|
||||
"[foo: [1, 2, 3, :bar],\n bazzz: :bat]"
|
||||
end
|
||||
|
||||
test :keyword_with_raw do
|
||||
assert inspect([a: 1], raw: true) == "[{:a, 1}]"
|
||||
assert inspect([a: 1, b: 2], raw: true) == "[{:a, 1}, {:b, 2}]"
|
||||
assert inspect([a: 1, a: 2, b: 2], raw: true) == "[{:a, 1}, {:a, 2}, {:b, 2}]"
|
||||
end
|
||||
|
||||
test :non_keyword do
|
||||
assert inspect([{ Regex, 1 }]) == "[{Regex, 1}]"
|
||||
end
|
||||
|
||||
test :opt_infer do
|
||||
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :infer) == "[101, 114, 105, 99, 0, 109, 106]"
|
||||
assert inspect('eric', char_lists: :infer) == "'eric'"
|
||||
assert inspect([0], char_lists: :infer) == "[0]"
|
||||
end
|
||||
|
||||
test :opt_as_strings do
|
||||
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :as_char_lists) == "'eric\\000mj'"
|
||||
assert inspect('eric', char_lists: :as_char_lists) == "'eric'"
|
||||
assert inspect([0], char_lists: :as_char_lists) == "'\\000'"
|
||||
end
|
||||
|
||||
test :opt_as_lists do
|
||||
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :as_lists) == "[101, 114, 105, 99, 0, 109, 106]"
|
||||
assert inspect('eric', char_lists: :as_lists) == "[101, 114, 105, 99]"
|
||||
assert inspect([0], char_lists: :as_lists) == "[0]"
|
||||
end
|
||||
|
||||
test :non_printable do
|
||||
assert inspect([{:b, 1}, {:a, 1}]) == "[b: 1, a: 1]"
|
||||
end
|
||||
|
||||
+10
-12
@@ -1,7 +1,8 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
Code.require_file "../../test_helper.exs", __DIR__
|
||||
|
||||
defmodule IEx.AnsiDocsTest do
|
||||
use IEx.Case
|
||||
defmodule IO.ANSI.DocsTest do
|
||||
use ExUnit.Case, async: true
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
@colors [ enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
@@ -11,19 +12,16 @@ defmodule IEx.AnsiDocsTest do
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright" ]
|
||||
|
||||
@opts [colors: @colors]
|
||||
|
||||
def format(str, use_ansi // true) do
|
||||
cmd = "IEx.ANSIDocs.print(#{inspect str}, #{inspect use_ansi})"
|
||||
capture_iex(cmd, @opts)
|
||||
def format_heading(str) do
|
||||
capture_io(fn -> IO.ANSI.Docs.print_heading(str, @colors) end) |> String.strip
|
||||
end
|
||||
|
||||
test "non-ansi heading just uses an asterisk" do
|
||||
assert capture_iex("IEx.ANSIDocs.print_heading(\"wibble\", false)", []) == "* wibble"
|
||||
def format(str) do
|
||||
capture_io(fn -> IO.ANSI.Docs.print(str, @colors) end) |> String.strip
|
||||
end
|
||||
|
||||
test "ansi heading is formatted" do
|
||||
result = capture_iex("IEx.ANSIDocs.print_heading(\"wibble\", true)", @opts)
|
||||
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.ends_with?(result, "\e[0m\n\e[0m")
|
||||
assert String.contains?(result, " wibble ")
|
||||
@@ -5,25 +5,25 @@ defmodule IOTest do
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
test :read_with_count do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__), [:charlist])
|
||||
assert 'FOO' == IO.read(file, 3)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :read_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
|
||||
assert "Русский" == IO.read(file, 7)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :binread do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__))
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
|
||||
assert "Русский" == IO.binread(file, 14)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :getn do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__))
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__))
|
||||
assert "F" == IO.getn(file, "")
|
||||
assert "O" == IO.getn(file, "")
|
||||
assert "O" == IO.getn(file, "")
|
||||
@@ -33,47 +33,47 @@ defmodule IOTest do
|
||||
end
|
||||
|
||||
test :getn_with_count do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__), [:charlist])
|
||||
assert 'FOO' == IO.getn(file, "", 3)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :getn_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
|
||||
assert "Русский" == IO.getn(file, "", 7)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :gets do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__), [:charlist])
|
||||
assert 'FOO\n' == IO.gets(file, "")
|
||||
assert :eof == IO.gets(file, "")
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :gets_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
|
||||
assert "Русский\n" == IO.gets(file, "")
|
||||
assert "日\n" == IO.gets(file, "")
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :readline do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__))
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/file.txt', __DIR__))
|
||||
assert "FOO\n" == IO.read(file, :line)
|
||||
assert :eof == IO.read(file, :line)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :readline_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
|
||||
assert "Русский\n" == IO.read(file, :line)
|
||||
assert "日\n" == IO.read(file, :line)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :binreadline do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__))
|
||||
{ :ok, file } = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
|
||||
assert "Русский\n" == IO.binread(file, :line)
|
||||
assert "日\n" == IO.binread(file, :line)
|
||||
assert File.close(file) == :ok
|
||||
|
||||
@@ -17,27 +17,19 @@ bar """
|
||||
"""
|
||||
end
|
||||
|
||||
test :heredoc_with_extra do
|
||||
assert 21 == __ENV__.line
|
||||
assert "foo\nbar\nbar\n" == """ <> "bar\n"
|
||||
foo
|
||||
bar
|
||||
"""
|
||||
end
|
||||
|
||||
test :aligned_heredoc do
|
||||
assert "foo\nbar\nbar\n" == """ <> "bar\n"
|
||||
assert "foo\nbar\n" == """
|
||||
foo
|
||||
bar
|
||||
"""
|
||||
end
|
||||
|
||||
test :heredoc_with_interpolation do
|
||||
assert "37\n" == """
|
||||
assert "29\n" == """
|
||||
#{__ENV__.line}
|
||||
"""
|
||||
|
||||
assert "\n42\n" == """
|
||||
assert "\n34\n" == """
|
||||
|
||||
#{__ENV__.line}
|
||||
"""
|
||||
@@ -107,44 +99,39 @@ bar
|
||||
end
|
||||
|
||||
test :literal do
|
||||
assert <<106, 111, 115, 101>> == << "jose" :: binary >>
|
||||
assert <<106, 111, 115, 101>> == << "jose" :: bits >>
|
||||
assert <<106, 111, 115, 101>> == << "jose" :: bitstring >>
|
||||
assert <<106, 111, 115, 101>> == << "jose" :: bytes >>
|
||||
assert <<106,111,115,195,169>> == << "josé" :: binary >>
|
||||
assert <<106,111,115,195,169>> == << "josé" :: bits >>
|
||||
assert <<106,111,115,195,169>> == << "josé" :: bitstring >>
|
||||
assert <<106,111,115,195,169>> == << "josé" :: bytes >>
|
||||
|
||||
assert <<106, 111, 115, 101>> == << "jose" :: utf8 >>
|
||||
assert <<106, 111, 115, 101>> == << 'jose' :: utf8 >>
|
||||
|
||||
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << "jose" :: utf16 >>
|
||||
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << 'jose' :: utf16 >>
|
||||
|
||||
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << "jose" :: [utf16, little] >>
|
||||
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << 'jose' :: [utf16, little] >>
|
||||
|
||||
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << "jose" :: utf32 >>
|
||||
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << 'jose' :: utf32 >>
|
||||
assert <<106,111,115,195,169>> == << "josé" :: utf8 >>
|
||||
assert <<0,106,0,111,0,115,0,233>> == << "josé" :: utf16 >>
|
||||
assert <<106,0,111,0,115,0,233,0>> == << "josé" :: [utf16, little] >>
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,233>> == << "josé" :: utf32 >>
|
||||
end
|
||||
|
||||
test :literal_errors do
|
||||
assert_raise ArgumentError, fn ->
|
||||
assert_raise CompileError, fn ->
|
||||
Code.eval_string(%s[<< "foo" :: integer >>])
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
assert_raise CompileError, fn ->
|
||||
Code.eval_string(%s[<< "foo" :: float >>])
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
assert_raise CompileError, fn ->
|
||||
Code.eval_string(%s[<< 'foo' :: binary >>])
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
Code.eval_string(%s[<<1::size(4)>> <> "foo"])
|
||||
end
|
||||
end
|
||||
|
||||
@binary "new "
|
||||
@charlist 'old '
|
||||
@binary "new "
|
||||
|
||||
test :bitsyntax_with_expansion do
|
||||
assert <<@binary, "world">> == "new world"
|
||||
assert <<@charlist, "world">> == "old world"
|
||||
end
|
||||
|
||||
test :bitsyntax_translation do
|
||||
@@ -178,7 +165,7 @@ bar
|
||||
sec_data = "another"
|
||||
<< size(refb) :: refb_spec,
|
||||
refb :: binary,
|
||||
size(sec_data) :: [size(1) | signed_16],
|
||||
size(sec_data) :: [size(1), signed_16],
|
||||
sec_data :: binary >>
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,10 +7,6 @@ defmodule Kernel.CaseTest do
|
||||
assert (case 1, do: (1 -> :ok; 2 -> :wrong)) == :ok
|
||||
end
|
||||
|
||||
test :nested_case do
|
||||
assert get_case == 2
|
||||
end
|
||||
|
||||
test :nested_variables do
|
||||
assert vars_case(400, 1) == { 400, 1 }
|
||||
assert vars_case(401, 1) == { 400, -1 }
|
||||
@@ -41,27 +37,6 @@ defmodule Kernel.CaseTest do
|
||||
refute 1.0 in [1, 2, 3], "not in assertion"
|
||||
end
|
||||
|
||||
defp get_case do
|
||||
case internal do
|
||||
:invalid ->
|
||||
status = :fail
|
||||
1 ->
|
||||
case other_internal do
|
||||
status ->
|
||||
status
|
||||
end
|
||||
end
|
||||
status
|
||||
end
|
||||
|
||||
defp internal do
|
||||
1
|
||||
end
|
||||
|
||||
defp other_internal do
|
||||
2
|
||||
end
|
||||
|
||||
defp vars_case(x, vx) do
|
||||
case x > 400 do
|
||||
true ->
|
||||
|
||||
@@ -5,7 +5,7 @@ import PathHelpers
|
||||
defmodule Kernel.CLI.InitTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :code_init do
|
||||
test "handles code on initialization" do
|
||||
assert elixir('-e "IO.puts [?3]"') == '3\n'
|
||||
|
||||
result = elixir('-e "IO.puts inspect(System.argv)" #{fixture_path("init_sample.exs")} -o 1 2 3')
|
||||
@@ -16,7 +16,7 @@ end
|
||||
defmodule Kernel.CLI.OptionParsingTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :path do
|
||||
test "properly parses paths" do
|
||||
root = fixture_path("../../..") |> to_char_list
|
||||
list = elixir('-pa "#{root}/*" -pz "#{root}/lib/*" -e "IO.inspect(:code.get_path, limit: :infinity)"')
|
||||
{ path, _ } = Code.eval_string list, []
|
||||
@@ -34,7 +34,7 @@ end
|
||||
defmodule Kernel.CLI.AtExitTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :at_exit do
|
||||
test "invokes at_exit callbacks" do
|
||||
assert elixir(fixture_path("at_exit.exs") |> to_char_list) ==
|
||||
'goodbye cruel world with status 0\n'
|
||||
end
|
||||
@@ -43,7 +43,7 @@ end
|
||||
defmodule Kernel.CLI.ErrorTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :code_error do
|
||||
test "properly format errors" do
|
||||
assert :string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
|
||||
assert :string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
|
||||
|
||||
@@ -52,24 +52,10 @@ defmodule Kernel.CLI.ErrorTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.CLI.SyntaxErrorTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defp check_output(elixir_cmd, expected_msg) do
|
||||
o = elixir(elixir_cmd)
|
||||
assert :string.str(o, expected_msg) == 1, "Expected this output: `#{expected_msg}`\nbut got this output: `#{o}`"
|
||||
end
|
||||
|
||||
test :syntax_code_error do
|
||||
check_output('-e "[1,2"', '** (TokenMissingError) nofile:1: missing terminator: ]')
|
||||
check_output('-e "case 1 end"', %C"** (SyntaxError) nofile:1: unexpected token: end")
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.CLI.CompileTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :compile_code do
|
||||
test "compiles code" do
|
||||
fixture = fixture_path "compile_sample.ex"
|
||||
assert elixirc('#{fixture} -o #{tmp_path}') == ''
|
||||
assert File.regular?(tmp_path "Elixir.CompileSample.beam")
|
||||
@@ -77,7 +63,7 @@ defmodule Kernel.CLI.CompileTest do
|
||||
File.rm(tmp_path("Elixir.CompileSample.beam"))
|
||||
end
|
||||
|
||||
test :compile_code_verbose do
|
||||
test "compiles code with verbose mode" do
|
||||
fixture = fixture_path "compile_sample.ex"
|
||||
assert elixirc('#{fixture} -o #{tmp_path} --verbose') ==
|
||||
'Compiled #{fixture}\n'
|
||||
@@ -86,13 +72,12 @@ defmodule Kernel.CLI.CompileTest do
|
||||
File.rm(tmp_path("Elixir.CompileSample.beam"))
|
||||
end
|
||||
|
||||
test :possible_deadlock do
|
||||
output = elixirc('#{fixture_path("parallel_deadlock")} -o #{tmp_path}')
|
||||
foo = '* #{fixture_path "parallel_deadlock/foo.ex"} is missing module Bar'
|
||||
bar = '* #{fixture_path "parallel_deadlock/bar.ex"} is missing module Foo'
|
||||
assert :string.str(output, foo) > 0, "expected foo.ex to miss module Bar"
|
||||
assert :string.str(output, bar) > 0, "expected bar.ex to miss module Foo"
|
||||
assert :string.str(output, 'elixir_compiler') == 0, "expected elixir_compiler to not be in output"
|
||||
test "fails on missing patterns" do
|
||||
fixture = fixture_path "compile_sample.ex"
|
||||
output = elixirc('#{fixture} non_existing.ex -o #{tmp_path}')
|
||||
assert :string.str(output, 'non_existing.ex') > 0, "expected non_existing.ex to be mentionned"
|
||||
assert :string.str(output, 'compile_sample.ex') == 0, "expected compile_sample.ex to not be mentionned"
|
||||
refute File.exists?(tmp_path("Elixir.CompileSample.beam")) , "expected the sample to not be compiled"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -100,14 +85,36 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
||||
use ExUnit.Case
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
test :files do
|
||||
test "compiles files solving dependencies" do
|
||||
fixtures = [fixture_path("parallel_compiler/bar.ex"), fixture_path("parallel_compiler/foo.ex")]
|
||||
assert capture_io(fn ->
|
||||
assert [Bar, Foo] = Kernel.ParallelCompiler.files fixtures
|
||||
end) =~ "message_from_foo"
|
||||
end
|
||||
|
||||
test :warnings_as_errors do
|
||||
test "does not hang on missing dependencies" do
|
||||
fixtures = [fixture_path("parallel_compiler/bat.ex")]
|
||||
assert capture_io(fn ->
|
||||
assert_raise CompileError, fn ->
|
||||
Kernel.ParallelCompiler.files fixtures
|
||||
end
|
||||
end) =~ "Compilation error"
|
||||
end
|
||||
|
||||
test "handles possible deadlocks" do
|
||||
fixtures = [fixture_path("parallel_deadlock/foo.ex"), fixture_path("parallel_deadlock/bar.ex")]
|
||||
|
||||
msg = capture_io(fn ->
|
||||
assert_raise UndefinedFunctionError, fn ->
|
||||
Kernel.ParallelCompiler.files fixtures
|
||||
end
|
||||
end)
|
||||
|
||||
assert msg =~ "* #{fixture_path "parallel_deadlock/foo.ex"} is missing module Bar"
|
||||
assert msg =~ "* #{fixture_path "parallel_deadlock/bar.ex"} is missing module Foo"
|
||||
end
|
||||
|
||||
test "warnings_as_errors" do
|
||||
warnings_as_errors = Code.compiler_options[:warnings_as_errors]
|
||||
|
||||
try do
|
||||
|
||||
@@ -17,6 +17,12 @@ defmodule Kernel.ErrorsTest do
|
||||
'\end\nlol\nbarbecue'
|
||||
end
|
||||
|
||||
test :invalid_or_reserved_codepoint do
|
||||
assert_compile_fail ArgumentError,
|
||||
"invalid or reserved unicode codepoint 55296",
|
||||
'?\\x{D800}'
|
||||
end
|
||||
|
||||
test :sigil_terminator do
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)",
|
||||
@@ -61,10 +67,10 @@ defmodule Kernel.ErrorsTest do
|
||||
'+.foo'
|
||||
end
|
||||
|
||||
test :syntax_error_on_op_ambiguity do
|
||||
test :compile_error_on_op_ambiguity do
|
||||
msg = "nofile:1: \"a -1\" looks like a function call but there is a variable named \"a\", " <>
|
||||
"please use explicit parenthesis or even spaces"
|
||||
assert_compile_fail SyntaxError, msg, 'a = 1; a -1'
|
||||
assert_compile_fail CompileError, msg, 'a = 1; a -1'
|
||||
|
||||
max = 1
|
||||
assert max == 1
|
||||
@@ -119,7 +125,7 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :invalid_match_pattern do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: invalid pattern in match clause",
|
||||
"nofile:2: invalid expression in match",
|
||||
'''
|
||||
case true do
|
||||
true && true -> true
|
||||
@@ -170,9 +176,9 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :unbound_expr do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)",
|
||||
'^x(1)'
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid argument for unary operator ^, expected an existing variable, got: ^x(1)",
|
||||
'^x(1) = 1'
|
||||
end
|
||||
|
||||
test :name_for_defmodule do
|
||||
@@ -188,23 +194,23 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :invalid_quote_args do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: invalid args for quote",
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid arguments for quote",
|
||||
'quote 1'
|
||||
end
|
||||
|
||||
test :invalid_calls do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid call foo(1)(2)",
|
||||
'foo(1)(2)'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: invalid remote call on 1",
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid call 1.foo()",
|
||||
'1.foo'
|
||||
end
|
||||
|
||||
test :unhandled_stab do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:3: unhandled operator ->",
|
||||
'''
|
||||
defmodule Mod do
|
||||
@@ -217,8 +223,8 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :undefined_non_local_function do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: function casea/2 undefined",
|
||||
'casea foo, do: (bar -> baz)'
|
||||
"nofile:1: undefined function casea/2",
|
||||
'casea foo, do: 1'
|
||||
end
|
||||
|
||||
test :invalid_fn_args do
|
||||
@@ -277,7 +283,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :function_definition_with_alias do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: function names should start with lowercase characters or underscore, invalid name Bar",
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
@@ -293,7 +299,7 @@ defmodule Kernel.ErrorsTest do
|
||||
"nofile:3: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
import :erlang
|
||||
import :erlang, warn: false
|
||||
def foo, do: exit(:test)
|
||||
end
|
||||
'''
|
||||
@@ -349,7 +355,7 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :invalid_macro do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile: tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: {:foo, :bar, :baz, :bat}",
|
||||
"nofile: invalid quoted expression: {:foo, :bar, :baz, :bat}",
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
defmacrop oops do
|
||||
@@ -385,7 +391,7 @@ defmodule Kernel.ErrorsTest do
|
||||
assert_compile_fail ArgumentError,
|
||||
"could not call eval_quoted on module Record " <>
|
||||
"because it was already compiled",
|
||||
'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__'
|
||||
'Module.eval_quoted Record, quote(do: 1), [], file: __ENV__.file'
|
||||
end
|
||||
|
||||
test :interpolation_error do
|
||||
@@ -401,36 +407,36 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :invalid_definition do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid syntax in def 1.(hello)",
|
||||
'defmodule ErrorsTest, do: (def 1.(hello), do: true)'
|
||||
end
|
||||
|
||||
test :duplicated_bitstring_size do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: duplicated size definition for bitstring",
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: duplicated size definition in bitstring",
|
||||
'<<1 :: [size(12), size(13)]>>'
|
||||
end
|
||||
|
||||
test :invalid_bitstring_specified do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: unknown bitstring specifier :atom",
|
||||
'<<1 :: :atom>>'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: unknown bitstring specifier unknown",
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: unknown bitstring specifier unknown()",
|
||||
'<<1 :: unknown>>'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: unknown bitstring specifier another(12)",
|
||||
'<<1 :: another(12)>>'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: size in bitstring expects an integer or a variable as argument",
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: size in bitstring expects an integer or a variable as argument, got: :a",
|
||||
'<<1 :: size(:a)>>'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: unit in bitstring expects an integer as argument",
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: unit in bitstring expects an integer as argument, got: :x",
|
||||
'<<1 :: unit(:x)>>'
|
||||
end
|
||||
|
||||
@@ -442,7 +448,7 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :invalid_alias do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid :as for alias, nested alias Sample.Lists not allowed",
|
||||
"nofile:1: invalid value for keyword :as, expected an alias, got nested alias: Sample.Lists",
|
||||
'alias :lists, as: Sample.Lists'
|
||||
end
|
||||
|
||||
@@ -471,7 +477,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_not_record do
|
||||
assert_raise ArgumentError, "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1", fn ->
|
||||
assert_raise ArgumentError, "cannot access module Kernel.ErrorsTest because it is not a record", fn ->
|
||||
defmodule ErrorsTest do
|
||||
def sample(Kernel.ErrorsTest[integer: 0]), do: true
|
||||
end
|
||||
@@ -487,33 +493,27 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_invalid_keywords do
|
||||
assert_raise ArgumentError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]", fn ->
|
||||
assert_raise ArgumentError, "record Kernel.ErrorsTest.Config does not have the key: :foo", fn ->
|
||||
defmodule ErrorsTest do
|
||||
def sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_invalid_keywords_outside_match do
|
||||
assert_compile_fail ArgumentError,
|
||||
"record Kernel.ErrorsTest.Config does not have the keys: [:foo]",
|
||||
'Kernel.ErrorsTest.Config[foo: :bar]'
|
||||
end
|
||||
|
||||
test :invalid_rescue_clause do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:4: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format",
|
||||
'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
|
||||
end
|
||||
|
||||
test :invalid_bc_return do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: a bit comprehension expects a bit string << >> to be returned",
|
||||
'bc x inlist [1, 2, 3], do: x'
|
||||
end
|
||||
|
||||
test :invalid_bc_inbits_gen do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators",
|
||||
'bc x inbits "123", do: <<x>>'
|
||||
end
|
||||
@@ -525,7 +525,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :fun_different_arities do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: cannot mix clauses with different arities in function definition",
|
||||
'fn x -> x; x, y -> x + y end'
|
||||
end
|
||||
@@ -548,7 +548,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :bodyless_function_with_guard do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: missing do keyword in def",
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
@@ -594,8 +594,8 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :bad_unquoting do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile: expected a valid quoted expression, got: {Range, 1, 3}",
|
||||
assert_compile_fail CompileError,
|
||||
"nofile: invalid quoted expression: {Range, 1, 3}",
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
def range(unquote(1..3)), do: :ok
|
||||
@@ -605,41 +605,41 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :macros_error_stacktrace do
|
||||
assert [{:erlang, :+, [1, :foo], _}, {ErrorsTest, :sample, 1, _}|_] =
|
||||
rescue_stacktrace(""")
|
||||
rescue_stacktrace("""
|
||||
defmodule ErrorsTest do
|
||||
defmacro sample(num), do: num + :foo
|
||||
def other, do: sample(1)
|
||||
end
|
||||
"""
|
||||
""")
|
||||
end
|
||||
|
||||
test :macros_function_clause_stacktrace do
|
||||
assert [{__MODULE__, :sample, 1, _}|_] =
|
||||
rescue_stacktrace(""")
|
||||
rescue_stacktrace("""
|
||||
defmodule ErrorsTest do
|
||||
import Kernel.ErrorsTest
|
||||
sample(1)
|
||||
end
|
||||
"""
|
||||
""")
|
||||
end
|
||||
|
||||
test :macros_interpreted_function_clause_stacktrace do
|
||||
assert [{ErrorsTest, :sample, 1, _}|_] =
|
||||
rescue_stacktrace(""")
|
||||
rescue_stacktrace("""
|
||||
defmodule ErrorsTest do
|
||||
defmacro sample(0), do: 0
|
||||
def other, do: sample(1)
|
||||
end
|
||||
"""
|
||||
""")
|
||||
end
|
||||
|
||||
test :macros_compiled_callback do
|
||||
assert [{Kernel.ErrorsTest, :__before_compile__, [Macro.Env[module: ErrorsTest]], _}|_] =
|
||||
rescue_stacktrace(""")
|
||||
rescue_stacktrace("""
|
||||
defmodule ErrorsTest do
|
||||
Module.put_attribute(__MODULE__, :before_compile, Kernel.ErrorsTest)
|
||||
end
|
||||
"""
|
||||
""")
|
||||
end
|
||||
|
||||
defmacro sample(0), do: 0
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.ExpansionTarget do
|
||||
defmacro seventeen, do: 17
|
||||
end
|
||||
|
||||
defmodule Kernel.ExpansionTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
## __block__
|
||||
|
||||
test "__block__: expands to nil when empty" do
|
||||
assert expand(quote do: __block__()) == nil
|
||||
end
|
||||
|
||||
test "__block__: expands to argument when arity is 1" do
|
||||
assert expand(quote do: __block__(1)) == 1
|
||||
end
|
||||
|
||||
test "__block__: is recursive to argument when arity is 1" do
|
||||
assert expand(quote do: __block__(1, __block__(2))) == quote do: __block__(1, 2)
|
||||
end
|
||||
|
||||
test "__block__: accumulates vars" do
|
||||
assert expand(quote(do: (a = 1; a))) == quote do: (a = 1; a)
|
||||
end
|
||||
|
||||
## alias
|
||||
|
||||
test "alias: expand args, defines alias and returns itself" do
|
||||
alias true, as: True
|
||||
|
||||
input = quote do: (alias :hello, as: World, warn: True)
|
||||
{ output, env } = expand_env(input, __ENV__)
|
||||
|
||||
assert output == quote do: (alias :hello, as: :"Elixir.World", warn: true)
|
||||
assert env.aliases == [{:"Elixir.True", true}, {:"Elixir.World", :hello}]
|
||||
end
|
||||
|
||||
## __aliases__
|
||||
|
||||
test "__aliases__: expands even if no alias" do
|
||||
assert expand(quote do: World) == :"Elixir.World"
|
||||
assert expand(quote do: Elixir.World) == :"Elixir.World"
|
||||
end
|
||||
|
||||
test "__aliases__: expands with alias" do
|
||||
alias Hello, as: World
|
||||
assert expand_env(quote(do: World), __ENV__) |> elem(0) == :"Elixir.Hello"
|
||||
end
|
||||
|
||||
test "__aliases__: expands with alias is recursive" do
|
||||
alias Source, as: Hello
|
||||
alias Hello, as: World
|
||||
assert expand_env(quote(do: World), __ENV__) |> elem(0) == :"Elixir.Source"
|
||||
end
|
||||
|
||||
test "__aliases__: expands to elixir_aliases on runtime" do
|
||||
assert expand(quote do: hello.World) ==
|
||||
quote do: :elixir_aliases.concat([hello(), :World])
|
||||
end
|
||||
|
||||
## =
|
||||
|
||||
test "=: sets context to match" do
|
||||
assert expand(quote do: __ENV__.context = :match) == quote do: :match = :match
|
||||
end
|
||||
|
||||
test "=: defines vars" do
|
||||
{ output, env } = expand_env(quote(do: a = 1), __ENV__)
|
||||
assert output == quote(do: a = 1)
|
||||
assert { :a, __MODULE__ } in env.vars
|
||||
end
|
||||
|
||||
test "=: does not carry rhs imports" do
|
||||
assert expand(quote(do: flatten([1,2,3]) = import List)) ==
|
||||
quote(do: flatten([1,2,3]) = import :"Elixir.List", [])
|
||||
end
|
||||
|
||||
test "=: does not define _" do
|
||||
{ output, env } = expand_env(quote(do: _ = 1), __ENV__)
|
||||
assert output == quote(do: _ = 1)
|
||||
assert env.vars == []
|
||||
end
|
||||
|
||||
## Pseudo vars
|
||||
|
||||
test "__MODULE__" do
|
||||
assert expand(quote do: __MODULE__) == __MODULE__
|
||||
end
|
||||
|
||||
test "__DIR__" do
|
||||
assert expand(quote do: __DIR__) == __DIR__
|
||||
end
|
||||
|
||||
test "__CALLER__" do
|
||||
assert expand(quote do: __CALLER__) == quote do: __CALLER__
|
||||
end
|
||||
|
||||
test "__ENV__" do
|
||||
env = __ENV__
|
||||
assert expand_env(quote(do: __ENV__), env) ==
|
||||
{ { :{}, [], tuple_to_list(env.line(0)) }, env }
|
||||
end
|
||||
|
||||
test "__ENV__.accessor" do
|
||||
env = __ENV__
|
||||
assert expand_env(quote(do: __ENV__.file), env) == { __ENV__.file, env }
|
||||
assert expand_env(quote(do: __ENV__.unknown), env) ==
|
||||
{ quote(do: unquote({ :{}, [], tuple_to_list(env.line(0)) }).unknown), env }
|
||||
end
|
||||
|
||||
## Super
|
||||
|
||||
test "super: expand args" do
|
||||
assert expand(quote do: super(a, b)) == quote do: super(a(), b())
|
||||
end
|
||||
|
||||
## Vars
|
||||
|
||||
test "vars: expand to local call" do
|
||||
{ output, env } = expand_env(quote(do: a), __ENV__)
|
||||
assert output == quote(do: a())
|
||||
assert env.vars == []
|
||||
end
|
||||
|
||||
test "vars: forces variable to exist" do
|
||||
assert expand(quote do: (var!(a) = 1; var!(a)))
|
||||
|
||||
message = %r"expected var a to expand to an existing variable or be a part of a match"
|
||||
assert_raise CompileError, message, fn -> expand(quote do: var!(a)) end
|
||||
|
||||
message = %r"expected var a \(context Unknown\) to expand to an existing variable or be a part of a match"
|
||||
assert_raise CompileError, message, fn -> expand(quote do: var!(a, Unknown)) end
|
||||
end
|
||||
|
||||
test "^: expands args" do
|
||||
assert expand(quote do: ^a = 1) == quote do: ^a = 1
|
||||
end
|
||||
|
||||
test "^: raises outside match" do
|
||||
assert_raise CompileError, %r"cannot use \^a outside of match clauses", fn ->
|
||||
expand(quote do: ^a)
|
||||
end
|
||||
end
|
||||
|
||||
test "^: raises without var" do
|
||||
assert_raise CompileError, %r"invalid argument for unary operator \^, expected an existing variable, got: \^1", fn ->
|
||||
expand(quote do: ^1 = 1)
|
||||
end
|
||||
end
|
||||
|
||||
## Locals
|
||||
|
||||
test "locals: expands to remote calls" do
|
||||
assert { {:., _, [Kernel, :=~] }, _, [{:a, _, []}, {:b, _, []}] } =
|
||||
expand(quote do: a =~ b)
|
||||
end
|
||||
|
||||
test "locals: expands to configured local" do
|
||||
assert expand_env(quote(do: a), __ENV__.local(Hello)) |> elem(0) ==
|
||||
quote(do: :"Elixir.Hello".a())
|
||||
end
|
||||
|
||||
test "locals: custom imports" do
|
||||
assert expand(quote do: (import Kernel.ExpansionTarget; seventeen)) ==
|
||||
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; 17)
|
||||
end
|
||||
|
||||
## Tuples
|
||||
|
||||
test "tuples: expanded as arguments" do
|
||||
assert expand(quote(do: { a = 1, a })) == quote do: { a = 1, a() }
|
||||
assert expand(quote(do: { b, a = 1, a })) == quote do: { b(), a = 1, a() }
|
||||
end
|
||||
|
||||
## quote
|
||||
|
||||
test "quote: expanded to raw forms" do
|
||||
assert expand(quote do: (quote do: hello)) == { :{}, [], [:hello, [], __MODULE__] }
|
||||
end
|
||||
|
||||
## Anonymous calls
|
||||
|
||||
test "anonymous calls: expands base and args" do
|
||||
assert expand(quote do: a.(b)) == quote do: a().(b())
|
||||
end
|
||||
|
||||
test "anonymous calls: raises on atom base" do
|
||||
assert_raise CompileError, %r"invalid function call :foo.()", fn ->
|
||||
expand(quote do: :foo.(a))
|
||||
end
|
||||
end
|
||||
|
||||
## Remote calls
|
||||
|
||||
test "remote calls: expands to erlang" do
|
||||
assert expand(quote do: Kernel.is_atom(a)) == quote do: :erlang.is_atom(a())
|
||||
end
|
||||
|
||||
test "remote calls: expands macros" do
|
||||
assert expand(quote do: Kernel.ExpansionTest.thirteen) == 13
|
||||
end
|
||||
|
||||
test "remote calls: expands receiver and args" do
|
||||
assert expand(quote do: a.is_atom(b)) == quote do: a().is_atom(b())
|
||||
assert expand(quote do: (a = :foo).is_atom(a)) == quote do: (a = :foo).is_atom(a())
|
||||
end
|
||||
|
||||
test "remote calls: modules must be required for macros" do
|
||||
assert expand(quote do: (require Kernel.ExpansionTarget; Kernel.ExpansionTarget.seventeen)) ==
|
||||
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; 17)
|
||||
end
|
||||
|
||||
test "remote calls: raises when not required" do
|
||||
msg = %r"you must require Kernel\.ExpansionTarget before invoking the macro Kernel\.ExpansionTarget\.seventeen/0"
|
||||
assert_raise CompileError, msg, fn ->
|
||||
expand(quote do: Kernel.ExpansionTarget.seventeen)
|
||||
end
|
||||
end
|
||||
|
||||
## Comprehensions
|
||||
|
||||
test "lc: variables inside comprehensions do not leak" do
|
||||
assert expand(quote do: (lc(a inlist b, do: c = 1); c)) ==
|
||||
quote do: (lc(a inlist b(), do: c = 1); c())
|
||||
end
|
||||
|
||||
test "bc: variables inside comprehensions do not leak" do
|
||||
assert expand(quote do: (bc(a inbits b, do: c = 1); c)) ==
|
||||
quote do: (bc(a inbits b(), do: c = 1); c())
|
||||
end
|
||||
|
||||
## Capture
|
||||
|
||||
test "&: keeps locals" do
|
||||
assert expand(quote do: &unknown/2) ==
|
||||
{:&, [], [{:/, [], [{:unknown,[],nil}, 2]}]}
|
||||
assert expand(quote do: &unknown(&1, &2)) ==
|
||||
{:&, [], [{:/, [], [{:unknown,[],nil}, 2]}]}
|
||||
end
|
||||
|
||||
test "&: expands remotes" do
|
||||
assert expand(quote do: &List.flatten/2) ==
|
||||
quote do: :erlang.make_fun(:"Elixir.List", :flatten, 2)
|
||||
|
||||
assert expand(quote do: &Kernel.is_atom/1) ==
|
||||
quote do: :erlang.make_fun(:erlang, :is_atom, 1)
|
||||
end
|
||||
|
||||
test "&: expands macros" do
|
||||
|
||||
assert expand(quote do: (require Kernel.ExpansionTarget; &Kernel.ExpansionTarget.seventeen/0)) ==
|
||||
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; fn -> 17 end)
|
||||
end
|
||||
|
||||
## fn
|
||||
|
||||
test "fn: expands each clause" do
|
||||
assert expand(quote do: fn x -> x; _ -> x end) ==
|
||||
quote do: fn x -> x; _ -> x() end
|
||||
end
|
||||
|
||||
test "fn: does not share lexical in between clauses" do
|
||||
assert expand(quote do: fn 1 -> import List; 2 -> flatten([1,2,3]) end) ==
|
||||
quote do: fn 1 -> import :"Elixir.List", []; 2 -> flatten([1,2,3]) end
|
||||
end
|
||||
|
||||
test "fn: expands guards" do
|
||||
assert expand(quote do: fn x when x when __ENV__.context -> true end) ==
|
||||
quote do: fn x when x when :guard -> true end
|
||||
end
|
||||
|
||||
test "fn: does not leak vars" do
|
||||
assert expand(quote do: (fn x -> x end; x)) ==
|
||||
quote do: (fn x -> x end; x())
|
||||
end
|
||||
|
||||
## Case
|
||||
|
||||
test "case: expands each clause" do
|
||||
assert expand_and_clean(quote do: (case w do x -> x; _ -> x end)) ==
|
||||
quote do: (case w() do x -> x; _ -> x() end)
|
||||
end
|
||||
|
||||
test "case: does not share lexical in between clauses" do
|
||||
assert expand_and_clean(quote do: (case w do 1 -> import List; 2 -> flatten([1,2,3]) end)) ==
|
||||
quote do: (case w() do 1 -> import :"Elixir.List", []; 2 -> flatten([1,2,3]) end)
|
||||
end
|
||||
|
||||
test "case: expands guards" do
|
||||
assert expand_and_clean(quote do: (case w do x when x when __ENV__.context -> true end)) ==
|
||||
quote do: (case w() do x when x when :guard -> true end)
|
||||
end
|
||||
|
||||
test "case: does not leaks vars on head" do
|
||||
assert expand_and_clean(quote do: (case w do x -> x; y -> y end; :erlang.+(x, y))) ==
|
||||
quote do: (case w() do x -> x; y -> y end; :erlang.+(x(), y()))
|
||||
end
|
||||
|
||||
test "case: leaks vars" do
|
||||
assert expand_and_clean(quote do: (case w do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
|
||||
quote do: (case w() do x -> x = x; y -> y = y end; :erlang.+(x, y))
|
||||
end
|
||||
|
||||
## Receive
|
||||
|
||||
test "receive: expands each clause" do
|
||||
assert expand_and_clean(quote do: (receive do x -> x; _ -> x end)) ==
|
||||
quote do: (receive do x -> x; _ -> x() end)
|
||||
end
|
||||
|
||||
test "receive: does not share lexical in between clauses" do
|
||||
assert expand_and_clean(quote do: (receive do 1 -> import List; 2 -> flatten([1,2,3]) end)) ==
|
||||
quote do: (receive do 1 -> import :"Elixir.List", []; 2 -> flatten([1,2,3]) end)
|
||||
end
|
||||
|
||||
test "receive: expands guards" do
|
||||
assert expand_and_clean(quote do: (receive do x when x when __ENV__.context -> true end)) ==
|
||||
quote do: (receive do x when x when :guard -> true end)
|
||||
end
|
||||
|
||||
test "receive: does not leaks clause vars" do
|
||||
assert expand_and_clean(quote do: (receive do x -> x; y -> y end; :erlang.+(x, y))) ==
|
||||
quote do: (receive do x -> x; y -> y end; :erlang.+(x(), y()))
|
||||
end
|
||||
|
||||
test "receive: leaks vars" do
|
||||
assert expand_and_clean(quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
|
||||
quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))
|
||||
end
|
||||
|
||||
test "receive: leaks vars on after" do
|
||||
assert expand_and_clean(quote do: (receive do x -> x = x after y -> y; w = y end; :erlang.+(x, w))) ==
|
||||
quote do: (receive do x -> x = x after y() -> y(); w = y() end; :erlang.+(x, w))
|
||||
end
|
||||
|
||||
## 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())
|
||||
end
|
||||
|
||||
test "try: expands after" do
|
||||
assert expand(quote do: (try do x after z = y end; z)) ==
|
||||
quote do: (try do x() after z = y() end; z())
|
||||
end
|
||||
|
||||
test "try: expands else" do
|
||||
assert expand(quote do: (try do x else z -> z end; z)) ==
|
||||
quote do: (try do x() else z -> z end; z())
|
||||
end
|
||||
|
||||
test "try: expands rescue" do
|
||||
assert expand(quote do: (try do x rescue x -> x; Error -> x end; x)) ==
|
||||
quote do: (try do x() rescue unquote(:in)(x, _) -> x; unquote(:in)(_, [:"Elixir.Error"]) -> x() end; x())
|
||||
end
|
||||
|
||||
## Binaries
|
||||
|
||||
test "bitstrings: expands modifiers" do
|
||||
assert expand(quote do: (import Kernel.ExpansionTarget; << x :: seventeen >>)) ==
|
||||
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; << x() :: [unquote(:size)(17)] >>)
|
||||
|
||||
assert expand(quote do: (import Kernel.ExpansionTarget; << seventeen :: seventeen, x :: size(seventeen) >> = 1)) ==
|
||||
quote do: (import :"Elixir.Kernel.ExpansionTarget", [];
|
||||
<< seventeen :: [unquote(:size)(17)], x :: [unquote(:size)(seventeen)] >> = 1)
|
||||
end
|
||||
|
||||
test "bitstrings: expands modifiers args" do
|
||||
assert expand(quote do: (require Kernel.ExpansionTarget; << x :: size(Kernel.ExpansionTarget.seventeen) >>)) ==
|
||||
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; << x() :: [unquote(:size)(17)] >>)
|
||||
end
|
||||
|
||||
## Invalid
|
||||
|
||||
test "handles invalid expressions" do
|
||||
assert_raise CompileError, %r"invalid quoted expression: {1, 2, 3}", fn ->
|
||||
expand(quote do: unquote({ 1, 2, 3 }))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defmacro thirteen do
|
||||
13
|
||||
end
|
||||
|
||||
defp expand_and_clean(expr) do
|
||||
expand_env(expr, __ENV__) |> elem(0) |> Macro.update_meta(&Keyword.drop(&1, [:export]))
|
||||
end
|
||||
|
||||
defp expand(expr) do
|
||||
expand_env(expr, __ENV__) |> elem(0)
|
||||
end
|
||||
|
||||
defp expand_env(expr, env) do
|
||||
{ expr, env } = :elixir_exp.expand(expr, :elixir_env.ex_to_env(env))
|
||||
{ expr, set_elem(env, 0, Macro.Env) }
|
||||
end
|
||||
end
|
||||
@@ -85,8 +85,8 @@ defmodule Kernel.FnTest do
|
||||
end
|
||||
|
||||
test "capture and partially apply on call" do
|
||||
assert (&(&1.file)).(__ENV__) == __FILE__
|
||||
assert (&(&1.file(&2))).(__ENV__, "Hello").file == "Hello"
|
||||
assert (&(&1.module)).(__ENV__) == __MODULE__
|
||||
assert (&(&1.module(&2))).(__ENV__, Hello).module == Hello
|
||||
end
|
||||
|
||||
test "capture block like" do
|
||||
@@ -110,21 +110,21 @@ defmodule Kernel.FnTest do
|
||||
end
|
||||
|
||||
test "failure on block" do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid args for &, block expressions " <>
|
||||
"are not allowed, got: (\n 1\n 2\n)",
|
||||
"&(1;2)"
|
||||
end
|
||||
|
||||
test "failure on other types" do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid args for &, expected an expression in the format of &Mod.fun/arity, " <>
|
||||
"&local/arity or a capture containing at least one argument as &1, got: :foo",
|
||||
"&:foo"
|
||||
end
|
||||
|
||||
test "failure when no captures" do
|
||||
assert_compile_fail SyntaxError,
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid args for &, expected an expression in the format of &Mod.fun/arity, " <>
|
||||
"&local/arity or a capture containing at least one argument as &1, got: foo()",
|
||||
"&foo()"
|
||||
|
||||
@@ -9,7 +9,6 @@ defmodule Kernel.ImportTest do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
test "import all" do
|
||||
import :lists
|
||||
assert flatten([1, [2], 3]) == [1, 2, 3]
|
||||
@@ -132,8 +131,11 @@ defmodule Kernel.ImportTest do
|
||||
try do
|
||||
import :lists
|
||||
flatten([1, [2], 3])
|
||||
flunk
|
||||
catch
|
||||
_ -> flatten([:a, [:b], :c])
|
||||
_, _ ->
|
||||
# Buggy local duplicate is untouched
|
||||
assert duplicate([1], 2) == [1]
|
||||
end
|
||||
|
||||
# Buggy local duplicate is untouched
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user