Erlang error improvements (#1960)

This commit is contained in:
Benedikt Reinartz 2023-06-06 13:52:50 +02:00 committed by GitHub
parent dd5850502d
commit 377b677a6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 105 additions and 19 deletions

View file

@ -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()

View file

@ -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)}

View file

@ -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