Skip to content

Commit 670d89b

Browse files
committed
Merge pull request atomvm#1633 from pguyot/w15/add-io_lib-functions
Add several missing io_lib functions See also atomvm#1509 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 510122a + 13d2c75 commit 670d89b

File tree

6 files changed

+247
-57
lines changed

6 files changed

+247
-57
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3131
- Added `lists:flatmap/2`
3232
- Added `io:fwrite/1,2,3` and `io:format/3` as well as few io functions required by remote shell
3333
- Added `code:is_loaded/1` and `code:which/1`
34+
- Added several `io_lib` functions including `io_lib:fwrite/2` and `io_lib:write_atom/1`
3435

3536
### Fixed
3637
- ESP32: improved sntp sync speed from a cold boot.

libs/estdlib/src/erlang.erl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
list_to_integer/1,
5858
list_to_integer/2,
5959
list_to_tuple/1,
60+
iolist_size/1,
6061
iolist_to_binary/1,
6162
binary_to_atom/1,
6263
binary_to_atom/2,
@@ -665,13 +666,24 @@ list_to_integer(_String, _Base) ->
665666
list_to_tuple(_List) ->
666667
erlang:nif_error(undefined).
667668

669+
%%-----------------------------------------------------------------------------
670+
%% @equiv byte_size(iolist_to_binary(IOList))
671+
%% @param IOList IO list to compute the binary size of
672+
%% @returns the number of bytes of IOList if it was convered to `binary()'
673+
%% @doc Compute the length in bytes of the IO list or `binary()'
674+
%% @end
675+
%%-----------------------------------------------------------------------------
676+
-spec iolist_size(IOList :: iodata()) -> non_neg_integer().
677+
iolist_size(_IOList) ->
678+
erlang:nif_error(undefined).
679+
668680
%%-----------------------------------------------------------------------------
669681
%% @param IOList IO list to convert to binary
670682
%% @returns a binary with the bytes of the IO list
671683
%% @doc Convert an IO list to binary.
672684
%% @end
673685
%%-----------------------------------------------------------------------------
674-
-spec iolist_to_binary(IOList :: iolist()) -> binary().
686+
-spec iolist_to_binary(IOList :: iodata()) -> binary().
675687
iolist_to_binary(_IOList) ->
676688
erlang:nif_error(undefined).
677689

libs/estdlib/src/io_lib.erl

Lines changed: 149 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,53 @@
2727
%%-----------------------------------------------------------------------------
2828
-module(io_lib).
2929

30-
-export([format/2, latin1_char_list/1]).
30+
-export([
31+
write/1,
32+
format/2,
33+
fwrite/2,
34+
latin1_char_list/1,
35+
write_atom/1,
36+
printable_list/1,
37+
write_string/1,
38+
write_string/2,
39+
chars_length/1
40+
]).
41+
42+
-export_type([
43+
chars/0
44+
]).
45+
46+
-type chars() :: [char() | chars()].
47+
48+
%%-----------------------------------------------------------------------------
49+
%% @equiv format(Format, Args)
50+
%% @param Format format string
51+
%% @param Args format argument
52+
%% @returns string
53+
%% @doc Format string and data to a string.
54+
%% Features most of OTP `io_lib:format/2'.
55+
%% Raises `badarg' error if the number of format specifiers
56+
%% does not match the length of the Args.
57+
%% @end
58+
%%-----------------------------------------------------------------------------
59+
fwrite(Format, Args) ->
60+
format(Format, Args).
3161

3262
%%-----------------------------------------------------------------------------
3363
%% @param Format format string
3464
%% @param Args format argument
3565
%% @returns string
3666
%% @doc Format string and data to a string.
37-
%% Approximates features of OTP io_lib:format/2, but
38-
%% only supports ~p and ~n format specifiers.
67+
%% Features most of OTP `io_lib:format/2'.
3968
%% Raises `badarg' error if the number of format specifiers
4069
%% does not match the length of the Args.
4170
%% @end
4271
%%-----------------------------------------------------------------------------
43-
-spec format(Format :: string(), Args :: list()) -> string().
72+
-spec format(Format :: io:format(), Args :: list()) -> string().
73+
format(Format, Args) when is_binary(Format) ->
74+
format(binary_to_list(Format), Args);
75+
format(Format, Args) when is_atom(Format) ->
76+
format(atom_to_list(Format), Args);
4477
format(Format, Args) ->
4578
{FormatTokens, Instr} = split(Format),
4679
case length(FormatTokens) == length(Args) + 1 of
@@ -236,11 +269,7 @@ format_string(Format, T) ->
236269
format_spw(#format{control = s}, T) when is_atom(T) ->
237270
erlang:atom_to_list(T);
238271
format_spw(_Format, T) when is_atom(T) ->
239-
AtomStr = erlang:atom_to_list(T),
240-
case atom_requires_quotes(T, AtomStr) of
241-
false -> AtomStr;
242-
true -> [$', AtomStr, $']
243-
end;
272+
write_atom(T);
244273
format_spw(#format{control = s, mod = t} = Format, T) when is_binary(T) ->
245274
case unicode:characters_to_list(T, utf8) of
246275
L when is_list(L) -> L;
@@ -254,8 +283,8 @@ format_spw(#format{control = Control, mod = t} = Format, T) when is_binary(T) ->
254283
L when is_list(L) ->
255284
FormattedStr =
256285
case {Control, test_string_class(L)} of
257-
{p, latin1_printable} -> format_p_string(L, []);
258-
{p, unicode} -> [format_p_string(L, []), "/utf8"];
286+
{p, latin1_printable} -> write_string(L, $");
287+
{p, unicode} -> [write_string(L, $"), "/utf8"];
259288
_ -> lists:join($,, [integer_to_list(B) || B <- L])
260289
end,
261290
[$<, $<, FormattedStr, $>, $>];
@@ -266,7 +295,7 @@ format_spw(#format{control = Control, mod = undefined}, T) when is_binary(T) ->
266295
L = erlang:binary_to_list(T),
267296
FormattedStr =
268297
case {Control, test_string_class(L)} of
269-
{p, latin1_printable} -> format_p_string(L, []);
298+
{p, latin1_printable} -> write_string(L, $");
270299
_ -> lists:join($,, [integer_to_list(B) || B <- L])
271300
end,
272301
[$<, $<, FormattedStr, $>, $>];
@@ -279,7 +308,7 @@ format_spw(#format{control = s, mod = Mod}, L) when is_list(L) ->
279308
end;
280309
format_spw(#format{control = p} = Format, L) when is_list(L) ->
281310
case test_string_class(L) of
282-
latin1_printable -> format_p_string(L, []);
311+
latin1_printable -> write_string(L, $");
283312
_ -> [$[, lists:join($,, [format_spw(Format, E) || E <- L]), $]]
284313
end;
285314
format_spw(#format{control = w} = Format, L) when is_list(L) ->
@@ -314,7 +343,6 @@ format_spw(#format{mod = Mod} = Format, T) when is_map(T) ->
314343
$}
315344
].
316345

317-
%% We will probably need to add 'maybe' with OTP 27
318346
-define(RESERVED_KEYWORDS, [
319347
'after',
320348
'and',
@@ -334,6 +362,7 @@ format_spw(#format{mod = Mod} = Format, T) when is_map(T) ->
334362
'fun',
335363
'if',
336364
'let',
365+
'maybe',
337366
'not',
338367
'of',
339368
'or',
@@ -345,23 +374,38 @@ format_spw(#format{mod = Mod} = Format, T) when is_map(T) ->
345374
'xor'
346375
]).
347376

348-
%% @private
349-
atom_requires_quotes(Atom, AtomStr) ->
377+
-spec write_atom(Atom :: atom()) -> chars().
378+
write_atom(Atom) ->
379+
AtomStr = erlang:atom_to_list(Atom),
350380
case lists:member(Atom, ?RESERVED_KEYWORDS) of
351-
true -> true;
352-
false -> atom_requires_quotes0(AtomStr)
381+
true -> [$', AtomStr, $'];
382+
false -> write_atom_maybe_quote_escape(AtomStr)
353383
end.
354384

355-
atom_requires_quotes0([C | _T]) when C < $a orelse C > $z -> true;
356-
atom_requires_quotes0([_C | T]) -> atom_requires_quotes1(T).
357-
358-
atom_requires_quotes1([]) -> false;
359-
atom_requires_quotes1([$@ | T]) -> atom_requires_quotes1(T);
360-
atom_requires_quotes1([$_ | T]) -> atom_requires_quotes1(T);
361-
atom_requires_quotes1([C | T]) when C >= $A andalso C =< $Z -> atom_requires_quotes1(T);
362-
atom_requires_quotes1([C | T]) when C >= $0 andalso C =< $9 -> atom_requires_quotes1(T);
363-
atom_requires_quotes1([C | T]) when C >= $a andalso C =< $z -> atom_requires_quotes1(T);
364-
atom_requires_quotes1(_) -> true.
385+
%% @private
386+
write_atom_maybe_quote_escape([C | _T] = AtomStr) when C < $a orelse C > $z ->
387+
write_atom_maybe_quote_escape(AtomStr, true, []);
388+
write_atom_maybe_quote_escape(AtomStr) ->
389+
write_atom_maybe_quote_escape(AtomStr, false, []).
390+
391+
write_atom_maybe_quote_escape([], false, Acc) ->
392+
lists:reverse(Acc);
393+
write_atom_maybe_quote_escape([], true, Acc) ->
394+
[$' | lists:reverse([$' | Acc])];
395+
write_atom_maybe_quote_escape([$@ | T], Quote, Acc) ->
396+
write_atom_maybe_quote_escape(T, Quote, [$@ | Acc]);
397+
write_atom_maybe_quote_escape([$_ | T], Quote, Acc) ->
398+
write_atom_maybe_quote_escape(T, Quote, [$_ | Acc]);
399+
write_atom_maybe_quote_escape([C | T], Quote, Acc) when C >= $A andalso C =< $Z ->
400+
write_atom_maybe_quote_escape(T, Quote, [C | Acc]);
401+
write_atom_maybe_quote_escape([C | T], Quote, Acc) when C >= $0 andalso C =< $9 ->
402+
write_atom_maybe_quote_escape(T, Quote, [C | Acc]);
403+
write_atom_maybe_quote_escape([C | T], Quote, Acc) when C >= $a andalso C =< $z ->
404+
write_atom_maybe_quote_escape(T, Quote, [C | Acc]);
405+
write_atom_maybe_quote_escape([$' | T], _Quote, Acc) ->
406+
write_atom_maybe_quote_escape(T, true, [$', $\\ | Acc]);
407+
write_atom_maybe_quote_escape([C | T], _Quote, Acc) ->
408+
write_atom_maybe_quote_escape(T, true, [C | Acc]).
365409

366410
%% @private
367411
format_integer(#format{control = C, precision = Precision0}, T0) when
@@ -447,8 +491,8 @@ format_char(_, _) ->
447491
%% @private
448492
%% String classes:
449493
%% latin1_printable
450-
%% latin1_unprintable
451494
%% unicode
495+
%% unprintable
452496
%% io_lib doesn't distinguish between valid unicode and invalid unicode
453497
%% characters. This is done with io, though, when actually writing the string.
454498
%% Compare:
@@ -464,38 +508,91 @@ test_string_class([H | T], Class) when is_integer(H) andalso H >= 0 ->
464508
case {Class, char_class(H)} of
465509
{_, latin1_printable} -> Class;
466510
{latin1_printable, CharClass} -> CharClass;
467-
{_, latin1_unprintable} -> Class;
468-
{latin1_unprintable, CharClass} -> CharClass;
469-
_ -> unicode
511+
{_, unicode} -> Class;
512+
{unicode, CharClass} -> CharClass;
513+
_ -> unprintable
470514
end,
471515
test_string_class(T, NewClass);
472516
test_string_class([], Class) ->
473517
Class;
474518
test_string_class(_String, _Class) ->
475519
not_a_string.
476520

477-
char_class(H) when H >= 0 andalso H < 8 -> latin1_unprintable;
521+
char_class(H) when H >= 0 andalso H < 8 -> unprintable;
478522
char_class(27) -> latin1_printable;
479-
char_class(H) when H >= 14 andalso H < 32 -> latin1_unprintable;
523+
char_class(H) when H >= 14 andalso H < 32 -> unprintable;
480524
char_class(H) when H < 256 -> latin1_printable;
481525
char_class(_H) -> unicode.
482526

527+
%%-----------------------------------------------------------------------------
528+
%% @equiv write_string(String, $")
529+
%% @param String string to print
530+
%% @doc Returns the list of characters needed to print String as a string.
531+
%% @end
532+
%%-----------------------------------------------------------------------------
533+
-spec write_string(string()) -> chars().
534+
write_string(String) ->
535+
write_string(String, $").
536+
537+
%% @private
538+
-spec write_string(string(), char()) -> chars().
539+
write_string(String, $") ->
540+
format_p_string(String, $", []).
541+
542+
%% @private
543+
format_p_string([], Q, Acc) ->
544+
[Q, lists:reverse(Acc), Q];
545+
format_p_string([8 | T], Q, Acc) ->
546+
format_p_string(T, Q, ["\\b" | Acc]);
547+
format_p_string([9 | T], Q, Acc) ->
548+
format_p_string(T, Q, ["\\t" | Acc]);
549+
format_p_string([10 | T], Q, Acc) ->
550+
format_p_string(T, Q, ["\\n" | Acc]);
551+
format_p_string([11 | T], Q, Acc) ->
552+
format_p_string(T, Q, ["\\v" | Acc]);
553+
format_p_string([12 | T], Q, Acc) ->
554+
format_p_string(T, Q, ["\\f" | Acc]);
555+
format_p_string([13 | T], Q, Acc) ->
556+
format_p_string(T, Q, ["\\r" | Acc]);
557+
format_p_string([27 | T], Q, Acc) ->
558+
format_p_string(T, Q, ["\\e" | Acc]);
559+
format_p_string([Q | T], Q, Acc) ->
560+
format_p_string(T, Q, ["\\", Q | Acc]);
561+
format_p_string([H | T], Q, Acc) ->
562+
format_p_string(T, Q, [H | Acc]).
563+
564+
%%-----------------------------------------------------------------------------
565+
%% @param List term to test
566+
%% @doc Determine if `List' is a flat list of printable characters
567+
%% @end
568+
%%-----------------------------------------------------------------------------
569+
-spec printable_list(List :: any()) -> boolean().
570+
printable_list(List) ->
571+
StringClass = test_string_class(List),
572+
case {StringClass, io:printable_range()} of
573+
{not_a_string, _} -> false;
574+
{unprintable, _} -> false;
575+
{latin1_printable, _} -> true;
576+
{unicode, unicode} -> true;
577+
{unicode, latin1} -> false
578+
end.
579+
483580
%% @private
484-
format_p_string([], Acc) ->
485-
[$", lists:reverse(Acc), $"];
486-
format_p_string([8 | T], Acc) ->
487-
format_p_string(T, ["\\b" | Acc]);
488-
format_p_string([9 | T], Acc) ->
489-
format_p_string(T, ["\\t" | Acc]);
490-
format_p_string([10 | T], Acc) ->
491-
format_p_string(T, ["\\n" | Acc]);
492-
format_p_string([11 | T], Acc) ->
493-
format_p_string(T, ["\\v" | Acc]);
494-
format_p_string([12 | T], Acc) ->
495-
format_p_string(T, ["\\f" | Acc]);
496-
format_p_string([13 | T], Acc) ->
497-
format_p_string(T, ["\\r" | Acc]);
498-
format_p_string([27 | T], Acc) ->
499-
format_p_string(T, ["\\e" | Acc]);
500-
format_p_string([H | T], Acc) ->
501-
format_p_string(T, [H | Acc]).
581+
-spec chars_length(chars()) -> non_neg_integer().
582+
chars_length(S) ->
583+
try
584+
iolist_size(S)
585+
catch
586+
_:_ ->
587+
string:length(S)
588+
end.
589+
590+
%%-----------------------------------------------------------------------------
591+
%% @equiv forma("~w", [Term])
592+
%% @param Term term to represent
593+
%% @doc Returns a character list that represents Term
594+
%% @end
595+
%%-----------------------------------------------------------------------------
596+
-spec write(any()) -> chars().
597+
write(Term) ->
598+
format("~w", [Term]).

libs/estdlib/src/string.erl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
%%-----------------------------------------------------------------------------
2828
-module(string).
2929

30-
-export([to_upper/1, to_lower/1, split/2, split/3, trim/1, trim/2, find/2, find/3]).
30+
-export([to_upper/1, to_lower/1, split/2, split/3, trim/1, trim/2, find/2, find/3, length/1]).
3131

3232
%%-----------------------------------------------------------------------------
3333
%% @param Input a string or character to convert
@@ -251,3 +251,14 @@ find_list([_C | Rest] = String, SearchPattern, leading) ->
251251
end;
252252
find_list([], _SearchPattern, leading) ->
253253
nomatch.
254+
255+
%%-----------------------------------------------------------------------------
256+
%% @param String string to compute the length of
257+
%% @doc Return the length of the string in characters.
258+
%% @end
259+
%%-----------------------------------------------------------------------------
260+
-spec length(String :: unicode:chardata()) -> non_neg_integer().
261+
length(String) when is_list(String) ->
262+
erlang:length(String);
263+
length(String) when is_binary(String) ->
264+
erlang:length(unicode:characters_to_list(String)).

0 commit comments

Comments
 (0)