Use unambiguous mapping between erlang and elixir variable names (#2556)

This commit is contained in:
Jonatan Kłosko 2024-04-09 12:35:00 +02:00 committed by GitHub
parent 1cac107402
commit 6d994a5542
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 2 deletions

View file

@ -808,17 +808,47 @@ defmodule Livebook.Runtime.Evaluator do
defp elixir_to_erlang_var(name) do
name
|> :erlang.atom_to_binary()
|> Macro.camelize()
|> toggle_var_case()
|> :erlang.binary_to_atom()
end
defp erlang_to_elixir_var(name) do
name
|> :erlang.atom_to_binary()
|> Macro.underscore()
|> toggle_var_case()
|> :erlang.binary_to_atom()
end
# Unambiguously maps variable names from camel case to underscore
# case, and vice-versa. The mapping is defined as follows:
#
# 1. The first character case is changed
#
# 2. Underscore followed by lower character maps to upper character,
# and vice-versa
#
defp toggle_var_case(<<h, t::binary>>) do
do_toggle_var_case(<<toggle_char_case(h)>>, t)
end
defp do_toggle_var_case(acc, <<?_, h, t::binary>>) when h >= ?a and h <= ?z do
do_toggle_var_case(<<acc::binary, toggle_char_case(h)>>, t)
end
defp do_toggle_var_case(acc, <<h, t::binary>>) when h >= ?A and h <= ?Z do
do_toggle_var_case(<<acc::binary, ?_, toggle_char_case(h)>>, t)
end
defp do_toggle_var_case(acc, <<h, t::binary>>) do
do_toggle_var_case(<<acc::binary, h>>, t)
end
defp do_toggle_var_case(acc, <<>>), do: acc
defp toggle_char_case(char) when char >= ?a and char <= ?z, do: char - 32
defp toggle_char_case(char) when char >= ?A and char <= ?Z, do: char + 32
defp toggle_char_case(char), do: char
defp filter_erlang_code_markers(code_markers) do
Enum.reject(code_markers, &(&1.line == 0))
end

View file

@ -1268,6 +1268,48 @@ defmodule Livebook.Runtime.EvaluatorTest do
assert [{:z, 1}, {:y, 1}, {:x, 1}] == binding
end
test "uses unambiguous camelization for erlang/elixir bindings", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, :erlang, "{JSON, JsOn, JsON} = {1, 2, 3}.", :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, terminal_text(_), metadata()}
Evaluator.evaluate_code(
evaluator,
:elixir,
"""
assertion1 = {j_s_o_n, js_on, js_o_n} == {1, 2, 3}
{j_s_o_n, js_on, js_o_n} = {11, 12, 13}
""",
:code_2,
[:code_1]
)
assert_receive {:runtime_evaluation_response, :code_2, terminal_text(_), metadata()}
Evaluator.evaluate_code(
evaluator,
:erlang,
"""
Assertion2 = {JSON, JsOn, JsON} =:= {11, 12, 13}.
""",
:code_3,
[:code_2]
)
assert_receive {:runtime_evaluation_response, :code_3, terminal_text(_), metadata()}
%{binding: binding} =
Evaluator.get_evaluation_context(evaluator, [:code_3, :code_2, :code_1])
assert [
{:assertion2, true},
{:js_on, 12},
{:js_o_n, 13},
{:j_s_o_n, 11},
{:assertion1, true}
] == binding
end
test "inspects erlang results using erlang format", %{evaluator: evaluator} do
code = ~S"#{x=>1}."
Evaluator.evaluate_code(evaluator, :erlang, code, :code_1, [], file: "file.ex")