aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/src')
-rw-r--r--lib/stdlib/src/beam_lib.erl25
-rw-r--r--lib/stdlib/src/calendar.erl103
-rw-r--r--lib/stdlib/src/dets.erl23
-rw-r--r--lib/stdlib/src/epp.erl13
-rw-r--r--lib/stdlib/src/erl_eval.erl15
-rw-r--r--lib/stdlib/src/erl_internal.erl2
-rw-r--r--lib/stdlib/src/erl_lint.erl3
-rw-r--r--lib/stdlib/src/erl_parse.yrl9
-rw-r--r--lib/stdlib/src/erl_pp.erl6
-rw-r--r--lib/stdlib/src/erl_tar.erl4
-rw-r--r--lib/stdlib/src/filename.erl31
-rw-r--r--lib/stdlib/src/gen_statem.erl230
-rw-r--r--lib/stdlib/src/io_lib.erl67
-rw-r--r--lib/stdlib/src/io_lib_format.erl38
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl66
-rw-r--r--lib/stdlib/src/ms_transform.erl11
-rw-r--r--lib/stdlib/src/proc_lib.erl175
-rw-r--r--lib/stdlib/src/stdlib.appup.src53
18 files changed, 524 insertions, 350 deletions
diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl
index ef4c7d255c..3386cfcbe6 100644
--- a/lib/stdlib/src/beam_lib.erl
+++ b/lib/stdlib/src/beam_lib.erl
@@ -53,7 +53,7 @@
%%-------------------------------------------------------------------------
--type beam() :: module() | file:filename() | binary().
+-type beam() :: file:filename() | binary().
-type debug_info() :: {DbgiVersion :: atom(), Backend :: module(), Data :: term()} | 'no_debug_info'.
-type forms() :: [erl_parse:abstract_form() | erl_parse:form_info()].
@@ -690,30 +690,31 @@ chunk_to_data(debug_info=Id, Chunk, File, _Cs, AtomTable, Mod) ->
<<0:8,N:8,Mode0:N/binary,Rest/binary>> ->
Mode = binary_to_atom(Mode0, utf8),
Term = decrypt_chunk(Mode, Mod, File, Id, Rest),
- {AtomTable, {Id, Term}};
+ {AtomTable, {Id, anno_from_term(Term)}};
_ ->
case catch binary_to_term(Chunk) of
{'EXIT', _} ->
error({invalid_chunk, File, chunk_name_to_id(Id, File)});
Term ->
- {AtomTable, {Id, Term}}
+ {AtomTable, {Id, anno_from_term(Term)}}
end
end;
chunk_to_data(abstract_code=Id, Chunk, File, _Cs, AtomTable, Mod) ->
+ %% Before Erlang/OTP 20.0.
case Chunk of
<<>> ->
{AtomTable, {Id, no_abstract_code}};
<<0:8,N:8,Mode0:N/binary,Rest/binary>> ->
Mode = binary_to_atom(Mode0, utf8),
Term = decrypt_chunk(Mode, Mod, File, Id, Rest),
- {AtomTable, {Id, anno_from_term(Term)}};
+ {AtomTable, {Id, old_anno_from_term(Term)}};
_ ->
case catch binary_to_term(Chunk) of
{'EXIT', _} ->
error({invalid_chunk, File, chunk_name_to_id(Id, File)});
Term ->
try
- {AtomTable, {Id, anno_from_term(Term)}}
+ {AtomTable, {Id, old_anno_from_term(Term)}}
catch
_:_ ->
error({invalid_chunk, File,
@@ -947,14 +948,24 @@ decrypt_chunk(Type, Module, File, Id, Bin) ->
error({key_missing_or_invalid, File, Id})
end.
-anno_from_term({raw_abstract_v1, Forms}) ->
+old_anno_from_term({raw_abstract_v1, Forms}) ->
{raw_abstract_v1, anno_from_forms(Forms)};
-anno_from_term({Tag, Forms}) when Tag =:= abstract_v1; Tag =:= abstract_v2 ->
+old_anno_from_term({Tag, Forms}) when Tag =:= abstract_v1;
+ Tag =:= abstract_v2 ->
try {Tag, anno_from_forms(Forms)}
catch
_:_ ->
{Tag, Forms}
end;
+old_anno_from_term(T) ->
+ T.
+
+anno_from_term({debug_info_v1=Tag1, erl_abstract_code=Tag2, {Forms, Opts}}) ->
+ try {Tag1, Tag2, {anno_from_forms(Forms), Opts}}
+ catch
+ _:_ ->
+ {Tag1, Tag2, {Forms, Opts}}
+ end;
anno_from_term(T) ->
T.
diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl
index 9a600c1972..3a8fe2211b 100644
--- a/lib/stdlib/src/calendar.erl
+++ b/lib/stdlib/src/calendar.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -357,13 +357,17 @@ rfc3339_to_system_time(DateTimeString) ->
rfc3339_to_system_time(DateTimeString, Options) ->
Unit = proplists:get_value(unit, Options, second),
%% _T is the character separating the date and the time:
- {DateStr, [_T|TimeStr]} = lists:split(10, DateTimeString),
- {TimeStr2, TimeStr3} = lists:split(8, TimeStr),
- {ok, [Hour, Min, Sec], []} = io_lib:fread("~d:~d:~d", TimeStr2),
- {ok, [Year, Month, Day], []} = io_lib:fread("~d-~d-~d", DateStr),
+ [Y1, Y2, Y3, Y4, $-, Mon1, Mon2, $-, D1, D2, _T,
+ H1, H2, $:, Min1, Min2, $:, S1, S2 | TimeStr] = DateTimeString,
+ Hour = list_to_integer([H1, H2]),
+ Min = list_to_integer([Min1, Min2]),
+ Sec = list_to_integer([S1, S2]),
+ Year = list_to_integer([Y1, Y2, Y3, Y4]),
+ Month = list_to_integer([Mon1, Mon2]),
+ Day = list_to_integer([D1, D2]),
DateTime = {{Year, Month, Day}, {Hour, Min, Sec}},
IsFractionChar = fun(C) -> C >= $0 andalso C =< $9 orelse C =:= $. end,
- {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr3),
+ {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr),
Time = datetime_to_system_time(DateTime),
Secs = Time - offset_adjustment(Time, second, UtcOffset),
check(DateTimeString, Options, Secs),
@@ -451,8 +455,9 @@ system_time_to_rfc3339(Time, Options) ->
DateTime = system_time_to_datetime(Secs),
{{Year, Month, Day}, {Hour, Min, Sec}} = DateTime,
FractionStr = fraction_str(Factor, AdjustedTime),
- flat_fwrite("~4.10.0B-~2.10.0B-~2.10.0B~c~2.10.0B:~2.10.0B:~2.10.0B~s~s",
- [Year, Month, Day, T, Hour, Min, Sec, FractionStr, Offset]).
+ L = [pad4(Year), "-", pad2(Month), "-", pad2(Day), [T],
+ pad2(Hour), ":", pad2(Min), ":", pad2(Sec), FractionStr, Offset],
+ lists:append(L).
%% time_difference(T1, T2) = Tdiff
%%
@@ -529,24 +534,41 @@ valid_date({Y, M, D}) ->
%% day_to_year(DayOfEpoch) = {Year, DayOfYear}
%%
-%% The idea here is to first guess a year, and then adjust. Although
-%% the implementation is recursive, at most 1 or 2 recursive steps
+%% The idea here is to first set the upper and lower bounds for a year,
+%% and then adjust a range by interpolation search. Although complexity
+%% of the algorithm is log(log(n)), at most 1 or 2 recursive steps
%% are taken.
-%% If DayOfEpoch is very large, we need far more than 1 or 2 iterations,
-%% since we just subtract a yearful of days at a time until we're there.
%%
-spec day_to_year(non_neg_integer()) -> {year(), day_of_year()}.
day_to_year(DayOfEpoch) when DayOfEpoch >= 0 ->
- Y0 = DayOfEpoch div ?DAYS_PER_YEAR,
- {Y1, D1} = dty(Y0, DayOfEpoch, dy(Y0)),
+ YMax = DayOfEpoch div ?DAYS_PER_YEAR,
+ YMin = DayOfEpoch div ?DAYS_PER_LEAP_YEAR,
+ {Y1, D1} = dty(YMin, YMax, DayOfEpoch, dy(YMin), dy(YMax)),
{Y1, DayOfEpoch - D1}.
--spec dty(year(), non_neg_integer(), non_neg_integer()) ->
+-spec dty(year(), year(), non_neg_integer(), non_neg_integer(),
+ non_neg_integer()) ->
{year(), non_neg_integer()}.
-dty(Y, D1, D2) when D1 < D2 ->
- dty(Y-1, D1, dy(Y-1));
-dty(Y, _D1, D2) ->
- {Y, D2}.
+dty(Min, Max, _D1, DMin, _DMax) when Min == Max ->
+ {Min, DMin};
+dty(Min, Max, D1, DMin, DMax) ->
+ Diff = Max - Min,
+ Mid = Min + (Diff * (D1 - DMin)) div (DMax - DMin),
+ MidLength =
+ case is_leap_year(Mid) of
+ true -> ?DAYS_PER_LEAP_YEAR;
+ false -> ?DAYS_PER_YEAR
+ end,
+ case dy(Mid) of
+ D2 when D1 < D2 ->
+ NewMax = Mid - 1,
+ dty(Min, NewMax, D1, DMin, dy(NewMax));
+ D2 when D1 - D2 >= MidLength ->
+ NewMin = Mid + 1,
+ dty(NewMin, Max, D1, dy(NewMin), DMax);
+ D2 ->
+ {Mid, D2}
+ end.
%%
%% The Gregorian days of the iso week 01 day 1 for a given year.
@@ -663,7 +685,7 @@ offset(OffsetOption, Secs0) when OffsetOption =:= "";
Secs = abs(Secs0),
Hour = Secs div 3600,
Min = (Secs rem 3600) div 60,
- io_lib:fwrite("~c~2.10.0B:~2.10.0B", [Sign, Hour, Min]);
+ [Sign | lists:append([pad2(Hour), ":", pad2(Min)])];
offset(OffsetOption, _Secs) ->
OffsetOption.
@@ -678,8 +700,10 @@ offset_string_adjustment(_Time, _Unit, "Z") ->
0;
offset_string_adjustment(_Time, _Unit, "z") ->
0;
-offset_string_adjustment(_Time, _Unit, [Sign|Tz]) ->
- {ok, [Hour, Min], []} = io_lib:fread("~d:~d", Tz),
+offset_string_adjustment(_Time, _Unit, Tz) ->
+ [Sign, H1, H2, $:, M1, M2] = Tz,
+ Hour = list_to_integer([H1, H2]),
+ Min = list_to_integer([M1, M2]),
Adjustment = 3600 * Hour + 60 * Min,
case Sign of
$- -> -Adjustment;
@@ -687,20 +711,19 @@ offset_string_adjustment(_Time, _Unit, [Sign|Tz]) ->
end.
local_offset(SystemTime, Unit) ->
- LocalTime = system_time_to_local_time(SystemTime, Unit),
+ %% Not optimized for special cases.
UniversalTime = system_time_to_universal_time(SystemTime, Unit),
+ LocalTime = erlang:universaltime_to_localtime(UniversalTime),
LocalSecs = datetime_to_gregorian_seconds(LocalTime),
UniversalSecs = datetime_to_gregorian_seconds(UniversalTime),
LocalSecs - UniversalSecs.
+fraction_str(1, _Time) ->
+ "";
fraction_str(Factor, Time) ->
- case Time rem Factor of
- 0 ->
- "";
- Fraction ->
- FS = io_lib:fwrite(".~*..0B", [log10(Factor), abs(Fraction)]),
- string:trim(FS, trailing, "0")
- end.
+ Fraction = Time rem Factor,
+ S = integer_to_list(abs(Fraction)),
+ [$. | pad(log10(Factor) - length(S), S)].
fraction(second, _) ->
0;
@@ -721,5 +744,21 @@ log10(1000) -> 3;
log10(1000000) -> 6;
log10(1000000000) -> 9.
-flat_fwrite(F, S) ->
- lists:flatten(io_lib:fwrite(F, S)).
+pad(0, S) ->
+ S;
+pad(I, S) ->
+ [$0 | pad(I - 1, S)].
+
+pad2(N) when N < 10 ->
+ [$0 | integer_to_list(N)];
+pad2(N) ->
+ integer_to_list(N).
+
+pad4(N) when N < 10 ->
+ [$0, $0, $0 | integer_to_list(N)];
+pad4(N) when N < 100 ->
+ [$0, $0 | integer_to_list(N)];
+pad4(N) when N < 1000 ->
+ [$0 | integer_to_list(N)];
+pad4(N) ->
+ integer_to_list(N).
diff --git a/lib/stdlib/src/dets.erl b/lib/stdlib/src/dets.erl
index e016d5a80e..0488c2bef2 100644
--- a/lib/stdlib/src/dets.erl
+++ b/lib/stdlib/src/dets.erl
@@ -616,12 +616,18 @@ next(Tab, Key) ->
%% Assuming that a file already exists, open it with the
%% parameters as already specified in the file itself.
%% Return a ref leading to the file.
-open_file(File) ->
- case dets_server:open_file(to_list(File)) of
- badarg -> % Should not happen.
- erlang:error(dets_process_died, [File]);
- Reply ->
- einval(Reply, [File])
+open_file(File0) ->
+ File = to_list(File0),
+ case is_list(File) of
+ true ->
+ case dets_server:open_file(File) of
+ badarg -> % Should not happen.
+ erlang:error(dets_process_died, [File]);
+ Reply ->
+ einval(Reply, [File])
+ end;
+ false ->
+ erlang:error(badarg, [File0])
end.
-spec open_file(Name, Args) -> {'ok', Name} | {'error', Reason} when
@@ -1088,6 +1094,7 @@ defaults(Tab, Args) ->
debug = false},
Fun = fun repl/2,
Defaults = lists:foldl(Fun, Defaults0, Args),
+ true = is_list(Defaults#open_args.file),
is_comp_min_max(Defaults).
to_list(T) when is_atom(T) -> atom_to_list(T);
@@ -1112,9 +1119,7 @@ repl({delayed_write, {Delay,Size} = C}, Defs)
Defs#open_args{delayed_write = C};
repl({estimated_no_objects, I}, Defs) ->
repl({min_no_slots, I}, Defs);
-repl({file, File}, Defs) when is_list(File) ->
- Defs#open_args{file = File};
-repl({file, File}, Defs) when is_atom(File) ->
+repl({file, File}, Defs) ->
Defs#open_args{file = to_list(File)};
repl({keypos, P}, Defs) when is_integer(P), P > 0 ->
Defs#open_args{keypos =P};
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
index cc34d4bdd3..181a524db6 100644
--- a/lib/stdlib/src/epp.erl
+++ b/lib/stdlib/src/epp.erl
@@ -117,6 +117,7 @@ open(Name, File, StartLocation, Path, Pdm) ->
{'ok', Epp} | {'ok', Epp, Extra} | {'error', ErrorDescriptor} when
Options :: [{'default_encoding', DefEncoding :: source_encoding()} |
{'includes', IncludePath :: [DirectoryName :: file:name()]} |
+ {'source_name', SourceName :: file:name()} |
{'macros', PredefMacros :: macros()} |
{'name',FileName :: file:name()} |
'extra'],
@@ -248,6 +249,7 @@ parse_file(Ifile, Path, Predefs) ->
{'ok', [Form]} | {'ok', [Form], Extra} | {error, OpenError} when
FileName :: file:name(),
Options :: [{'includes', IncludePath :: [DirectoryName :: file:name()]} |
+ {'source_name', SourceName :: file:name()} |
{'macros', PredefMacros :: macros()} |
{'default_encoding', DefEncoding :: source_encoding()} |
'extra'],
@@ -540,9 +542,10 @@ server(Pid, Name, Options, #epp{pre_opened=PreOpened}=St) ->
init_server(Pid, Name, Options, St)
end.
-init_server(Pid, Name, Options, St0) ->
+init_server(Pid, FileName, Options, St0) ->
+ SourceName = proplists:get_value(source_name, Options, FileName),
Pdm = proplists:get_value(macros, Options, []),
- Ms0 = predef_macros(Name),
+ Ms0 = predef_macros(FileName),
case user_predef(Pdm, Ms0) of
{ok,Ms1} ->
#epp{file = File, location = AtLocation} = St0,
@@ -552,14 +555,14 @@ init_server(Pid, Name, Options, St0) ->
epp_reply(Pid, {ok,self(),Encoding}),
%% ensure directory of current source file is
%% first in path
- Path = [filename:dirname(Name) |
+ Path = [filename:dirname(FileName) |
proplists:get_value(includes, Options, [])],
- St = St0#epp{delta=0, name=Name, name2=Name,
+ St = St0#epp{delta=0, name=SourceName, name2=SourceName,
path=Path, macs=Ms1,
default_encoding=DefEncoding},
From = wait_request(St),
Anno = erl_anno:new(AtLocation),
- enter_file_reply(From, file_name(Name), Anno,
+ enter_file_reply(From, file_name(SourceName), Anno,
AtLocation, code),
wait_req_scan(St);
{error,E} ->
diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl
index 31c0e60fe1..2066b2f60f 100644
--- a/lib/stdlib/src/erl_eval.erl
+++ b/lib/stdlib/src/erl_eval.erl
@@ -329,7 +329,8 @@ expr({'fun',Line,{clauses,Cs}} = Ex, Bs, Lf, Ef, RBs) ->
20 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) ->
eval_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T], Info) end;
_Other ->
- erlang:raise(error, {'argument_limit',{'fun',Line,Cs}},
+ L = erl_anno:location(Line),
+ erlang:raise(error, {'argument_limit',{'fun',L,to_terms(Cs)}},
?STACKTRACE)
end,
ret_expr(F, Bs, RBs);
@@ -381,7 +382,9 @@ expr({named_fun,Line,Name,Cs} = Ex, Bs, Lf, Ef, RBs) ->
eval_named_fun([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T],
RF, Info) end;
_Other ->
- erlang:raise(error, {'argument_limit',{named_fun,Line,Name,Cs}},
+ L = erl_anno:location(Line),
+ erlang:raise(error, {'argument_limit',
+ {named_fun,L,Name,to_terms(Cs)}},
?STACKTRACE)
end,
ret_expr(F, Bs, RBs);
@@ -1092,7 +1095,7 @@ match(Pat, Term, Bs) ->
match(Pat, Term, Bs, BBs) ->
case catch match1(Pat, Term, Bs, BBs) of
invalid ->
- erlang:raise(error, {illegal_pattern,Pat}, ?STACKTRACE);
+ erlang:raise(error, {illegal_pattern,to_term(Pat)}, ?STACKTRACE);
Other ->
Other
end.
@@ -1288,6 +1291,12 @@ merge_bindings(Bs1, Bs2) ->
%% end
%% end, Bs2, Bs1).
+to_terms(Abstrs) ->
+ [to_term(Abstr) || Abstr <- Abstrs].
+
+to_term(Abstr) ->
+ erl_parse:anno_to_term(Abstr).
+
%% Substitute {value, A, Item} for {var, A, Var}, preserving A.
%% {value, A, Item} is a shell/erl_eval convention, and for example
%% the linter cannot handle it.
diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl
index b311a843c2..939abaff00 100644
--- a/lib/stdlib/src/erl_internal.erl
+++ b/lib/stdlib/src/erl_internal.erl
@@ -74,6 +74,7 @@ guard_bif(element, 2) -> true;
guard_bif(float, 1) -> true;
guard_bif(floor, 1) -> true;
guard_bif(hd, 1) -> true;
+guard_bif(is_map_key, 2) -> true;
guard_bif(length, 1) -> true;
guard_bif(map_size, 1) -> true;
guard_bif(map_get, 2) -> true;
@@ -109,7 +110,6 @@ new_type_test(is_function, 2) -> true;
new_type_test(is_integer, 1) -> true;
new_type_test(is_list, 1) -> true;
new_type_test(is_map, 1) -> true;
-new_type_test(is_map_key, 2) -> true;
new_type_test(is_number, 1) -> true;
new_type_test(is_pid, 1) -> true;
new_type_test(is_port, 1) -> true;
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index e9ac2fcdff..e0cd68617b 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -2262,8 +2262,7 @@ expr({'fun',Line,Body}, Vt, St) ->
{[],St};
{function,M,F,A} ->
%% New in R15.
- {Bvt, St1} = expr_list([M,F,A], Vt, St),
- {vtupdate(Bvt, Vt),St1}
+ expr_list([M,F,A], Vt, St)
end;
expr({named_fun,_,'_',Cs}, Vt, St) ->
fun_clauses(Cs, Vt, St);
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index 3390cee8ae..5fa9c4f75c 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -872,7 +872,7 @@ Erlang code.
-type af_fun_type() :: {'type', anno(), 'fun', []}
| {'type', anno(), 'fun', [{'type', anno(), 'any'} |
abstract_type()]}
- | {'type', anno(), 'fun', af_function_type()}.
+ | af_function_type().
-type af_integer_range_type() ::
{'type', anno(), 'range', [af_singleton_integer_type()]}.
@@ -924,10 +924,11 @@ Erlang code.
-type af_function_constraint() :: [af_constraint()].
-type af_constraint() :: {'type', anno(), 'constraint',
- af_lit_atom('is_subtype'),
- [af_type_variable() | abstract_type()]}. % [V, T]
+ [af_lit_atom('is_subtype') |
+ [af_type_variable() | abstract_type()]]}. % [IsSubtype, [V, T]]
-type af_singleton_integer_type() :: af_integer()
+ | af_character()
| af_unary_op(af_singleton_integer_type())
| af_binary_op(af_singleton_integer_type()).
@@ -980,7 +981,7 @@ Erlang code.
-type af_unary_op(T) :: {'op', anno(), unary_op(), T}.
--type unary_op() :: '+' | '*' | 'bnot' | 'not'.
+-type unary_op() :: '+' | '-' | 'bnot' | 'not'.
%% See also lib/stdlib/{src/erl_bits.erl,include/erl_bits.hrl}.
-type type_specifier_list() :: 'default' | [type_specifier(), ...].
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 367dbefb82..ada3ff5de3 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -59,7 +59,7 @@
_ -> ?TEST(T)
end).
-define(EXPRS_TEST(L),
- [?TEST(E) || E <- L]).
+ _ = [?TEST(E) || E <- L]).
-define(TEST(T),
%% Assumes that erl_anno has been compiled with DEBUG=true.
%% erl_pp does not use the annoations, but test it anyway.
@@ -697,6 +697,8 @@ fun_info(Extra) ->
%% BITS:
+bit_grp([], _Opts) ->
+ leaf("<<>>");
bit_grp(Fs, Opts) ->
append([['<<'], [bit_elems(Fs, Opts)], ['>>']]).
diff --git a/lib/stdlib/src/erl_tar.erl b/lib/stdlib/src/erl_tar.erl
index d8b8f466b1..7064fcacfa 100644
--- a/lib/stdlib/src/erl_tar.erl
+++ b/lib/stdlib/src/erl_tar.erl
@@ -324,7 +324,7 @@ do_open(Name, Mode) when is_list(Mode) ->
open1({binary,Bin}, read, _Raw, Opts) when is_binary(Bin) ->
case file:open(Bin, [ram,binary,read]) of
{ok,File} ->
- _ = [ram_file:uncompress(File) || Opts =:= [compressed]],
+ _ = [ram_file:uncompress(File) || lists:member(compressed, Opts)],
{ok, #reader{handle=File,access=read,func=fun file_op/2}};
Error ->
Error
@@ -357,7 +357,7 @@ open_mode([read|Rest], false, Raw, Opts) ->
open_mode([write|Rest], false, Raw, Opts) ->
open_mode(Rest, write, Raw, Opts);
open_mode([compressed|Rest], Access, Raw, Opts) ->
- open_mode(Rest, Access, Raw, [compressed|Opts]);
+ open_mode(Rest, Access, Raw, [compressed,read_ahead|Opts]);
open_mode([cooked|Rest], Access, _Raw, Opts) ->
open_mode(Rest, Access, [], Opts);
open_mode([], Access, Raw, Opts) ->
diff --git a/lib/stdlib/src/filename.erl b/lib/stdlib/src/filename.erl
index a322bd002d..b7b7b562ab 100644
--- a/lib/stdlib/src/filename.erl
+++ b/lib/stdlib/src/filename.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1012,24 +1012,33 @@ filename_string_to_binary(List) ->
%% basedir
%% http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
--type basedir_type() :: 'user_cache' | 'user_config' | 'user_data'
- | 'user_log'
- | 'site_config' | 'site_data'.
+-type basedir_path_type() :: 'user_cache' | 'user_config' | 'user_data'
+ | 'user_log'.
+-type basedir_paths_type() :: 'site_config' | 'site_data'.
--spec basedir(Type,Application) -> file:filename_all() when
- Type :: basedir_type(),
+-type basedir_opts() :: #{author => string() | binary(),
+ os => 'windows' | 'darwin' | 'linux',
+ version => string() | binary()}.
+
+-spec basedir(PathType,Application) -> file:filename_all() when
+ PathType :: basedir_path_type(),
+ Application :: string() | binary();
+ (PathsType,Application) -> [file:filename_all()] when
+ PathsType :: basedir_paths_type(),
Application :: string() | binary().
basedir(Type,Application) when is_atom(Type), is_list(Application) orelse
is_binary(Application) ->
basedir(Type, Application, #{}).
--spec basedir(Type,Application,Opts) -> file:filename_all() when
- Type :: basedir_type(),
+-spec basedir(PathType,Application,Opts) -> file:filename_all() when
+ PathType :: basedir_path_type(),
+ Application :: string() | binary(),
+ Opts :: basedir_opts();
+ (PathsType,Application,Opts) -> [file:filename_all()] when
+ PathsType :: basedir_paths_type(),
Application :: string() | binary(),
- Opts :: #{author => string() | binary(),
- os => 'windows' | 'darwin' | 'linux',
- version => string() | binary()}.
+ Opts :: basedir_opts().
basedir(Type,Application,Opts) when is_atom(Type), is_map(Opts),
is_list(Application) orelse
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index faa43fbc1e..8965af253b 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -398,17 +398,11 @@ timeout_event_type(Type) ->
data :: term(),
postponed = [] :: [{event_type(),term()}],
%%
- timer_refs = #{} :: % timer ref => the timer's event type
- #{reference() => timeout_event_type()},
- timer_types = #{} :: % timer's event type => timer ref
- #{timeout_event_type() => reference()},
- cancel_timers = 0 :: non_neg_integer(),
- %% We add a timer to both timer_refs and timer_types
- %% when we start it. When we request an asynchronous
- %% timer cancel we remove it from timer_types. When
- %% the timer cancel message arrives we remove it from
- %% timer_refs.
- %%
+ timers = {#{},#{}} ::
+ {%% timer ref => the timer's event type
+ TimerRefs :: #{reference() => timeout_event_type()},
+ %% timer's event type => timer ref
+ TimerTypes :: #{timeout_event_type() => reference()}},
hibernate = false :: boolean(),
hibernate_after = infinity :: timeout()}).
@@ -857,7 +851,7 @@ wakeup_from_hibernate(Parent, Debug, S) ->
%% and detours through sys:handle_system_message/7 and proc_lib:hibernate/3
%% Entry point for system_continue/3
-loop(Parent, Debug, #state{hibernate = true, cancel_timers = 0} = S) ->
+loop(Parent, Debug, #state{hibernate = true} = S) ->
loop_hibernate(Parent, Debug, S);
loop(Parent, Debug, S) ->
loop_receive(Parent, Debug, S).
@@ -893,70 +887,20 @@ loop_receive(
Q = [EXIT],
terminate(exit, Reason, ?STACKTRACE(), Debug, S, Q);
{timeout,TimerRef,TimerMsg} ->
- #state{
- timer_refs = TimerRefs,
- timer_types = TimerTypes} = S,
- case TimerRefs of
- #{TimerRef := TimerType} ->
- %% We know of this timer; is it a running
- %% timer or a timer being cancelled that
- %% managed to send a late timeout message?
- case TimerTypes of
- #{TimerType := TimerRef} ->
- %% The timer type maps back to this
- %% timer ref, so it was a running timer
- %% Unregister the triggered timeout
- NewTimerRefs =
- maps:remove(TimerRef, TimerRefs),
- NewTimerTypes =
- maps:remove(TimerType, TimerTypes),
- loop_receive_result(
- Parent, Debug,
- S#state{
- timer_refs = NewTimerRefs,
- timer_types = NewTimerTypes},
- TimerType, TimerMsg);
- _ ->
- %% This was a late timeout message
- %% from timer being cancelled, so
- %% ignore it and expect a cancel_timer
- %% msg shortly
- loop_receive(Parent, Debug, S)
- end;
- _ ->
+ case S#state.timers of
+ {#{TimerRef := TimerType} = TimerRefs,TimerTypes} ->
+ %% Our timer
+ NewTimers =
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimerType, TimerTypes)},
+ loop_receive_result(
+ Parent, Debug,
+ S#state{timers = NewTimers},
+ TimerType, TimerMsg);
+ {#{},_} ->
%% Not our timer; present it as an event
loop_receive_result(Parent, Debug, S, info, Msg)
end;
- {cancel_timer,TimerRef,_} ->
- #state{
- timer_refs = TimerRefs,
- cancel_timers = CancelTimers,
- hibernate = Hibernate} = S,
- case TimerRefs of
- #{TimerRef := _} ->
- %% We must have requested a cancel
- %% of this timer so it is already
- %% removed from TimerTypes
- NewTimerRefs =
- maps:remove(TimerRef, TimerRefs),
- NewCancelTimers = CancelTimers - 1,
- NewS =
- S#state{
- timer_refs = NewTimerRefs,
- cancel_timers = NewCancelTimers},
- if
- Hibernate =:= true, NewCancelTimers =:= 0 ->
- %% No more cancel_timer msgs to expect;
- %% we can hibernate
- loop_hibernate(Parent, Debug, NewS);
- NewCancelTimers >= 0 -> % Assert
- loop_receive(Parent, Debug, NewS)
- end;
- _ ->
- %% Not our cancel_timer msg;
- %% present it as an event
- loop_receive_result(Parent, Debug, S, info, Msg)
- end;
_ ->
%% External msg
case Msg of
@@ -1429,9 +1373,7 @@ loop_event_done(
loop_event_done(
Parent, Debug_0,
#state{
- state = State, postponed = P_0,
- timer_refs = TimerRefs_0, timer_types = TimerTypes_0,
- cancel_timers = CancelTimers_0} = S,
+ state = State, postponed = P_0, timers = Timers_0} = S,
Events_0, Event_0, NextState, NewData,
#trans_opts{
hibernate = Hibernate, timeouts_r = TimeoutsR,
@@ -1463,22 +1405,17 @@ loop_event_done(
if
NextState =:= State ->
{Events_0,P_1,
- cancel_timer_by_type(
- timeout, {TimerTypes_0,CancelTimers_0})};
+ cancel_timer_by_type(timeout, Timers_0)};
true ->
{lists:reverse(P_1, Events_0),
[],
cancel_timer_by_type(
state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes_0,CancelTimers_0}))}
- %% The state timer is removed from TimerTypes
- %% but remains in TimerRefs until we get
- %% the cancel_timer msg
+ cancel_timer_by_type(timeout, Timers_0))}
end,
- {TimerRefs_3,{TimerTypes_3,CancelTimers_3},TimeoutEvents} =
+ {Timers_3,TimeoutEvents} =
%% Stop and start timers
- parse_timers(TimerRefs_0, Timers_2, TimeoutsR),
+ parse_timers(Timers_2, TimeoutsR),
%% Place next events last in reversed queue
Events_3R = lists:reverse(Events_2, NextEventsR),
%% Enqueue immediate timeout events
@@ -1489,9 +1426,7 @@ loop_event_done(
state = NextState,
data = NewData,
postponed = P_2,
- timer_refs = TimerRefs_3,
- timer_types = TimerTypes_3,
- cancel_timers = CancelTimers_3,
+ timers = Timers_3,
hibernate = Hibernate},
lists:reverse(Events_4R)).
@@ -1501,8 +1436,7 @@ loop_event_done_fast(
Parent, Hibernate,
#state{
state = NextState,
- timer_types = #{timeout := _} = TimerTypes,
- cancel_timers = CancelTimers} = S,
+ timers = {_,#{timeout := _}} = Timers} = S,
Events, P, NextState, NewData) ->
%%
%% Same state, event timeout active
@@ -1510,8 +1444,7 @@ loop_event_done_fast(
loop_event_done_fast(
Parent, Hibernate, S,
Events, P, NextState, NewData,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers}));
+ cancel_timer_by_type(timeout, Timers));
loop_event_done_fast(
Parent, Hibernate,
#state{state = NextState} = S,
@@ -1529,8 +1462,7 @@ loop_event_done_fast(
loop_event_done_fast(
Parent, Hibernate,
#state{
- timer_types = #{timeout := _} = TimerTypes,
- cancel_timers = CancelTimers} = S,
+ timers = {_,#{timeout := _}} = Timers} = S,
Events, P, NextState, NewData) ->
%%
%% State change, event timeout active
@@ -1540,13 +1472,11 @@ loop_event_done_fast(
lists:reverse(P, Events), [], NextState, NewData,
cancel_timer_by_type(
state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
+ cancel_timer_by_type(timeout, Timers)));
loop_event_done_fast(
Parent, Hibernate,
#state{
- timer_types = #{state_timeout := _} = TimerTypes,
- cancel_timers = CancelTimers} = S,
+ timers = {_,#{state_timeout := _}} = Timers} = S,
Events, P, NextState, NewData) ->
%%
%% State change, state timeout active
@@ -1556,8 +1486,7 @@ loop_event_done_fast(
lists:reverse(P, Events), [], NextState, NewData,
cancel_timer_by_type(
state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
+ cancel_timer_by_type(timeout, Timers)));
loop_event_done_fast(
Parent, Hibernate,
#state{} = S,
@@ -1577,9 +1506,7 @@ loop_event_done_fast(
%% Fast path
%%
loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData,
- {TimerTypes,CancelTimers}) ->
+ Parent, Hibernate, S, Events, P, NextState, NewData, Timers) ->
%%
loop_event_done(
Parent, ?not_sys_debug,
@@ -1587,8 +1514,7 @@ loop_event_done_fast(
state = NextState,
data = NewData,
postponed = P,
- timer_types = TimerTypes,
- cancel_timers = CancelTimers,
+ timers = Timers,
hibernate = Hibernate},
Events).
@@ -1703,41 +1629,40 @@ classify_time(_, _, Opts) when is_list(Opts) ->
%% and pending event timer
%%
%% Stop and start timers non-event timers
-parse_timers(TimerRefs, Timers, TimeoutsR) ->
- parse_timers(TimerRefs, Timers, TimeoutsR, #{}, []).
+parse_timers(Timers, TimeoutsR) ->
+ parse_timers(Timers, TimeoutsR, #{}, []).
%%
-parse_timers(
- TimerRefs, Timers, [], _Seen, TimeoutEvents) ->
+parse_timers(Timers, [], _Seen, TimeoutEvents) ->
%%
- {TimerRefs,Timers,TimeoutEvents};
+ {Timers,TimeoutEvents};
parse_timers(
- TimerRefs, Timers, [Timeout|TimeoutsR], Seen, TimeoutEvents) ->
+ Timers, [Timeout|TimeoutsR], Seen, TimeoutEvents) ->
%%
case Timeout of
{TimerType,Time,TimerMsg,TimerOpts} ->
%% Absolute timer
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
+ Timers, TimeoutsR, Seen, TimeoutEvents,
TimerType, Time, TimerMsg, listify(TimerOpts));
%% Relative timers below
{TimerType,0,TimerMsg} ->
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
+ Timers, TimeoutsR, Seen, TimeoutEvents,
TimerType, zero, TimerMsg, []);
{TimerType,Time,TimerMsg} ->
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
+ Timers, TimeoutsR, Seen, TimeoutEvents,
TimerType, Time, TimerMsg, [])
end.
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
+ Timers, TimeoutsR, Seen, TimeoutEvents,
TimerType, Time, TimerMsg, TimerOpts) ->
case Seen of
#{TimerType := _} ->
%% Type seen before - ignore
parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents);
+ Timers, TimeoutsR, Seen, TimeoutEvents);
#{} ->
%% Unseen type - handle
NewSeen = Seen#{TimerType => true},
@@ -1745,13 +1670,13 @@ parse_timers(
infinity ->
%% Cancel any running timer
parse_timers(
- TimerRefs, cancel_timer_by_type(TimerType, Timers),
+ cancel_timer_by_type(TimerType, Timers),
TimeoutsR, NewSeen, TimeoutEvents);
zero ->
%% Cancel any running timer
%% Handle zero time timeouts later
parse_timers(
- TimerRefs, cancel_timer_by_type(TimerType, Timers),
+ cancel_timer_by_type(TimerType, Timers),
TimeoutsR, NewSeen,
[{TimerType,TimerMsg}|TimeoutEvents]);
_ ->
@@ -1759,26 +1684,27 @@ parse_timers(
TimerRef =
erlang:start_timer(
Time, self(), TimerMsg, TimerOpts),
- case Timers of
- {#{TimerType := OldTimerRef} = TimerTypes,
- CancelTimers} ->
- %% Cancel the running timer
+ {TimerRefs,TimerTypes} = Timers,
+ case TimerTypes of
+ #{TimerType := OldTimerRef} ->
+ %% Cancel the running timer,
+ %% update the timeout type,
+ %% insert the new timer ref,
+ %% and remove the old timer ref
cancel_timer(OldTimerRef),
- NewCancelTimers = CancelTimers + 1,
%% Insert the new timer into
%% both TimerRefs and TimerTypes
parse_timers(
- TimerRefs#{TimerRef => TimerType},
- {TimerTypes#{TimerType => TimerRef},
- NewCancelTimers},
+ {maps:remove(
+ OldTimerRef,
+ TimerRefs#{TimerRef => TimerType}),
+ TimerTypes#{TimerType := TimerRef}},
TimeoutsR, NewSeen, TimeoutEvents);
- {#{} = TimerTypes,CancelTimers} ->
- %% Insert the new timer into
- %% both TimerRefs and TimerTypes
+ #{} ->
+ %% Insert the new timer type and ref
parse_timers(
- TimerRefs#{TimerRef => TimerType},
- {TimerTypes#{TimerType => TimerRef},
- CancelTimers},
+ {TimerRefs#{TimerRef => TimerType},
+ TimerTypes#{TimerType => TimerRef}},
TimeoutsR, NewSeen, TimeoutEvents)
end
end
@@ -2021,24 +1947,34 @@ listify(Item) when is_list(Item) ->
listify(Item) ->
[Item].
+
+-define(cancel_timer(TimerRef),
+ case erlang:cancel_timer(TimerRef) of
+ false ->
+ %% No timer found and we have not seen the timeout message
+ receive
+ {timeout,(TimerRef),_} ->
+ ok
+ end;
+ _ ->
+ %% Timer was running
+ ok
+ end).
+
+-compile({inline, [cancel_timer/1]}).
+cancel_timer(TimerRef) ->
+ ?cancel_timer(TimerRef).
+
%% Cancel timer if running, otherwise no op
%%
-%% This is an asynchronous cancel so the timer is not really cancelled
-%% until we get a cancel_timer msg i.e {cancel_timer,TimerRef,_}.
-%% In the mean time we might get a timeout message.
-%%
-%% Remove the timer from TimerTypes.
-%% When we get the cancel_timer msg we remove it from TimerRefs.
+%% Remove the timer from Timers.
-compile({inline, [cancel_timer_by_type/2]}).
-cancel_timer_by_type(TimerType, {TimerTypes,CancelTimers} = TT_CT) ->
+cancel_timer_by_type(TimerType, {TimerRefs,TimerTypes} = Timers) ->
case TimerTypes of
#{TimerType := TimerRef} ->
- ok = erlang:cancel_timer(TimerRef, [{async,true}]),
- {maps:remove(TimerType, TimerTypes),CancelTimers + 1};
+ ?cancel_timer(TimerRef),
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimerType, TimerTypes)};
#{} ->
- TT_CT
+ Timers
end.
-
--compile({inline, [cancel_timer/1]}).
-cancel_timer(TimerRef) ->
- ok = erlang:cancel_timer(TimerRef, [{async,true}]).
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index 3a5aba60b4..2b5a374cf2 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -87,6 +87,8 @@
-export([limit_term/2]).
+-export([chars_length/1]).
+
-export_type([chars/0, latin1_string/0, continuation/0,
fread_error/0, fread_item/0, format_spec/0, chars_limit/0]).
@@ -178,11 +180,11 @@ fread(Cont, Chars, Format) ->
Data :: [term()].
format(Format, Args) ->
- case catch io_lib_format:fwrite(Format, Args) of
- {'EXIT',_} ->
- erlang:error(badarg, [Format, Args]);
- Other ->
- Other
+ try io_lib_format:fwrite(Format, Args)
+ catch
+ C:R:S ->
+ test_modules_loaded(C, R, S),
+ erlang:error(badarg, [Format, Args])
end.
-spec format(Format, Data, Options) -> chars() when
@@ -193,11 +195,11 @@ format(Format, Args) ->
CharsLimit :: chars_limit().
format(Format, Args, Options) ->
- case catch io_lib_format:fwrite(Format, Args, Options) of
- {'EXIT',_} ->
- erlang:error(badarg, [Format, Args, Options]);
- Other ->
- Other
+ try io_lib_format:fwrite(Format, Args, Options)
+ catch
+ C:R:S ->
+ test_modules_loaded(C, R, S),
+ erlang:error(badarg, [Format, Args])
end.
-spec scan_format(Format, Data) -> FormatList when
@@ -208,7 +210,9 @@ format(Format, Args, Options) ->
scan_format(Format, Args) ->
try io_lib_format:scan(Format, Args)
catch
- _:_ -> erlang:error(badarg, [Format, Args])
+ C:R:S ->
+ test_modules_loaded(C, R, S),
+ erlang:error(badarg, [Format, Args])
end.
-spec unscan_format(FormatList) -> {Format, Data} when
@@ -223,7 +227,12 @@ unscan_format(FormatList) ->
FormatList :: [char() | format_spec()].
build_text(FormatList) ->
- io_lib_format:build(FormatList).
+ try io_lib_format:build(FormatList)
+ catch
+ C:R:S ->
+ test_modules_loaded(C, R, S),
+ erlang:error(badarg, [FormatList])
+ end.
-spec build_text(FormatList, Options) -> chars() when
FormatList :: [char() | format_spec()],
@@ -232,7 +241,23 @@ build_text(FormatList) ->
CharsLimit :: chars_limit().
build_text(FormatList, Options) ->
- io_lib_format:build(FormatList, Options).
+ try io_lib_format:build(FormatList, Options)
+ catch
+ C:R:S ->
+ test_modules_loaded(C, R, S),
+ erlang:error(badarg, [FormatList, Options])
+ end.
+
+%% Failure to load a module must not be labeled as badarg.
+%% C, R, and S are included so that the original error, which could be
+%% a bug in io_lib_format, can be found by tracing on
+%% test_modules_loaded/3.
+test_modules_loaded(_C, _R, _S) ->
+ Modules = [io_lib_format, io_lib_pretty, string, unicode],
+ case code:ensure_modules_loaded(Modules) of
+ ok -> ok;
+ Error -> erlang:error(Error)
+ end.
-spec print(Term) -> chars() when
Term :: term().
@@ -1108,3 +1133,17 @@ test_limit_map_assoc(K, V, D) ->
test_limit(V, D - 1).
test_limit_bitstring(_, _) -> ok.
+
+-spec chars_length(chars()) -> non_neg_integer().
+%% Optimized for deep lists S such that deep_latin1_char_list(S) is
+%% true. No binaries allowed! It is assumed that $\r is never followed
+%% by $\n if S is an iolist() (string:length() assigns such a
+%% sub-sequence length 1).
+chars_length(S) ->
+ try
+ %% true = deep_latin1_char_list(S),
+ iolist_size(S)
+ catch
+ _:_ ->
+ string:length(S)
+ end.
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index c814ab50d4..d1aa4cd157 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -38,18 +38,16 @@
%% and it also splits the handling of the control characters into two
%% parts.
--spec fwrite(Format, Data) -> FormatList when
+-spec fwrite(Format, Data) -> io_lib:chars() when
Format :: io:format(),
- Data :: [term()],
- FormatList :: [char() | io_lib:format_spec()].
+ Data :: [term()].
fwrite(Format, Args) ->
build(scan(Format, Args)).
--spec fwrite(Format, Data, Options) -> FormatList when
+-spec fwrite(Format, Data, Options) -> io_lib:chars() when
Format :: io:format(),
Data :: [term()],
- FormatList :: [char() | io_lib:format_spec()],
Options :: [Option],
Option :: {'chars_limit', CharsLimit},
CharsLimit :: io_lib:chars_limit().
@@ -248,8 +246,9 @@ count_small([#{control_char := $W}|Cs], #{w := W} = Cnts) ->
count_small(Cs, Cnts#{w := W + 1});
count_small([#{control_char := $s}|Cs], #{w := W} = Cnts) ->
count_small(Cs, Cnts#{w := W + 1});
-count_small([S|Cs], #{other := Other} = Cnts) when is_list(S) ->
- count_small(Cs, Cnts#{other := Other + string:length(S)});
+count_small([S|Cs], #{other := Other} = Cnts) when is_list(S);
+ is_binary(S) ->
+ count_small(Cs, Cnts#{other := Other + io_lib:chars_length(S)});
count_small([C|Cs], #{other := Other} = Cnts) when is_integer(C) ->
count_small(Cs, Cnts#{other := Other + 1});
count_small([], #{p := P, s := S, w := W, other := Other}) ->
@@ -281,10 +280,15 @@ build_limited([#{control_char := C, args := As, width := F, adjust := Ad,
true -> MaxLen0 div Count0
end,
S = control_limited(C, As, F, Ad, P, Pad, Enc, Str, MaxChars, I),
- Len = string:length(S),
NumOfPs = decr_pc(C, NumOfPs0),
Count = Count0 - 1,
- MaxLen = sub(MaxLen0, Len),
+ MaxLen = if
+ MaxLen0 < 0 -> % optimization
+ MaxLen0;
+ true ->
+ Len = io_lib:chars_length(S),
+ sub(MaxLen0, Len)
+ end,
if
NumOfPs > 0 -> [S|build_limited(Cs, NumOfPs, Count,
MaxLen, indentation(S, I))];
@@ -407,7 +411,7 @@ base(B) when is_integer(B) ->
term(T, none, _Adj, none, _Pad) -> T;
term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
term(T, F, Adj, P0, Pad) ->
- L = string:length(T),
+ L = io_lib:chars_length(T),
P = erlang:min(L, case P0 of none -> F; _ -> min(P0, F) end),
if
L > P ->
@@ -714,7 +718,7 @@ fwrite_g(Fl, F, Adj, P, Pad) when P >= 1 ->
end.
-%% iolist_to_chars(iolist()) -> deep_char_list()
+%% iolist_to_chars(iolist()) -> io_lib:chars()
iolist_to_chars([C|Cs]) when is_integer(C), C >= $\000, C =< $\377 ->
[C | iolist_to_chars(Cs)];
@@ -730,7 +734,7 @@ iolist_to_chars(B) when is_binary(B) ->
%% cbinary() | nil())
%% cbinary() :: unicode:unicode_binary() | unicode:latin1_binary()
-%% cdata_to_chars(cdata()) -> io_lib:deep_char_list()
+%% cdata_to_chars(cdata()) -> io_lib:chars()
cdata_to_chars([C|Cs]) when is_integer(C), C >= $\000 ->
[C | cdata_to_chars(Cs)];
@@ -746,7 +750,7 @@ cdata_to_chars(B) when is_binary(B) ->
limit_string(S, F, CharsLimit) when CharsLimit < 0; CharsLimit >= F -> S;
limit_string(S, _F, CharsLimit) ->
- case string:length(S) =< CharsLimit of
+ case io_lib:chars_length(S) =< CharsLimit of
true -> S;
false -> [string:slice(S, 0, sub(CharsLimit, 3)), "..."]
end.
@@ -760,11 +764,11 @@ limit_field(F, CharsLimit) ->
string(S, none, _Adj, none, _Pad) -> S;
string(S, F, Adj, none, Pad) ->
- string_field(S, F, Adj, string:length(S), Pad);
+ string_field(S, F, Adj, io_lib:chars_length(S), Pad);
string(S, none, _Adj, P, Pad) ->
- string_field(S, P, left, string:length(S), Pad);
+ string_field(S, P, left, io_lib:chars_length(S), Pad);
string(S, F, Adj, P, Pad) when F >= P ->
- N = string:length(S),
+ N = io_lib:chars_length(S),
if F > P ->
if N > P ->
adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index dca1b37ef3..ffc02febdc 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -507,20 +507,20 @@ print_length(#{}=M, _D, _T, _RF, _Enc, _Str) when map_size(M) =:= 0 ->
{"#{}", 3, 0, no_more};
print_length(Atom, _D, _T, _RF, Enc, _Str) when is_atom(Atom) ->
S = write_atom(Atom, Enc),
- {S, string:length(S), 0, no_more};
+ {S, io_lib:chars_length(S), 0, no_more};
print_length(List, D, T, RF, Enc, Str) when is_list(List) ->
%% only flat lists are "printable"
case Str andalso printable_list(List, D, T, Enc) of
true ->
%% print as string, escaping double-quotes in the list
S = write_string(List, Enc),
- {S, string:length(S), 0, no_more};
+ {S, io_lib:chars_length(S), 0, no_more};
{true, Prefix} ->
%% Truncated lists when T < 0 could break some existing code.
S = write_string(Prefix, Enc),
%% NumOfDots = 0 to avoid looping--increasing the depth
%% does not make Prefix longer.
- {[S | "..."], 3 + string:length(S), 0, no_more};
+ {[S | "..."], 3 + io_lib:chars_length(S), 0, no_more};
false ->
case print_length_list(List, D, T, RF, Enc, Str) of
{What, Len, Dots, _More} when Dots > 0 ->
@@ -564,7 +564,7 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
{[$<,$<,S,$>,$>], 4 + length(S), 0, no_more};
{false, List} when is_list(List) ->
S = io_lib:write_string(List, $"), %"
- {[$<,$<,S,"/utf8>>"], 9 + string:length(S), 0, no_more};
+ {[$<,$<,S,"/utf8>>"], 9 + io_lib:chars_length(S), 0, no_more};
{true, true, Prefix} ->
S = io_lib:write_string(Prefix, $"), %"
More = fun(T1, Dd) ->
@@ -576,7 +576,7 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
More = fun(T1, Dd) ->
?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str)
end,
- {[$<,$<,S|"/utf8...>>"], 12 + string:length(S), 3, More};
+ {[$<,$<,S|"/utf8...>>"], 12 + io_lib:chars_length(S), 3, More};
false ->
case io_lib:write_binary(Bin, D, T) of
{S, <<>>} ->
@@ -591,7 +591,7 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
print_length(Term, _D, _T, _RF, _Enc, _Str) ->
S = io_lib:write(Term),
%% S can contain unicode, so iolist_size(S) cannot be used here
- {S, string:length(S), 0, no_more}.
+ {S, io_lib:chars_length(S), 0, no_more}.
print_length_map(Map, 1, _T, RF, Enc, Str) ->
More = fun(T1, Dd) -> ?FUNCTION_NAME(Map, 1+Dd, T1, RF, Enc, Str) end,
@@ -651,7 +651,7 @@ print_length_record(Tuple, 1, _T, RF, RDefs, Enc, Str) ->
{"{...}", 5, 3, More};
print_length_record(Tuple, D, T, RF, RDefs, Enc, Str) ->
Name = [$# | write_atom(element(1, Tuple), Enc)],
- NameL = string:length(Name),
+ NameL = io_lib:chars_length(Name),
T1 = tsub(T, NameL+2),
L = print_length_fields(RDefs, D - 1, T1, Tuple, 2, RF, Enc, Str),
{Len, Dots} = list_length(L, NameL + 2, 0),
@@ -677,7 +677,7 @@ print_length_fields([Def | Defs], D, T, Tuple, I, RF, Enc, Str) ->
print_length_field(Def, D, T, E, RF, Enc, Str) ->
Name = write_atom(Def, Enc),
- NameL = string:length(Name) + 3,
+ NameL = io_lib:chars_length(Name) + 3,
{_, Len, Dots, _} =
Field = print_length(E, D, tsub(T, NameL), RF, Enc, Str),
{{field, Name, NameL, Field}, NameL + Len, Dots, no_more}.
@@ -721,8 +721,8 @@ printable_list(_L, 1, _T, _Enc) ->
printable_list(L, _D, T, latin1) when T < 0 ->
io_lib:printable_latin1_list(L);
printable_list(L, _D, T, Enc) when T >= 0 ->
- case slice(L, tsub(T, 2)) of
- {prefix, ""} ->
+ case slice(L, tsub(T, 2), Enc) of
+ false ->
false;
{prefix, Prefix} when Enc =:= latin1 ->
io_lib:printable_latin1_list(Prefix) andalso {true, Prefix};
@@ -737,14 +737,46 @@ printable_list(L, _D, T, Enc) when T >= 0 ->
printable_list(L, _D, T, _Uni) when T < 0->
io_lib:printable_list(L).
-slice(L, N) ->
- case string:length(L) =< N of
- true ->
+slice(L, N, latin1) ->
+ try lists:split(N, L) of
+ {_, []} ->
all;
- false ->
- {prefix, string:slice(L, 0, N)}
+ {[], _} ->
+ false;
+ {L1, _} ->
+ {prefix, L1}
+ catch
+ _:_ ->
+ all
+ end;
+slice(L, N, _Uni) ->
+ %% Be careful not to traverse more of L than necessary.
+ try string:slice(L, 0, N) of
+ "" ->
+ false;
+ Prefix ->
+ %% Assume no binaries are introduced by string:slice().
+ case is_flat(L, lists:flatlength(Prefix)) of
+ true ->
+ case string:equal(Prefix, L) of
+ true ->
+ all;
+ false ->
+ {prefix, Prefix}
+ end;
+ false ->
+ false
+ end
+ catch _:_ -> false
end.
+is_flat(_L, 0) ->
+ true;
+is_flat([C|Cs], N) when is_integer(C) ->
+ is_flat(Cs, N - 1);
+is_flat(_, _N) ->
+ false.
+
printable_bin0(Bin, D, T, Enc) ->
Len = case D >= 0 of
true ->
@@ -763,6 +795,8 @@ printable_bin0(Bin, D, T, Enc) ->
end,
printable_bin(Bin, Len, D, Enc).
+printable_bin(_Bin, 0, _D, _Enc) ->
+ false;
printable_bin(Bin, Len, D, latin1) ->
N = erlang:min(20, Len),
L = binary_to_list(Bin, 1, N),
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index d117481d2e..97ec785c62 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -224,10 +224,12 @@ transform_from_shell(Dialect, Clauses, BoundEnvironment) ->
%% Called when translating during compiling
%%
--spec parse_transform(Forms, Options) -> Forms2 when
+-spec parse_transform(Forms, Options) -> Forms2 | Errors | Warnings when
Forms :: [erl_parse:abstract_form() | erl_parse:form_info()],
Forms2 :: [erl_parse:abstract_form() | erl_parse:form_info()],
- Options :: term().
+ Options :: term(),
+ Errors :: {error, ErrInfo :: [tuple()], WarnInfo :: []},
+ Warnings :: {warning, Forms2, WarnInfo :: [tuple()]}.
parse_transform(Forms, _Options) ->
SaveFilename = setup_filename(),
@@ -554,8 +556,8 @@ tg({call, Line, {remote,_,{atom,_,erlang},{atom, Line2, FunName}},ParaList},
FunName,length(ParaList)}})
end;
tg({call, Line, {remote,_,{atom,_,ModuleName},
- {atom, _, FunName}},_ParaList},B) ->
- throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName}});
+ {atom, _, FunName}},ParaList},B) ->
+ throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName,length(ParaList)}});
tg({cons,Line, H, T},B) ->
{cons, Line, tg(H,B), tg(T,B)};
tg({nil, Line},_B) ->
@@ -944,6 +946,7 @@ real_guard_function(node,0) -> true;
real_guard_function(node,1) -> true;
real_guard_function(round,1) -> true;
real_guard_function(size,1) -> true;
+real_guard_function(bit_size,1) -> true;
real_guard_function(map_size,1) -> true;
real_guard_function(map_get,2) -> true;
real_guard_function(tl,1) -> true;
diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl
index 89a840be2d..cfbaf8b242 100644
--- a/lib/stdlib/src/proc_lib.erl
+++ b/lib/stdlib/src/proc_lib.erl
@@ -30,7 +30,7 @@
start/3, start/4, start/5, start_link/3, start_link/4, start_link/5,
hibernate/3,
init_ack/1, init_ack/2,
- init_p/3,init_p/5,format/1,format/2,format/3,report_cb/1,
+ init_p/3,init_p/5,format/1,format/2,format/3,report_cb/2,
initial_call/1,
translate_initial_call/1,
stop/1, stop/3]).
@@ -509,7 +509,7 @@ crash_report(Class, Reason, StartF, Stacktrace) ->
report=>[my_info(Class, Reason, StartF, Stacktrace),
linked_info(self())]},
#{domain=>[otp,sasl],
- report_cb=>fun proc_lib:report_cb/1,
+ report_cb=>fun proc_lib:report_cb/2,
logger_formatter=>#{title=>"CRASH REPORT"},
error_logger=>#{tag=>error_report,type=>crash_report}}).
@@ -750,14 +750,16 @@ check(Res) -> Res.
%%% Format a generated crash info structure.
%%% -----------------------------------------------------------
--spec report_cb(CrashReport) -> {Format,Args} when
- CrashReport :: #{label=>{proc_lib,crash},report=>[term()]},
- Format :: io:format(),
- Args :: [term()].
-report_cb(#{label:={proc_lib,crash},
- report:=CrashReport}) ->
- Depth = error_logger:get_format_depth(),
- get_format_and_args(CrashReport, utf8, Depth).
+-spec report_cb(CrashReport,FormatOpts) -> unicode:chardata() when
+ CrashReport :: #{label => {proc_lib,crash},
+ report => [term()]},
+ FormatOpts :: logger:report_cb_config().
+report_cb(#{label:={proc_lib,crash}, report:=CrashReport}, Extra) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ do_format(CrashReport, maps:merge(Default,Extra)).
-spec format(CrashReport) -> string() when
CrashReport :: [term()].
@@ -777,84 +779,121 @@ format(CrashReport, Encoding) ->
Depth :: unlimited | pos_integer().
format(CrashReport, Encoding, Depth) ->
- {F,A} = get_format_and_args(CrashReport, Encoding, Depth),
- lists:flatten(io_lib:format(F,A)).
-
-get_format_and_args([OwnReport,LinkReport], Encoding, Depth) ->
- Extra = {Encoding,Depth},
- MyIndent = " ",
- {OwnFormat,OwnArgs} = format_report(OwnReport, MyIndent, Extra, [], []),
- {LinkFormat,LinkArgs} = format_link_report(LinkReport, MyIndent, Extra, [], []),
- {" crasher:~n"++OwnFormat++" neighbours:~n"++LinkFormat,OwnArgs++LinkArgs}.
-
-format_link_report([], _Indent, _Extra, Format, Args) ->
- {lists:flatten(lists:reverse(Format)),lists:append(lists:reverse(Args))};
-format_link_report([Link|Reps], Indent, Extra, Format, Args) ->
+ do_format(CrashReport, #{chars_limit => unlimited,
+ depth => Depth,
+ encoding => Encoding,
+ single_line => false}).
+
+do_format([OwnReport,LinkReport], #{single_line:=Single}=Extra) ->
+ Indent = if Single -> "";
+ true -> " "
+ end,
+ MyIndent = Indent ++ Indent,
+ Sep = nl(Single,"; "),
+ OwnFormat = format_report(OwnReport, MyIndent, Extra),
+ LinkFormat = lists:join(Sep,format_link_report(LinkReport, MyIndent, Extra)),
+ Nl = nl(Single," "),
+ Str = io_lib:format("~scrasher:"++Nl++"~ts"++Sep++"~sneighbours:"++Nl++"~ts",
+ [Indent,OwnFormat,Indent,LinkFormat]),
+ lists:flatten(Str).
+
+format_link_report([Link|Reps], Indent0, #{single_line:=Single}=Extra) ->
Rep = case Link of
{neighbour,Rep0} -> Rep0;
_ -> Link
end,
+ Indent = if Single -> "";
+ true -> Indent0
+ end,
LinkIndent = [" ",Indent],
- {LinkFormat,LinkArgs} = format_report(Rep, LinkIndent, Extra, [], []),
- F = "~sneighbour:\n"++LinkFormat,
- A = [Indent|LinkArgs],
- format_link_report(Reps, Indent, Extra, [F|Format], [A|Args]);
-format_link_report(Rep, Indent, Extra, Format, Args) ->
- {F,A} = format_report(Rep, Indent, Extra, [], []),
- format_link_report([], Indent, Extra, [F|Format],[A|Args]).
-
-format_report([], _Indent, _Extra, Format, Args) ->
- {lists:flatten(lists:reverse(Format)),lists:append(lists:reverse(Args))};
-format_report([Rep|Reps], Indent, Extra, Format, Args) ->
- {F,A} = format_rep(Rep, Indent, Extra),
- format_report(Reps, Indent, Extra, [F|Format], [A|Args]);
-format_report(Rep, Indent, {Enc,unlimited}=Extra, Format, Args) ->
- {F,A} = {"~s~"++modifier(Enc)++"p~n", [Indent, Rep]},
- format_report([], Indent, Extra, [F|Format], [A|Args]);
-format_report(Rep, Indent, {Enc,Depth}=Extra, Format, Args) ->
- {F,A} = {"~s~"++modifier(Enc)++"P~n", [Indent, Rep, Depth]},
- format_report([], Indent, Extra, [F|Format], [A|Args]).
-
-format_rep({initial_call,InitialCall}, Indent, Extra) ->
- format_mfa(Indent, InitialCall, Extra);
-format_rep({error_info,{Class,Reason,StackTrace}}, _Indent, Extra) ->
- {lists:flatten(format_exception(Class, Reason, StackTrace, Extra)),[]};
-format_rep({Tag,Data}, Indent, Extra) ->
- format_tag(Indent, Tag, Data, Extra).
-
-format_mfa(Indent, {M,F,Args}=StartF, {Enc,_}=Extra) ->
+ [[Indent,"neighbour:",nl(Single," "),format_report(Rep, LinkIndent, Extra)]|
+ format_link_report(Reps, Indent, Extra)];
+format_link_report(Rep, Indent, Extra) ->
+ format_report(Rep, Indent, Extra).
+
+format_report(Rep, Indent, #{single_line:=Single}=Extra) when is_list(Rep) ->
+ lists:join(nl(Single,", "),format_rep(Rep, Indent, Extra));
+format_report(Rep, Indent0, #{encoding:=Enc,depth:=Depth,
+ chars_limit:=Limit,single_line:=Single}) ->
+ {P,Tl} = p(Enc,Depth),
+ {Indent,Width} = if Single -> {"","0"};
+ true -> {Indent0,""}
+ end,
+ Opts = if is_integer(Limit) -> [{chars_limit,Limit}];
+ true -> []
+ end,
+ io_lib:format("~s~"++Width++P, [Indent, Rep | Tl], Opts).
+
+format_rep([{initial_call,InitialCall}|Rep], Indent, Extra) ->
+ [format_mfa(Indent, InitialCall, Extra)|format_rep(Rep, Indent, Extra)];
+format_rep([{error_info,{Class,Reason,StackTrace}}|Rep], Indent, Extra) ->
+ [format_exception(Class, Reason, StackTrace, Extra)|
+ format_rep(Rep, Indent, Extra)];
+format_rep([{Tag,Data}|Rep], Indent, Extra) ->
+ [format_tag(Indent, Tag, Data, Extra)|format_rep(Rep, Indent, Extra)];
+format_rep(_, _, _Extra) ->
+ [].
+
+format_exception(Class, Reason, StackTrace,
+ #{encoding:=Enc,depth:=Depth,chars_limit:=Limit,
+ single_line:=Single}=Extra) ->
+ PF = pp_fun(Extra),
+ StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
+ if Single ->
+ {P,Tl} = p(Enc,Depth),
+ Opts = if is_integer(Limit) -> [{chars_limit,Limit}];
+ true -> []
+ end,
+ [atom_to_list(Class), ": ",
+ io_lib:format("~0"++P,[{Reason,StackTrace}|Tl],Opts)];
+ true ->
+ EI = " ",
+ [EI, erl_error:format_exception(1+length(EI), Class, Reason,
+ StackTrace, StackFun, PF, Enc)]
+ end.
+
+format_mfa(Indent0, {M,F,Args}=StartF, #{encoding:=Enc,single_line:=Single}=Extra) ->
+ Indent = if Single -> "";
+ true -> Indent0
+ end,
try
A = length(Args),
- {lists:flatten([Indent,"initial call: ",atom_to_list(M),
- $:,to_string(F, Enc),$/,integer_to_list(A),"\n"]),[]}
+ [Indent,"initial call: ",atom_to_list(M),$:,to_string(F, Enc),$/,
+ integer_to_list(A)]
catch
error:_ ->
format_tag(Indent, initial_call, StartF, Extra)
end.
-format_tag(Indent, Tag, Data, {Enc,Depth}) ->
- {P,Tl} = p(Enc, Depth),
- {"~s~p: ~80.18" ++ P ++ "\n", [Indent, Tag, Data|Tl]}.
-
-format_exception(Class, Reason, StackTrace, {Enc,_}=Extra) ->
- PF = pp_fun(Extra),
- StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
- %% EI = " exception: ",
- EI = " ",
- [EI, erl_error:format_exception(1+length(EI), Class, Reason,
- StackTrace, StackFun, PF, Enc), "\n"].
-
to_string(A, latin1) ->
io_lib:write_atom_as_latin1(A);
to_string(A, _) ->
io_lib:write_atom(A).
-pp_fun({Enc,Depth}) ->
+pp_fun(#{encoding:=Enc,depth:=Depth,chars_limit:=Limit,single_line:=Single}) ->
{P,Tl} = p(Enc, Depth),
+ Width = if Single -> "0";
+ true -> ""
+ end,
+ Opts = if is_integer(Limit) -> [{chars_limit,Limit}];
+ true -> []
+ end,
fun(Term, I) ->
- io_lib:format("~." ++ integer_to_list(I) ++ P, [Term|Tl])
+ io_lib:format("~" ++ Width ++ "." ++ integer_to_list(I) ++ P,
+ [Term|Tl], Opts)
end.
+format_tag(Indent0, Tag, Data, #{encoding:=Enc,depth:=Depth,chars_limit:=Limit,single_line:=Single}) ->
+ {P,Tl} = p(Enc, Depth),
+ {Indent,Width} = if Single -> {"","0"};
+ true -> {Indent0,""}
+ end,
+ Opts = if is_integer(Limit) -> [{chars_limit,Limit}];
+ true -> []
+ end,
+ io_lib:format("~s~" ++ Width ++ "p: ~" ++ Width ++ ".18" ++ P,
+ [Indent, Tag, Data|Tl], Opts).
+
p(Encoding, Depth) ->
{Letter, Tl} = case Depth of
unlimited -> {"p", []};
@@ -866,6 +905,8 @@ p(Encoding, Depth) ->
modifier(latin1) -> "";
modifier(_) -> "t".
+nl(true,Else) -> Else;
+nl(false,_) -> "\n".
%%% -----------------------------------------------------------
%%% Stop a process and wait for it to terminate
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 8d1cc09a8b..9a1b92a87c 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -16,11 +16,50 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
+%%
+%% We allow upgrade from, and downgrade to all previous
+%% versions from the following OTP releases:
+%% - OTP 20
+%% - OTP 21
+%%
+%% We also allow upgrade from, and downgrade to all
+%% versions that have branched off from the above
+%% stated previous versions.
+%%
{"%VSN%",
- %% Up from - max one major revision back
- [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.*
- {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}],% OTP-21.*
- %% Down to - max one major revision back
- [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.*
- {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-20.*
-}.
+ [{<<"^3\\.4$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.5(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.5$">>,[restart_new_emulator]},
+ {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.6$">>,[restart_new_emulator]},
+ {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.8$">>,[restart_new_emulator]},
+ {<<"^3\\.8\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.8\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ [{<<"^3\\.4$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.5(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.5$">>,[restart_new_emulator]},
+ {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.6$">>,[restart_new_emulator]},
+ {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.8$">>,[restart_new_emulator]},
+ {<<"^3\\.8\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.8\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.