Compare commits

...
15 Commits
Author SHA1 Message Date
José Valim 34df817638 Release v1.0.2 2014-10-21 15:19:58 -02:00
José Valim 311a5790d6 Update release instructions
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	Makefile
2014-10-21 15:02:37 -02:00
José Valim b4c75f97b4 Fail to compile try without else/catch/after/rescue, closes #2797
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:57:55 -02:00
José Valim 70692e6618 Improve token errors for aliases, closes #2818
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:57:47 -02:00
José Valim 529d584262 Unify error reporting from EEx, closes #2833
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>

Conflicts:
	lib/eex/test/eex_test.exs
2014-10-21 14:57:36 -02:00
José Valim c0fb275d5b Properly handle eval_failure warnings, closes #2835
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-21 14:56:55 -02:00
José Valim 24f46be144 Use unamed tables for module compilation
* We have created a main named table that keeps all module information

* We have reduced the amount of tables by storing internal, docs and
  attributes altogether and using table looks for retriving the relevant
  information

* At this moment, creating a module in Elixir defines three tables,
  one for data, another for functions and another for clauses

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-17 13:53:04 +02:00
José Valim 0fe3da9dea Update CHANGELOG 2014-10-15 20:54:31 +02:00
José Valim 03d0ad3f03 No longer inline binary expressions in EEx
String.Chars is now always inlined by the compiler.

Closes #2815

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:48 +02:00
José Valim a87f43701f Merge pull request #2819 from benjamintanweihao/patch-3
Fix missing concatenation operator.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:34 +02:00
k1complete dce3a3a2a5 set null prompt function in dumbterm mode
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:52:19 +02:00
Eduardo Gurgel 7b20084b28 Add OTP 17.3 to run on Travis CI
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:51:57 +02:00
José Valim 542808102f Build .beam location from scratch on mix compile.elixir
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-10-15 20:51:41 +02:00
Eric Meadows-Jönsson fd18b6a41f Fix String.replace/4 typespec
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-07 19:41:53 +02:00
Eric Meadows-Jönsson 4c9c95f80f Add task for pushing standalone mix
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2014-10-07 15:23:47 +02:00
31 changed files with 352 additions and 280 deletions
+1
View File
@@ -8,3 +8,4 @@ notifications:
otp_release:
- 17.0
- 17.1
- 17.3
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## v1.0.2 (2014-10-21)
* Bug fixes
* [EEx] Remove invalid warning when attempting to inline code in EEx
* [EEx] Unify syntax errors to always include `file:line:`
* [IEx] Set null prompt function in dumb term mode
* [Kernel] Do not define an ets table with the same name as the module as it clashes with user code
* [Kernel] Fail compiling try expressions if they lack one of catch/after/rescue/else clauses
* [Kernel] Improve error messages when parsing fails on aliases
* [Kernel] Improve warnings coming from compile-time evaulation of arguments
* [String] Fix `String.replace/4` typespec
## v1.0.1 (2014-10-07)
* Enhancements
+13 -2
View File
@@ -1,4 +1,5 @@
REBAR := rebar
DOCS := v1.0
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -146,8 +147,18 @@ release_zip: compile
release_docs: docs
cd ../docs
rm -rf ../docs/stable
mv docs ../docs/stable
rm -rf ../docs/$(DOCS)
mv docs ../docs/$(DOCS)
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
publish_mix: compile
cd lib/mix && MIX_ENV=prod mix escript.build
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/v$(VERSION)/mix --acl public-read
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/mix --acl public-read
rm lib/mix/mix
rm -rf lib/mix/_build
#==> Tests tasks
+7 -7
View File
@@ -18,23 +18,23 @@ This document simply outlines the release process:
7. Push master and the new tag
8. Release new docs with `make release_docs`, move docs to `docs/stable` and push them
8. Release new docs with `make release_docs`, move docs to `docs/stable` if appropriate, and push
9. Release new zip with `make release_zip`, push `Precompiled.zip` to GitHub Releases
10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
11. Build and push standalone Mix with `make publish_mix` (requires AWS credentials)
## New vMAJOR.MINOR releases
11. Create a new branch "vMAJOR.MINOR" release
11. Create a new branch "vMAJOR.MINOR"
12. Move stable docs to `docs/vOLD-MAJOR.OLD-MINOR`
12. Move docs generation to `docs/vOLD-MAJOR.OLD-MINOR` and copy them to `docs/stable`
13. Move master docs to `docs/stable`
13. Bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
14. Bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
15. `make release_docs` and push it to `elixir-lang/docs`
14. `make release_docs` and push it to `elixir-lang/docs`
## Places where version is mentioned
+1 -1
View File
@@ -1 +1 @@
1.0.1
1.0.2
+5 -1
View File
@@ -1,5 +1,9 @@
defmodule EEx.SyntaxError do
defexception [:message]
defexception [:message, :file, :line]
def message(exception) do
"#{exception.file}:#{exception.line}: #{exception.message}"
end
end
defmodule EEx do
+13 -8
View File
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
def compile(source, opts) do
file = opts[:file] || "nofile"
line = opts[:line] || 1
tokens = EEx.Tokenizer.tokenize(source, line)
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
case EEx.Tokenizer.tokenize(source, line) do
{:ok, tokens} ->
state = %{engine: opts[:engine] || @default_engine,
file: file, line: line, quoted: [], start_line: nil}
generate_buffer(tokens, "", [], state)
{:error, line, message} ->
raise EEx.SyntaxError, line: line, file: file, message: message
end
end
# Generates the buffers by handling each expression from the tokenizer
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
{buffer, t}
end
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
end
defp generate_buffer([], buffer, [], state) do
state.engine.handle_body(buffer)
end
defp generate_buffer([], _buffer, _scope, _state) do
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>."
defp generate_buffer([], _buffer, _scope, state) do
raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
file: state.file, line: state.line
end
# Creates a placeholder and wrap it inside the expression block
+1 -4
View File
@@ -99,10 +99,7 @@ defmodule EEx.Engine do
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> (case unquote(expr) do
bin when is_binary(bin) -> bin
oth -> String.Chars.to_string(oth)
end)
tmp <> String.Chars.to_string(unquote(expr))
end
end
+31 -20
View File
@@ -3,7 +3,8 @@ defmodule EEx.Tokenizer do
@doc """
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
It returns {:ok, list} with the following tokens:
* `{:text, contents}`
* `{:expr, line, marker, contents}`
@@ -11,33 +12,43 @@ defmodule EEx.Tokenizer do
* `{:middle_expr, line, marker, contents}`
* `{:end_expr, line, marker, contents}`
Or `{:error, line, error}` in case of errors.
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(String.to_char_list(bin), line)
end
def tokenize(list, line) do
Enum.reverse(tokenize(list, line, [], []))
tokenize(list, line, [], [])
end
defp tokenize('<%%' ++ t, line, buffer, acc) do
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
tokenize rest, new_line, [?>, ?%|buffer], acc
case expr(t, line, [?%, ?<|buffer]) do
{:error, _, _} = error -> error
{:ok, buffer, new_line, rest} ->
tokenize rest, new_line, [?>, ?%|buffer], acc
end
end
defp tokenize('<%#' ++ t, line, buffer, acc) do
{_, new_line, rest} = tokenize_expr t, line, []
tokenize rest, new_line, buffer, acc
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, _, new_line, rest} ->
tokenize rest, new_line, buffer, acc
end
end
defp tokenize('<%' ++ t, line, buffer, acc) do
{marker, t} = retrieve_marker(t)
{expr, new_line, rest} = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
case expr(t, line, []) do
{:error, _, _} = error -> error
{:ok, expr, new_line, rest} ->
token = token_name(expr)
acc = tokenize_text(buffer, acc)
final = {token, line, marker, Enum.reverse(expr)}
tokenize rest, new_line, [], [final | acc]
end
end
defp tokenize('\n' ++ t, line, buffer, acc) do
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
end
defp tokenize([], _line, buffer, acc) do
tokenize_text(buffer, acc)
{:ok, Enum.reverse(tokenize_text(buffer, acc))}
end
# Retrieve marker for <%
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
# Tokenize an expression until we find %>
defp tokenize_expr([?%, ?>|t], line, buffer) do
{buffer, line, t}
defp expr([?%, ?>|t], line, buffer) do
{:ok, buffer, line, t}
end
defp tokenize_expr('\n' ++ t, line, buffer) do
tokenize_expr t, line + 1, [?\n|buffer]
defp expr('\n' ++ t, line, buffer) do
expr t, line + 1, [?\n|buffer]
end
defp tokenize_expr([h|t], line, buffer) do
tokenize_expr t, line, [h|buffer]
defp expr([h|t], line, buffer) do
expr t, line, [h|buffer]
end
defp tokenize_expr([], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>"
defp expr([], line, _buffer) do
{:error, line, "missing token '%>'"}
end
# Receive an expression content and check
+25 -24
View File
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
require EEx.Tokenizer, as: T
test "simple chars lists" do
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
assert T.tokenize('foo <% bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, "", ' bar '}]}
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
assert T.tokenize('foo <%= bar %>', 1) ==
{:ok, [{:text, 'foo '}, {:expr, 1, "=", ' bar '}]}
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
assert T.tokenize('foo\n<%= bar %>', 1) ==
{:ok, [{:text, 'foo\n'}, {:expr, 2, "=", ' bar '}]}
end
test "strings with more than one line and expression with more than one line" do
@@ -32,50 +35,50 @@ baz %>
<% foo %>
'''
assert T.tokenize(string, 1) == [
assert T.tokenize(string, 1) == {:ok, [
{:text, 'foo '},
{:expr, 1, "=", ' bar\n\nbaz '},
{:text, '\n'},
{:expr, 4, "", ' foo '},
{:text, '\n'}
]
]}
end
test "quotation" do
assert T.tokenize('foo <%% true %>', 1) == [
assert T.tokenize('foo <%% true %>', 1) == {:ok, [
{:text, 'foo <% true %>'}
]
]}
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
{:text, 'foo <% true do %>bar<% end %>'}
]
]}
end
test "comments" do
assert T.tokenize('foo <%# true %>', 1) == [
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
{:text, 'foo '}
]
]}
end
test "comments with do/end" do
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
{:text, 'foo bar'}
]
]}
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' cond do '},
{:middle_expr, 1, "", ' false -> '},
@@ -83,23 +86,21 @@ baz %>
{:middle_expr, 1, "", ' true -> '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, [
{:text, 'foo '},
{:start_expr, 1, "", ' if true do '},
{:text, 'bar'},
{:middle_expr, 1, "", ' else '},
{:text, 'baz'},
{:end_expr, 1, "", ' end '}
]
]}
end
test "raise syntax error when there is start mark and no end mark" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
T.tokenize('foo <% :bar', 1)
end
assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
end
end
+6 -6
View File
@@ -111,25 +111,25 @@ defmodule EExTest do
end
test "raises a syntax error when the token is invalid" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
EEx.compile_string "foo <%= bar"
end
end
test "raises a syntax error when end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% end %>"
end
end
test "raises a syntax error when start expression is found without an end expression" do
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
EEx.compile_string "foo <% if true do %>"
assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
EEx.compile_string "foo\n<% if true do %>"
end
end
test "raises a syntax error when nested end expression is found without an start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
test "raises a syntax error when nested end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
EEx.compile_string "foo <% if true do %><% end %><% end %>"
end
end
+1 -1
View File
@@ -33,7 +33,7 @@ defmodule Kernel.LexicalTracker do
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
val
end
+23 -28
View File
@@ -504,19 +504,19 @@ defmodule Module do
def add_doc(module, line, kind, tuple, signature, doc) when
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
table = data_table_for(module)
{signature, _} = :lists.mapfoldl fn(x, acc) ->
{simplify_signature(x, acc), acc + 1}
end, 1, signature
case :ets.lookup(table, tuple) do
case :ets.lookup(table, {:doc, tuple}) do
[] ->
:ets.insert(table, {tuple, line, kind, signature, doc})
:ets.insert(table, {{:doc, tuple}, line, kind, signature, doc})
:ok
[{tuple, line, _old_kind, old_sign, old_doc}] ->
[{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
:ets.insert(table, {
tuple,
doc_tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
@@ -612,7 +612,7 @@ defmodule Module do
"""
def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
table = defs_table_for(module)
:ets.lookup(table, tuple) != []
end
@@ -632,7 +632,7 @@ defmodule Module do
"""
def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
table = defs_table_for(module)
case :ets.lookup(table, tuple) do
[{_, ^kind, _, _, _, _, _}] -> true
_ -> false
@@ -652,8 +652,8 @@ defmodule Module do
"""
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
table = defs_table_for(module)
:lists.concat :ets.match(table, {:'$1', :_, :_, :_, :_, :_, :_})
end
@doc """
@@ -671,8 +671,8 @@ defmodule Module do
"""
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
table = defs_table_for(module)
:lists.concat :ets.match(table, {:'$1', kind, :_, :_, :_, :_, :_})
end
@doc """
@@ -698,12 +698,11 @@ defmodule Module do
[]
end
old = get_attribute(module, :__overridable)
old = :elixir_def_overridable.overridable(module)
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
{count + 1, clause, neighbours, false}
end, {1, clause, neighbours, false}, old)
put_attribute(module, :__overridable, merged)
:elixir_def_overridable.overridable(module, merged)
end
end
end
@@ -712,7 +711,7 @@ defmodule Module do
Returns `true` if `tuple` in `module` is marked as overridable.
"""
def overridable?(module, tuple) do
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
end
@doc """
@@ -731,7 +730,7 @@ defmodule Module do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = normalize_attribute(key, value)
acc = :ets.lookup_element(table, :__acc_attributes, 2)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
new =
if :lists.member(key, acc) do
@@ -832,13 +831,13 @@ defmodule Module do
table = data_table_for(module)
if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, :__persisted_attributes, 2)
:ets.insert(table, {:__persisted_attributes, [new|old]})
old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
:ets.insert(table, {{:elixir, :persisted_attributes}, [new|old]})
end
if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, :__acc_attributes, 2)
:ets.insert(table, {:__acc_attributes, [new|old]})
old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
:ets.insert(table, {{:elixir, :acc_attributes}, [new|old]})
end
end
@@ -901,7 +900,7 @@ defmodule Module do
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
cond do
:lists.member(key, acc) ->
@@ -956,15 +955,11 @@ defmodule Module do
end
defp data_table_for(module) do
module
:elixir_module.data_table(module)
end
defp function_table_for(module) do
:elixir_def.table(module)
end
defp docs_table_for(module) do
:elixir_module.docs_table(module)
defp defs_table_for(module) do
:elixir_module.defs_table(module)
end
defp assert_not_compiled!(fun, module) do
+1 -1
View File
@@ -83,7 +83,7 @@ defmodule Module.LocalsTracker do
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{_, val}] = :ets.lookup(table, :__locals_tracker)
[{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
val
end
+8 -8
View File
@@ -74,21 +74,21 @@ defmodule String do
be avoided in favor of binary functions or pattern matching.
For example, imagine you have a string `prefix` and you want to
remove this prefix from another string named `full`.
One may be tempted to write:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
...> String.slice(full, base, String.length(full) - base)
...> end
...> take_prefix.("Mr. John", "Mr. ")
"John"
Although the function above works, it performs poorly. To
calculate the length of the string, we need to traverse it
fully, so we traverse both `prefix` and `full` strings, then
slice the `full` one, traversing it again.
A first attempting at improving it could be with ranges:
iex> take_prefix = fn full, prefix ->
@@ -103,7 +103,7 @@ defmodule String do
extract a substring from a string, we can use `byte_size/1`
and `binary_part/3` as there is no chance we will slice in
the middle of a codepoint made of more than one byte:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> binary_part(full, base, byte_size(full) - base)
@@ -112,7 +112,7 @@ defmodule String do
"John"
Or simply used pattern matching:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> <<_ :: binary-size(base), rest :: binary>> = full
@@ -672,8 +672,8 @@ defmodule String do
"a[,,]b[,,]c"
"""
@spec replace(t, t, t) :: t
@spec replace(t, t, t, Keyword.t) :: t
@spec replace(t, t | Regex.t, t) :: t
@spec replace(t, t | Regex.t, t, Keyword.t) :: t
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
if Regex.regex?(pattern) do
+36 -48
View File
@@ -1,49 +1,35 @@
% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
-module(elixir_def).
-export([table/1, clauses_table/1, setup/1,
cleanup/1, reset_last/1, lookup_definition/2,
-export([setup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/2,
store_each/8, format_error/1]).
store_each/7, format_error/1]).
-include("elixir.hrl").
-define(attr, '__def_table').
-define(clauses_attr, '__clauses_table').
-define(last_def, {elixir, last_def}).
-define(attr, {elixir, def_table}).
-define(clauses_attr, {elixir, clauses_table}).
%% Table management functions. Called internally.
table(Module) ->
ets:lookup_element(Module, ?attr, 2).
clauses_table(Module) ->
ets:lookup_element(Module, ?clauses_attr, 2).
setup(Module) ->
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
reset_last(Module),
ok.
cleanup(Module) ->
ets:delete(table(Module)),
ets:delete(clauses_table(Module)).
%% Reset the last item. Useful when evaling code.
reset_last(Module) ->
ets:insert(table(Module), {last, []}).
ets:insert(elixir_module:data_table(Module), {?last_def, []}).
%% Looks up a definition from the database.
lookup_definition(Module, Tuple) ->
case ets:lookup(table(Module), Tuple) of
case ets:lookup(elixir_module:defs_table(Module), Tuple) of
[Result] ->
CTable = clauses_table(Module),
CTable = elixir_module:clas_table(Module),
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
_ ->
false
end.
delete_definition(Module, Tuple) ->
ets:delete(table(Module), Tuple),
ets:delete(clauses_table(Module), Tuple).
ets:delete(elixir_module:defs_table(Module), Tuple),
ets:delete(elixir_module:clas_table(Module), Tuple).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
@@ -98,16 +84,12 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocatio
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = ?m(E, file),
Table = table(Module),
CTable = clauses_table(Module),
File = ?m(E, file),
compile_super(Module, Super, E),
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
store_each(CheckClauses, Kind, File, Location,
Table, CTable, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Table, CTable, 0,
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Module, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
make_struct_available(Kind, Module, Name, Args),
@@ -166,7 +148,7 @@ normalize_location(File) ->
%% Compile super
compile_super(Module, true, #{function := Function}) ->
elixir_def_overridable:store(Module, Function, true);
elixir_def_overridable:super(Module, Function);
compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
@@ -229,9 +211,8 @@ is_macro(_) -> false.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
unwrap_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
Table = elixir_module:defs_table(Module),
CTable = elixir_module:clas_table(Module),
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
@@ -327,26 +308,33 @@ warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = {Name, Arity},
store_each(Check, Kind, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
Data = elixir_module:data_table(Module),
Defs = elixir_module:defs_table(Module),
Clas = elixir_module:clas_table(Module),
Tuple = {Name, Arity},
HasBody = Clauses =/= [],
case ets:lookup(Table, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
case ets:lookup(Defs, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
[] ->
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = {Defaults, HasBody, Defaults}
end,
Check andalso ets:insert(Table, {last, {Name, Arity}}),
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
%% Validations
@@ -355,8 +343,8 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
case ets:lookup_element(Table, last, 2) of
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
case ets:lookup_element(Data, ?last_def, 2) of
{Name,Arity} -> [];
[] -> [];
_ ->
@@ -379,8 +367,8 @@ check_valid_defaults(Line, File, Name, Arity, Kind, _, _, _) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{clauses_with_defaults, {Kind, Name, Arity}}).
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
+12 -8
View File
@@ -1,14 +1,18 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([store_pending/1, ensure_defined/4,
name/2, store/3, format_error/1]).
-export([setup/1, overridable/1, overridable/2, name/2, super/2, store_pending/1,
ensure_defined/4, format_error/1]).
-include("elixir.hrl").
-define(attr, {elixir, overridable}).
setup(Module) ->
overridable(Module, []).
overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
ets:lookup_element(elixir_module:data_table(Module), ?attr, 2).
overridable(Module, Value) ->
ets:insert(elixir_module:data_table(Module), {'__overridable', Value}).
ets:insert(elixir_module:data_table(Module), {?attr, Value}).
%% Check if an overridable function is defined.
@@ -51,11 +55,11 @@ store(Module, Function, GenerateName) ->
end,
Def = {function, Line, FinalName, Arity, Clauses},
elixir_def:store_each(false, FinalKind, File, Location,
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
elixir_def:store_each(false, FinalKind, File, Location, Module, Defaults, Def)
end.
%% Store pending declarations that were not manually made concrete.
super(Module, Function) ->
store(Module, Function, true).
store_pending(Module) ->
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
@@ -73,4 +77,4 @@ format_error({no_super, Module, {Name, Arity}}) ->
format_fa({Name, Arity}) ->
A = atom_to_binary(Name, utf8),
B = integer_to_binary(Arity),
<< A/binary, $/, B/binary >>.
<<A/binary, $/, B/binary>>.
+15 -1
View File
@@ -55,7 +55,16 @@ parse_error(Line, File, Error, <<>>) ->
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
%% Aliases are wrapped in ['']
parse_error(Line, File, Error, <<"['", Token/binary>>) when is_binary(Error) ->
Rest =
case binary:split(Token, <<"'">>) of
[Part, _] -> Part;
_ -> <<>>
end,
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary>>);
%% Binaries (and interpolation) are wrapped in [<<...>>]
parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
Rest =
case binary:split(Full, <<"<<">>) of
@@ -140,6 +149,11 @@ handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
Message = format_error(erl_lint, {unused_var, format_var(Var)}),
warn(Line, File, Message);
%% Handle literal eval failures
handle_file_warning(_, File, {Line,sys_core_fold,{eval_failure, Error}}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
warn(Line, File, ["this expression will fail with ", elixir_aliases:inspect(Struct)]);
%% Default behaviour
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
+3 -1
View File
@@ -101,7 +101,9 @@ do_receive(Meta, {Key, _}, _Acc, E) ->
%% Try
'try'(Meta, [], E) ->
compile_error(Meta, ?m(E, file), "missing do keywords in try");
compile_error(Meta, ?m(E, file), "missing do keyword in try");
'try'(Meta, [{do, _}], E) ->
compile_error(Meta, ?m(E, file), "missing catch/rescue/after/else keyword in try");
'try'(Meta, KV, E) when not is_list(KV) ->
elixir_errors:compile_error(Meta, ?m(E, file), "invalid arguments for try");
'try'(Meta, KV, E) ->
+4 -4
View File
@@ -9,7 +9,7 @@
-export([macro_for/3, local_for/3, local_for/4]).
-include("elixir.hrl").
-define(attr, '__locals_tracker').
-define(attr, {elixir, locals_tracker}).
-define(tracker, 'Elixir.Module.LocalsTracker').
macro_for(Module, Name, Arity) ->
@@ -75,7 +75,7 @@ setup(Module) ->
case code:is_loaded(?tracker) of
{file, _} ->
{ok, Pid} = ?tracker:start_link(),
ets:insert(Module, {?attr, Pid}),
ets:insert(elixir_module:data_table(Module), {?attr, Pid}),
ok;
false ->
ok
@@ -108,7 +108,7 @@ if_tracker(Module, Callback) ->
if_tracker(Module, ok, Callback).
if_tracker(Module, Default, Callback) ->
try ets:lookup_element(Module, ?attr, 2) of
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
Pid -> Callback(Pid)
catch
error:badarg -> Default
@@ -118,7 +118,7 @@ if_tracker(Module, Default, Callback) ->
cache_env(#{module := Module} = RE) ->
E = RE#{line := nil,vars := []},
try ets:lookup_element(Module, ?attr, 2) of
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
Pid ->
{Pid, ?tracker:cache_env(Pid, E)}
catch
+87 -88
View File
@@ -1,25 +1,27 @@
-module(elixir_module).
-export([compile/4, data_table/1, docs_table/1, is_open/1,
expand_callback/6, add_beam_chunk/3, format_error/1]).
-export([data_table/1, defs_table/1, clas_table/1, is_open/1,
compile/4, expand_callback/6, add_beam_chunk/3, format_error/1]).
-include("elixir.hrl").
-define(acc_attr, '__acc_attributes').
-define(docs_attr, '__docs_table').
-define(lexical_attr, '__lexical_tracker').
-define(persisted_attr, '__persisted_attributes').
-define(overridable_attr, '__overridable').
-define(location_attr, '__location').
-define(acc_attr, {elixir, acc_attributes}).
-define(lexical_attr, {elixir, lexical_tracker}).
-define(persisted_attr, {elixir, persisted_attributes}).
-define(overridable_attr, {elixir, overridable}).
-define(location_attr, {elixir, location}).
%% TABLE METHODS
data_table(Module) ->
Module.
ets:lookup_element(elixir_modules, Module, 2).
docs_table(Module) ->
ets:lookup_element(Module, ?docs_attr, 2).
defs_table(Module) ->
ets:lookup_element(elixir_modules, Module, 3).
clas_table(Module) ->
ets:lookup_element(elixir_modules, Module, 4).
is_open(Module) ->
Module == ets:info(Module, name).
ets:lookup(elixir_modules, Module) /= [].
%% Compilation hook
@@ -49,12 +51,12 @@ do_compile(Line, Module, Block, Vars, E) ->
check_module_availability(Line, File, Module),
Docs = elixir_compiler:get_opt(docs),
build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
{Data, Defs, Clas} = build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
try
{Result, NE} = eval_form(Line, Module, Block, Vars, E),
{Result, NE} = eval_form(Line, Module, Data, Block, Vars, E),
_ = case ets:lookup(data_table(Module), 'on_load') of
_ = case ets:lookup(Data, 'on_load') of
[] -> ok;
[{on_load,OnLoad}] ->
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
@@ -65,14 +67,14 @@ do_compile(Line, Module, Block, Vars, E) ->
{All, Forms0} = functions_form(Line, File, Module, Def, Defp,
Defmacro, Defmacrop, Exports, Functions),
Forms1 = specs_form(Module, Defmacro, Defmacrop, Forms0),
Forms2 = types_form(Line, File, Module, Forms1),
Forms3 = attributes_form(Line, File, Module, Forms2),
Forms1 = specs_form(Data, Defmacro, Defmacrop, Forms0),
Forms2 = types_form(Line, File, Data, Forms1),
Forms3 = attributes_form(Line, File, Data, Forms2),
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
case Docs of
true -> warn_unused_docs(Line, File, Module, doc);
true -> warn_unused_docs(Line, File, Data, doc);
false -> false
end,
@@ -83,65 +85,67 @@ do_compile(Line, Module, Block, Vars, E) ->
{attribute, Line, module, Module} | Forms3
],
Binary = load_form(Line, Final, compile_opts(Module), NE),
Binary = load_form(Line, Data, Final, compile_opts(Module), NE),
{module, Module, Binary, Result}
after
elixir_locals:cleanup(Module),
elixir_def:cleanup(Module),
ets:delete(docs_table(Module)),
ets:delete(data_table(Module))
ets:delete(Data),
ets:delete(Defs),
ets:delete(Clas),
ets:delete(elixir_modules, Module)
end.
%% Hook that builds both attribute and functions and set up common hooks.
build(Line, File, Module, Docs, Lexical) ->
%% Table with meta information about the module.
DataTable = data_table(Module),
OldTable = ets:info(DataTable, name),
case OldTable == DataTable of
true ->
[{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2),
case ets:lookup(elixir_modules, Module) of
[{Module, _, _, _, OldLine, OldFile}] ->
Error = {module_in_definition, Module, OldFile, OldLine},
elixir_errors:form_error([{line, Line}], File, ?MODULE, Error);
false ->
_ ->
[]
end,
DataTable = ets:new(DataTable, [set, named_table, public]),
ets:insert(DataTable, {before_compile, []}),
ets:insert(DataTable, {after_compile, []}),
Data = ets:new(Module, [set, public]),
Defs = ets:new(Module, [set, public]),
Clas = ets:new(Module, [bag, public]),
ets:insert(elixir_modules, {Module, Data, Defs, Clas, Line, File}),
ets:insert(Data, {before_compile, []}),
ets:insert(Data, {after_compile, []}),
ets:insert(Data, {moduledoc, nil}),
case Docs of
true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]});
_ -> ets:insert(DataTable, {on_definition, []})
true -> ets:insert(Data, {on_definition, [{'Elixir.Module', compile_doc}]});
_ -> ets:insert(Data, {on_definition, []})
end,
Attributes = [behaviour, on_load, spec, type, typep, opaque, callback, compile, external_resource],
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition, derive|Attributes]}),
ets:insert(DataTable, {?persisted_attr, [vsn|Attributes]}),
ets:insert(DataTable, {?docs_attr, ets:new(DataTable, [ordered_set, public])}),
ets:insert(DataTable, {?lexical_attr, Lexical}),
ets:insert(DataTable, {?overridable_attr, []}),
ets:insert(DataTable, {?location_attr, [{File, Line}]}),
Attributes = [behaviour, on_load, compile, external_resource],
ets:insert(Data, {?acc_attr, [before_compile, after_compile, on_definition, derive,
spec, type, typep, opaque, callback|Attributes]}),
ets:insert(Data, {?persisted_attr, [vsn|Attributes]}),
ets:insert(Data, {?lexical_attr, Lexical}),
%% Setup other modules
%% Setup definition related modules
elixir_def:setup(Module),
elixir_locals:setup(Module),
ok.
elixir_def_overridable:setup(Module),
{Data, Defs, Clas}.
%% Receives the module representation and evaluates it.
eval_form(Line, Module, Block, Vars, E) ->
eval_form(Line, Module, Data, Block, Vars, E) ->
{Value, EE} = elixir_compiler:eval_forms(Block, Vars, E),
elixir_def_overridable:store_pending(Module),
EV = elixir_env:linify({Line, EE#{vars := [], export_vars := nil}}),
EC = eval_callbacks(Line, Module, before_compile, [EV], EV),
EC = eval_callbacks(Line, Data, before_compile, [EV], EV),
elixir_def_overridable:store_pending(Module),
{Value, EC}.
eval_callbacks(Line, Module, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
eval_callbacks(Line, Data, Name, Args, E) ->
Callbacks = lists:reverse(ets:lookup_element(Data, Name, 2)),
lists:foldl(fun({M,F}, Acc) ->
expand_callback(Line, M, F, Args, Acc#{vars := [], export_vars := nil},
@@ -160,13 +164,11 @@ functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Exports, Body
%% Add attributes handling to the form
attributes_form(Line, File, Module, Current) ->
Table = data_table(Module),
attributes_form(Line, File, Data, Current) ->
AccAttrs = ets:lookup_element(Data, ?acc_attr, 2),
PersistedAttrs = ets:lookup_element(Data, ?persisted_attr, 2),
AccAttrs = ets:lookup_element(Table, '__acc_attributes', 2),
PersistedAttrs = ets:lookup_element(Table, '__persisted_attributes', 2),
Transform = fun({Key, Value}, Acc) ->
Transform = fun({Key, Value}, Acc) when is_atom(Key) ->
case lists:member(Key, PersistedAttrs) of
false -> Acc;
true ->
@@ -182,7 +184,8 @@ attributes_form(Line, File, Module, Current) ->
end
end,
ets:foldl(Transform, Current, Table).
Results = ets:select(Data, [{{'$1', '_'}, [{is_atom, '$1'}], ['$_']}]),
lists:foldl(Transform, Current, Results).
process_attribute(Line, File, external_resource, Values) ->
lists:usort([process_external_resource(Line, File, Value) || Value <- Values]);
@@ -197,22 +200,16 @@ process_external_resource(Line, File, Value) ->
%% Types
types_form(Line, File, Module, Forms0) ->
types_form(Line, File, Data, Forms0) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
'Elixir.Module':get_attribute(Module, typep) ++
'Elixir.Module':get_attribute(Module, opaque),
Types0 = get_typespec(Data, type) ++ get_typespec(Data, typep)
++ get_typespec(Data, opaque),
Types1 = ['Elixir.Kernel.Typespec':translate_type(Kind, Expr, Doc, Caller) ||
{Kind, Expr, Doc, Caller} <- Types0],
'Elixir.Module':delete_attribute(Module, type),
'Elixir.Module':delete_attribute(Module, typep),
'Elixir.Module':delete_attribute(Module, opaque),
warn_unused_docs(Line, File, Module, typedoc),
warn_unused_docs(Line, File, Data, typedoc),
Forms1 = types_attributes(Types1, Forms0),
Forms2 = export_types_attributes(Types1, Forms1),
typedocs_attributes(Types1, Forms2);
@@ -247,20 +244,15 @@ typedocs_attributes(Types, Forms) ->
%% Specs
specs_form(Module, Defmacro, Defmacrop, Forms) ->
specs_form(Data, Defmacro, Defmacrop, Forms) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
'Elixir.Module':get_attribute(Module, callback),
Specs0 = get_typespec(Data, spec) ++ get_typespec(Data, callback),
Specs1 = ['Elixir.Kernel.Typespec':translate_spec(Kind, Expr, Caller) ||
{Kind, Expr, Caller} <- Specs0],
Specs2 = lists:flatmap(fun(Spec) ->
translate_macro_spec(Spec, Defmacro, Defmacrop)
end, Specs1),
'Elixir.Module':delete_attribute(Module, spec),
'Elixir.Module':delete_attribute(Module, callback),
specs_attributes(Forms, Specs2);
{error, _} ->
@@ -307,11 +299,11 @@ compile_opts(Module) ->
[] -> []
end.
load_form(Line, Forms, Opts, E) ->
load_form(Line, Data, Forms, Opts, E) ->
elixir_compiler:module(Forms, Opts, E, fun(Module, Binary0) ->
Docs = elixir_compiler:get_opt(docs),
Binary = add_docs_chunk(Binary0, Module, Line, Docs),
eval_callbacks(Line, Module, after_compile, [E, Binary], E),
Binary = add_docs_chunk(Binary0, Data, Line, Docs),
eval_callbacks(Line, Data, after_compile, [E, Binary], E),
case get(elixir_compiled) of
Current when is_list(Current) ->
@@ -331,23 +323,30 @@ load_form(Line, Forms, Opts, E) ->
Binary
end).
add_docs_chunk(Bin, Module, Line, true) ->
add_docs_chunk(Bin, Data, Line, true) ->
ChunkData = term_to_binary({elixir_docs_v1, [
{docs, get_docs(Module)},
{moduledoc, get_moduledoc(Line, Module)}
]}),
{docs, get_docs(Data)},
{moduledoc, get_moduledoc(Line, Data)}
]}),
add_beam_chunk(Bin, "ExDc", ChunkData);
add_docs_chunk(Bin, _, _, _) -> Bin.
get_docs(Module) ->
ordsets:from_list(
get_docs(Data) ->
Match = ets:match(Data, {{doc, '$1'}, '$2', '$3', '$4', '$5'}),
lists:usort(
[{Tuple, Line, Kind, Sig, Doc} ||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
Kind =/= type, Kind =/= opaque]).
[Tuple, Line, Kind, Sig, Doc] <- Match,
Kind =/= type, Kind =/= opaque]).
get_moduledoc(Line, Module) ->
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
get_moduledoc(Line, Data) ->
{Line, ets:lookup_element(Data, moduledoc, 2)}.
get_typespec(Data, Key) ->
case ets:lookup(Data, Key) of
[{Key, Value}] -> Value;
[] -> []
end.
check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
@@ -370,8 +369,8 @@ check_module_availability(Line, File, Module) ->
ok
end.
warn_unused_docs(Line, File, Module, Attribute) ->
case ets:member(data_table(Module), Attribute) of
warn_unused_docs(Line, File, Data, Attribute) ->
case ets:member(Data, Attribute) of
true ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_doc, Attribute});
_ ->
+1
View File
@@ -28,4 +28,5 @@ init(ok) ->
}
],
_ = ets:new(elixir_modules, [set, public, named_table, {read_concurrency, true}]),
{ok, {{one_for_one, 3, 10}, Workers}}.
@@ -20,6 +20,10 @@ defmodule Kernel.ErrorsTest do
"nofile:1: syntax error before: \"world\"",
'"hello" "world"'
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: Foobar",
'1 Foobar'
assert_compile_fail SyntaxError,
"nofile:1: syntax error before: foo",
'Foo.:foo'
@@ -392,11 +392,6 @@ defmodule Kernel.ExpansionTest do
## Try
test "try: expands do" do
assert expand(quote do: (try do x = y end; x)) ==
quote do: (try do x = y() end; x())
end
test "try: expands catch" do
assert expand(quote do: (try do x catch x, y -> z = :erlang.+(x, y) end; z)) ==
quote do: (try do x() catch x, y -> z = :erlang.+(x, y) end; z())
@@ -417,6 +412,12 @@ defmodule Kernel.ExpansionTest do
quote do: (try do x() rescue unquote(:in)(x, _) -> x; unquote(:in)(_, [:"Elixir.Error"]) -> x() end; x())
end
test "try: expects more than do" do
assert_raise CompileError, ~r"missing catch/rescue/after/else keyword in try", fn ->
expand(quote do: (try do x = y end; x))
end
end
## Binaries
test "bitstrings: size * unit" do
@@ -375,6 +375,20 @@ defmodule Kernel.WarningTest do
purge Sample
end
test :badarg_warning do
assert capture_err(fn ->
assert_raise ArgumentError, fn ->
Code.eval_string """
defmodule Sample do
Atom.to_string "abc"
end
"""
end
end) =~ "warning: this expression will fail with ArgumentError"
after
purge [Sample]
end
test :undefined_function_for_behaviour do
assert capture_err(fn ->
Code.eval_string """
+6 -1
View File
@@ -53,13 +53,18 @@ defmodule IEx.CLI do
if tty_works? do
:user_drv.start([:"tty_sl -c -e", tty_args])
else
:user.start
:application.set_env(:stdlib, :shell_prompt_func,
{__MODULE__, :prompt})
:user.start()
unless match? {:win32, _}, :os.type do
IO.puts "Warning: could not run smart terminal, falling back to dumb one"
end
local_start()
end
end
def prompt(_n) do
[]
end
# Check if tty works. If it does not, we fall back to the
# simple/dumb terminal. This is starting the linked in
+10 -6
View File
@@ -15,7 +15,7 @@ defmodule Mix.Compilers.Elixir do
"""
def compile(manifest, srcs, skip, exts, dest, force, on_start) do
all = Mix.Utils.extract_files(srcs -- skip, exts)
{all_entries, skip_entries} = read_manifest(manifest, skip)
{all_entries, skip_entries} = read_manifest(manifest, dest, skip)
removed =
for {_b, _m, source, _d, _f, _bin} <- all_entries, not(source in all), do: source
@@ -181,28 +181,32 @@ defmodule Mix.Compilers.Elixir do
# Reads the manifest returning the results as tuples.
# The beam files are read, removed and stored in memory.
defp read_manifest(manifest, skip_paths) do
defp read_manifest(manifest, dest, skip_paths) do
initial = {[], []}
case File.read(manifest) do
{:ok, contents} ->
skip_paths = Enum.map(skip_paths, &(&1 <> "/"))
Enum.reduce String.split(contents, "\n"), initial, fn x, acc ->
read_manifest_entry(String.split(x, "\t"), acc, skip_paths)
read_manifest_entry(String.split(x, "\t"), acc, dest, skip_paths)
end
{:error, _} ->
initial
end
end
defp read_manifest_entry([beam, module, source|deps], {keep, skip}, skip_paths) do
defp read_manifest_entry([_beam, module, source|deps], {keep, skip}, dest, skip_paths) do
{deps, files} =
case Enum.split_while(deps, &(&1 != "Elixir")) do
{deps, ["Elixir"|files]} -> {deps, files}
{deps, _} -> {deps, []}
end
entry = {beam, module, source, deps, files, nil}
# TODO: Notice we do not use beam from the file.
# Once Elixir v1.1 is out, we can start writing "1"
# instead of the beam file in write_manifest/2.
entry = {Path.join(dest, module <> ".beam"), module, source,
deps, files, nil}
if String.starts_with?(source, skip_paths) do
{keep, [entry|skip]}
@@ -211,7 +215,7 @@ defmodule Mix.Compilers.Elixir do
end
end
defp read_manifest_entry(_, acc, _skip_paths) do
defp read_manifest_entry(_, acc, _dest, _skip_paths) do
acc
end
+2 -2
View File
@@ -39,8 +39,8 @@ defmodule Mix.Tasks.Deps.Clean do
do_clean(apps, build, deps)
true ->
Mix.raise "mix deps.clean expects dependencies as arguments or " <>
"a flag indicating which dependencies to clean " <>
"The --all option will clean all dependencies while"
"a flag indicating which dependencies to clean. " <>
"The --all option will clean all dependencies while " <>
"the --unused option cleans unused dependencies."
end
@@ -65,8 +65,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
assert Mix.Tasks.Compile.Elixir.run([]) == :ok
refute File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam")
refute Code.ensure_loaded?(A)
refute String.contains?(File.read!("_build/dev/lib/sample/.compile.elixir"),
"Elixir.A.beam")
refute String.contains?(File.read!("_build/dev/lib/sample/.compile.elixir"), "Elixir.A")
end
end
+2 -2
View File
@@ -552,8 +552,8 @@ defmodule Mix.Tasks.DepsTest do
File.mkdir_p!("_build/test/lib/git_repo")
message = "mix deps.clean expects dependencies as arguments or " <>
"a flag indicating which dependencies to clean " <>
"The --all option will clean all dependencies while"
"a flag indicating which dependencies to clean. " <>
"The --all option will clean all dependencies while " <>
"the --unused option cleans unused dependencies."
assert_raise Mix.Error, message, fn ->
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.0.1"},
{vsn, "1.0.2"},
{modules, [
elixir
]},