mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-08 14:04:31 +08:00
Erlang error improvements (#1960)
This commit is contained in:
parent
dd5850502d
commit
377b677a6e
3 changed files with 105 additions and 19 deletions
|
@ -683,7 +683,7 @@ defmodule Livebook.Runtime.Evaluator do
|
|||
:erl_eval.add_binding(elixir_to_erlang_var(name), value, erl_binding)
|
||||
end)
|
||||
|
||||
with {:ok, tokens, _} <- :erl_scan.string(String.to_charlist(code)),
|
||||
with {:ok, tokens, _} <- :erl_scan.string(String.to_charlist(code), {1, 1}, [:text]),
|
||||
{:ok, parsed} <- :erl_parse.parse_exprs(tokens),
|
||||
{:value, result, new_erl_binding} <- :erl_eval.exprs(parsed, erl_binding) do
|
||||
# Simple heuristic to detect the used variables. We look at
|
||||
|
@ -720,26 +720,12 @@ defmodule Livebook.Runtime.Evaluator do
|
|||
{{:ok, result, binding, env}, []}
|
||||
else
|
||||
# Tokenizer error
|
||||
{:error, err, location} ->
|
||||
code_marker = %{
|
||||
line: :erl_anno.line(location),
|
||||
severity: :error,
|
||||
description: "Tokenizer #{err}"
|
||||
}
|
||||
|
||||
{{:error, :error, {:token, err}, []}, filter_erlang_code_markers([code_marker])}
|
||||
{:error, {location, module, description}, _end_loc} ->
|
||||
process_erlang_error(env, code, location, module, description)
|
||||
|
||||
# Parser error
|
||||
{:error, {location, _module, err}} ->
|
||||
err = :erlang.list_to_binary(err)
|
||||
|
||||
code_marker = %{
|
||||
line: :erl_anno.line(location),
|
||||
severity: :error,
|
||||
description: "Parser #{err}"
|
||||
}
|
||||
|
||||
{{:error, :error, err, []}, filter_erlang_code_markers([code_marker])}
|
||||
{:error, {location, module, description}} ->
|
||||
process_erlang_error(env, code, location, module, description)
|
||||
end
|
||||
catch
|
||||
kind, error ->
|
||||
|
@ -748,6 +734,71 @@ defmodule Livebook.Runtime.Evaluator do
|
|||
end
|
||||
end
|
||||
|
||||
defp process_erlang_error(env, code, location, module, description) do
|
||||
line = :erl_anno.line(location)
|
||||
|
||||
formatted =
|
||||
module.format_error(description)
|
||||
|> :erlang.list_to_binary()
|
||||
|
||||
code_marker = %{
|
||||
line: line,
|
||||
severity: :error,
|
||||
description: "#{module}: #{formatted}"
|
||||
}
|
||||
|
||||
error_cons =
|
||||
case {module, description} do
|
||||
{:erl_parse, [~c"syntax error before: ", []]} ->
|
||||
&TokenMissingError.exception/1
|
||||
|
||||
_ ->
|
||||
&SyntaxError.exception/1
|
||||
end
|
||||
|
||||
error =
|
||||
error_cons.(
|
||||
file: env.file,
|
||||
line: line,
|
||||
column:
|
||||
case :erl_anno.column(location) do
|
||||
:undefined -> 1
|
||||
val -> val
|
||||
end,
|
||||
description: formatted,
|
||||
snippet: make_snippet(code, location)
|
||||
)
|
||||
|
||||
{{:error, :error, error, []}, filter_erlang_code_markers([code_marker])}
|
||||
end
|
||||
|
||||
defp make_snippet(code, location) do
|
||||
start_line = 1
|
||||
start_column = 1
|
||||
line = :erl_anno.line(location)
|
||||
|
||||
case :erl_anno.column(location) do
|
||||
:undefined ->
|
||||
nil
|
||||
|
||||
column ->
|
||||
lines = :string.split(code, "\n", :all)
|
||||
snippet = :lists.nth(line - start_line + 1, lines)
|
||||
|
||||
offset =
|
||||
if line == start_line do
|
||||
column - start_column
|
||||
else
|
||||
column - 1
|
||||
end
|
||||
|
||||
case :string.trim(code, :leading) do
|
||||
[] -> nil
|
||||
_ -> %{content: snippet, offset: offset}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp elixir_to_erlang_var(name) do
|
||||
name
|
||||
|> :erlang.atom_to_binary()
|
||||
|
|
|
@ -31,6 +31,19 @@ defmodule Livebook.Runtime.Evaluator.Formatter do
|
|||
to_output(value)
|
||||
end
|
||||
|
||||
def format_result({:error, kind, error, stacktrace}, :erlang) do
|
||||
if is_exception(error) do
|
||||
format_result({:error, kind, error, stacktrace}, :elixir)
|
||||
else
|
||||
formatted =
|
||||
:erl_error.format_exception(kind, error, stacktrace)
|
||||
|> error_color
|
||||
|> :erlang.list_to_binary()
|
||||
|
||||
{:error, formatted, error_type(error)}
|
||||
end
|
||||
end
|
||||
|
||||
def format_result({:error, kind, error, stacktrace}, _language) do
|
||||
formatted = format_error(kind, error, stacktrace)
|
||||
{:error, formatted, error_type(error)}
|
||||
|
|
|
@ -1168,6 +1168,28 @@ defmodule Livebook.Runtime.EvaluatorTest do
|
|||
|
||||
assert metadata.code_markers == []
|
||||
end
|
||||
|
||||
test "syntax and tokenizer errors are converted", %{evaluator: evaluator} do
|
||||
# Incomplete input
|
||||
Evaluator.evaluate_code(evaluator, :erlang, "X =", :code_1, [])
|
||||
assert_receive {:runtime_evaluation_response, :code_1, {:error, message, _}, metadata()}
|
||||
assert "\e[31m** (TokenMissingError)" <> _ = message
|
||||
|
||||
# Parser error
|
||||
Evaluator.evaluate_code(evaluator, :erlang, "X ==/== a.", :code_2, [])
|
||||
assert_receive {:runtime_evaluation_response, :code_2, {:error, message, _}, metadata()}
|
||||
assert "\e[31m** (SyntaxError)" <> _ = message
|
||||
|
||||
# Tokenizer error
|
||||
Evaluator.evaluate_code(evaluator, :erlang, "$a$", :code_3, [])
|
||||
assert_receive {:runtime_evaluation_response, :code_3, {:error, message, _}, metadata()}
|
||||
assert "\e[31m** (SyntaxError)" <> _ = message
|
||||
|
||||
# Erlang exception
|
||||
Evaluator.evaluate_code(evaluator, :erlang, "list_to_binary(1).", :code_4, [])
|
||||
assert_receive {:runtime_evaluation_response, :code_4, {:error, message, _}, metadata()}
|
||||
assert "\e[31mexception error: bad argument" <> _ = message
|
||||
end
|
||||
end
|
||||
|
||||
describe "formatting" do
|
||||
|
|
Loading…
Add table
Reference in a new issue