Skip to content

Commit 67a8ae1

Browse files
committed
Merge pull request #1284 from bettio/fix-elixir-inspect
Fix Elixir `inspect()` Closer to Elixir one (and more reliable), but not yet perfect. e.g. structs are still printed as maps. These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 71988d3 + 36cb1b8 commit 67a8ae1

File tree

3 files changed

+130
-10
lines changed

3 files changed

+130
-10
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ instead
3838
- Fix memory corruption in `unicode:characters_to_binary`
3939
- Fix handling of large literal indexes
4040
- `unicode:characters_to_list`: fixed bogus out_of_memory error on some platforms such as ESP32
41+
- Fix crash in Elixir library when doing `inspect(:atom)`
42+
- General inspect() compliance with Elixir behavior (but there are still some minor differences)
4143

4244
## [0.6.4] - 2024-08-18
4345

libs/exavmlib/lib/Kernel.ex

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,19 @@ defmodule Kernel do
4242
def inspect(term, opts \\ []) when is_list(opts) do
4343
case term do
4444
t when is_atom(t) ->
45-
[?:, atom_to_string(t)]
45+
atom_to_string(t, ":")
4646

4747
t when is_integer(t) ->
4848
:erlang.integer_to_binary(t)
4949

5050
t when is_list(t) ->
51-
# TODO: escape unprintable lists
52-
:erlang.list_to_binary(t)
51+
if is_printable_list(t) do
52+
str = :erlang.list_to_binary(t)
53+
<<?'::utf8, str::binary, ?'::utf8>>
54+
else
55+
[?[ | t |> inspect_join(?])]
56+
|> :erlang.list_to_binary()
57+
end
5358

5459
t when is_pid(t) ->
5560
:erlang.pid_to_list(t)
@@ -64,15 +69,19 @@ defmodule Kernel do
6469
|> :erlang.list_to_binary()
6570

6671
t when is_binary(t) ->
67-
# TODO: escape unprintable binaries
68-
t
72+
if is_printable_binary(t) do
73+
<<?"::utf8, t::binary, ?"::utf8>>
74+
else
75+
["<<" | t |> :erlang.binary_to_list() |> inspect_join(">>")]
76+
|> :erlang.list_to_binary()
77+
end
6978

7079
t when is_reference(t) ->
7180
:erlang.ref_to_list(t)
7281
|> :erlang.list_to_binary()
7382

7483
t when is_float(t) ->
75-
:erlang.float_to_binary(t)
84+
:erlang.float_to_binary(term, [{:decimals, 17}, :compact])
7685

7786
t when is_map(t) ->
7887
[?%, ?{ | t |> inspect_kv() |> join(?})]
@@ -88,6 +97,10 @@ defmodule Kernel do
8897
[inspect(e), last]
8998
end
9099

100+
defp inspect_join([h | e], last) when not is_list(e) do
101+
[inspect(h), " | ", inspect(e), last]
102+
end
103+
91104
defp inspect_join([h | t], last) do
92105
[inspect(h), ?,, ?\s | inspect_join(t, last)]
93106
end
@@ -118,12 +131,56 @@ defmodule Kernel do
118131
)
119132
end
120133

121-
defp atom_to_string(atom) do
122-
# TODO: use unicode rather than plain latin1
123-
# handle spaces and special characters
124-
:erlang.atom_to_binary(atom, :latin1)
134+
defp atom_to_string(atom, prefix \\ "") do
135+
case atom do
136+
true ->
137+
"true"
138+
139+
false ->
140+
"false"
141+
142+
nil ->
143+
"nil"
144+
145+
any_atom ->
146+
case :erlang.atom_to_binary(any_atom) do
147+
<<"Elixir.", displayable::binary>> ->
148+
displayable
149+
150+
other ->
151+
<<prefix::binary, other::binary>>
152+
end
153+
end
125154
end
126155

156+
defp is_printable_list([]), do: false
157+
158+
defp is_printable_list([char]) do
159+
is_printable_ascii(char)
160+
end
161+
162+
defp is_printable_list([char | t]) do
163+
if is_printable_ascii(char) do
164+
is_printable_list(t)
165+
else
166+
false
167+
end
168+
end
169+
170+
defp is_printable_list(_any), do: false
171+
172+
defp is_printable_ascii(char) do
173+
is_integer(char) and char >= 32 and char < 127 and char != ?'
174+
end
175+
176+
defp is_printable_binary(<<>>), do: true
177+
178+
defp is_printable_binary(<<char::utf8, rest::binary>>) when char >= 32 do
179+
is_printable_binary(rest)
180+
end
181+
182+
defp is_printable_binary(_any), do: false
183+
127184
@doc """
128185
Returns the biggest of the two given terms according to
129186
Erlang's term ordering.

tests/libs/exavmlib/Tests.ex

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,19 @@
1919
#
2020

2121
defmodule Tests do
22+
# defstruct [
23+
# :field1,
24+
# field2: 42
25+
# ]
26+
2227
@compile {:no_warn_undefined, :undef}
2328

2429
def start() do
2530
:ok = IO.puts("Running Elixir tests")
2631
:ok = test_enum()
2732
:ok = test_exception()
2833
:ok = test_chars_protocol()
34+
:ok = test_inspect()
2935
:ok = IO.puts("Finished Elixir tests")
3036
end
3137

@@ -234,7 +240,62 @@ defmodule Tests do
234240
:ok
235241
end
236242

243+
def test_inspect() do
244+
"true" = inspect(true)
245+
"false" = inspect(false)
246+
"nil" = inspect(nil)
247+
248+
":test" = inspect(:test)
249+
":アトム" = inspect(:アトム)
250+
"Test" = inspect(Test)
251+
252+
"5" = inspect(5)
253+
"5.0" = inspect(5.0)
254+
255+
~s[""] = inspect("")
256+
~s["hello"] = inspect("hello")
257+
~s["アトム"] = inspect("アトム")
258+
259+
"<<10>>" = inspect("\n")
260+
"<<0, 1, 2, 3>>" = inspect(<<0, 1, 2, 3>>)
261+
"<<195, 168, 0>>" = inspect(<<195, 168, 0>>)
262+
263+
"[]" = inspect([])
264+
"[0]" = inspect([0])
265+
"[9, 10]" = inspect([9, 10])
266+
~s'["test"]' = inspect(["test"])
267+
"'hello'" = inspect('hello')
268+
"[127]" = inspect([127])
269+
"[104, 101, 108, 108, 248]" = inspect('hellø')
270+
271+
~s([5 | "hello"]) = inspect([5 | "hello"])
272+
273+
"{}" = inspect({})
274+
"{1, 2}" = inspect({1, 2})
275+
"{:test, 1}" = inspect({:test, 1})
276+
277+
"%{}" = inspect(%{})
278+
either("%{a: 1, b: 2}", "%{b: 2, a: 1}", inspect(%{a: 1, b: 2}))
279+
either(~s[%{"a" => 1, "b" => 2}], ~s[%{"b" => 2, "a" => 1}], inspect(%{"a" => 1, "b" => 2}))
280+
281+
# TODO: structs are not yet supported
282+
# either(
283+
# ~s[%#{__MODULE__}{field1: nil, field2: 42}],
284+
# ~s[%#{__MODULE__}{field2: 42, field1: nil}],
285+
# inspect(%__MODULE__{})
286+
# )
287+
288+
:ok
289+
end
290+
237291
defp fact(n) when n < 0, do: :test
238292
defp fact(0), do: 1
239293
defp fact(n), do: fact(n - 1) * n
294+
295+
def either(a, b, value) do
296+
case value do
297+
^a -> a
298+
^b -> b
299+
end
300+
end
240301
end

0 commit comments

Comments
 (0)