%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 1996-2025. 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.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(io_lib_fread).
-moduledoc false.

-compile(nowarn_deprecated_catch).

%% Formatted input functions of io library.

-export([fread/2,fread/3]).

-import(lists, [reverse/1]).

-define(is_whitespace(C),
	((C) =:= $\s orelse (C) =:= $\t
	 orelse (C) =:= $\r orelse (C) =:= $\n)).

%%-----------------------------------------------------------------------

%% fread(Continuation, CharList, FormatString)
%%  This is the main function into the re-entrant formatted reader. It
%%  repeatedly collects lines and calls fread/2 to format the input until
%%  all the format string has been used. And it counts the characters.

-spec fread(Continuation, String, Format) -> Return when
      Continuation :: io_lib:continuation() |  [],
      String :: string() | 'eof',
      Format :: string(),
      Return :: {'more', Continuation1 :: io_lib:continuation()}
              | {'done', Result, LeftOverChars :: string()},
      Result :: {'ok', InputList :: [io_lib:fread_item()]}
              | 'eof'
              | {'error', {'fread', What :: io_lib:fread_error()}}.

fread([], Chars, Format) ->
    %%io:format("FREAD: ~w `~s'~n", [Format,Chars]),
    fread_collect(Format, [], 0, [], Chars);
fread({Format,Stack,N,Results}=_Continuation, Chars, _) ->
    %%io:format("FREAD: ~w `~s'~n", [_Continuation,Chars]),
    fread_collect(Format, Stack, N, Results, Chars).

fread_collect(Format, [$\r|Stack], N, Results, [$\n|Chars]) ->
    fread_line(Format, reverse(Stack), N, Results, Chars, [$\r,$\n]);
fread_collect(Format, Stack, N, Results, [$\n|Chars]) ->
    fread_line(Format, reverse(Stack), N, Results, Chars, [$\n]);
fread_collect(Format, Stack, N, Results, []) ->
    Continuation = {Format,Stack,N,Results},
    {more,Continuation};
fread_collect(Format, [$\r|Stack], N, Results, Chars) -> % Maybe eof
    fread_line(Format, reverse(Stack), N, Results, Chars, [$\r]);
fread_collect(Format, Stack, N, Results, [C|Chars]) ->
    fread_collect(Format, [C|Stack], N, Results, Chars);
fread_collect(Format, Stack, N, Results, Chars) -> % eof
    fread_line(Format, reverse(Stack), N, Results, Chars, []).

fread_line(Format0, Line, N0, Results0, More, Newline) ->
    %%io:format("FREAD1: `~s' `~s'~n", [Format0,Line]),
    Chars = if is_list(More) -> More; true -> [] end,
    case fread(Format0, Line, N0, Results0) of
	{ok,Results,[]} ->
	    {done,{ok,Results},Chars};
	{ok,Results,Rest} ->
	    %% Don't lose the whitespace
	    {done,{ok,Results},Rest++(Newline++Chars)};
	%% fread/4 should not return {more,...} on eof; guard just in case...
	%% Count newline characters here since fread/4 does not get them.
	{more,Format,N,Results} when is_list(Line), is_list(More) ->
	    fread_collect(Format, [], N+length(Newline), Results, More);
	{more,Format,N,Results} when is_list(Line) -> % eof
	    fread_line(Format, eof, N+length(Newline), Results, More, []);
	Other ->				%An error has occurred
	    {done,Other,More}
    end.


%% Conventions
%%   ~s 	String White terminated
%%   ~d         Integer terminated by ~[0-9]
%%   ~u         Unsigned integer in base 2..36, no leading whitespace
%%   ~-         Optional sign character, no leading whitespace
%%   ~f         Float
%%   ~a         as ~s but converted to an atom
%%   ~c		characters without any stripping
%%   ~n		number of characters scanned
%%   WHITE      Skip white space
%%   X          Literal X

-spec fread(Format, String) -> Result when
      Format :: string(),
      String :: string(),
      Result :: {'ok', InputList :: [io_lib:fread_item()],
                 LeftOverChars :: string()}
              | {'more', RestFormat :: string(),
                 Nchars :: non_neg_integer(),
                 InputStack :: io_lib:chars()}
              | {'error', {'fread', What :: io_lib:fread_error()}}.

fread(Format, Line) ->
    fread(Format, Line, 0, []).

fread([$~|Format0]=AllFormat, Line, N, Results) ->
    {Format,F,Sup,Unicode} = fread_field(Format0),
    fread1(Format, F, Sup, Unicode, Line, N, Results, AllFormat);
fread([C|Format], Line, N, Results) when ?is_whitespace(C) ->
    fread_skip_white(Format, Line, N, Results);
fread([C|Format], [C|Line], N, Results) ->
    fread(Format, Line, N+1, Results);
fread([_F|_Format], [_C|_Line], _N, _Results) ->
    fread_error(input);
fread([_|_]=Format, [], N, Results) ->
    {more,Format,N,Results};
fread([_|_], eof, 0, []) ->
    %% This is at start of input so no error.
    eof;
fread([_|_], eof, _N, _Results) ->
    %% This is an error as there is no more input.
    fread_error(input);
fread([], Line, _N, Results) ->
    {ok,reverse(Results),Line}.

fread_skip_white(Format, [C|Line], N, Results) when ?is_whitespace(C) ->
    fread_skip_white(Format, Line, N+1, Results);
fread_skip_white(Format, Line, N, Results) ->
    fread(Format, Line, N, Results).

%% fread_field(Format) 
%%  Reads the field specification paramters. Returns:
%%
%%	{RestFormat,FieldWidth,Suppress}

fread_field([$*|Format]) -> fread_field(Format, true, false);
fread_field(Format) -> fread_field(Format, false, false).

fread_field([C|Format], Sup, Unic) when C >= $0, C =< $9 ->
    fread_field(Format, C - $0, Sup, Unic);
fread_field([$t|Format], Sup, _Unic) -> 
    {Format,none,Sup,true};
fread_field(Format, Sup, Unic) -> 
    {Format,none,Sup,Unic}.

fread_field([C|Format], F, Sup, Unic) when C >= $0, C =< $9 ->
    fread_field(Format, 10*F + C - $0, Sup, Unic);
fread_field([$t|Format], F, Sup, _Unic) ->
    {Format,F,Sup,true};
fread_field(Format, F, Sup, Unic) ->
    {Format,F,Sup,Unic}.

%% fread1(Format, FieldWidth, Suppress, Unicode, Line, N, Results, AllFormat)
%% fread1(Format, FieldWidth, Suppress, Unicode, Line, N, Results)
%%  The main dispatch function for the formatting commands. Done in two
%%  stages so format commands that need no input can always be processed.

fread1([$l|Format], _F, Sup, _U, Line, N, Res, _AllFormat) ->
    fread(Format, Line, N, fread_result(Sup, N, Res));
fread1(_Format, _F, _Sup, _U, [], N, Res, AllFormat) ->
    %% Need more input here.
    {more,AllFormat,N,Res};
fread1(_Format, _F, _Sup, _U, eof, 0, [], _AllFormat) ->
    %% This is at start of input so no error.
    eof;
fread1(_Format, _F, _Sup, _U, eof, _N, _Res, _AllFormat) ->
    %% This is an error as there is no more input.
    fread_error(input);
fread1(Format, F, Sup, U, Line, N, Res, _AllFormat) ->
    fread1(Format, F, Sup, U, Line, N, Res).

fread1([$f|Format], none, Sup, false, Line0, N0, Res) ->
    {Line,N,Cs} = fread_float_cs(Line0, N0),
    fread_float(Cs, Sup, Format, Line, N, Res);
fread1([$f|Format], F, Sup, false, Line0, N, Res) ->
    {Line,Cs} = fread_chars(Line0, F, false),
    fread_float(Cs, Sup, Format, Line, N+F, Res);
fread1([$d|Format], none, Sup, false, Line0, N0, Res) ->
    {Line,N,Cs} = fread_int_cs(Line0, N0),
    fread_integer(Cs, 10, Sup, Format, Line, N, Res);
fread1([$d|Format], F, Sup, false, Line0, N, Res) ->
    {Line,Cs} = fread_chars(Line0, F, false),
    fread_integer(Cs, 10, Sup, Format, Line, N+F, Res);
fread1([$u|Format], none, Sup, false, Line0, N0, Res) ->
    {Line,N,Cs} = fread_digits(Line0, N0, 10, []),
    fread_unsigned(Cs, 10, Sup, Format, Line, N, Res);
fread1([$u|Format], F, Sup, false, Line0, N0, Res) when F >= 2, F =< 1+$Z-$A+10 ->
    {Line,N,Cs} = fread_digits(Line0, N0, F, []),
    fread_unsigned(Cs, F, Sup, Format, Line, N, Res);
fread1([$-|Format], _F, Sup, false, Line, N, Res) ->
    fread_sign_char(Sup, Format, Line, N, Res);
fread1([$#|Format], none, Sup, false, Line0, N0, Res) ->
    case catch
	begin
	    {Line1,N1,B1} = fread_base(Line0, N0),
	    B = abs(B1),
	    true = (B >= 2) and (B =< 1+$Z-$A+10),
	    {Line2,N2,Cs2} = fread_digits(Line1, N1, B, []),
	    fread_based(reverse(Cs2), B1, Sup, Format, Line2, N2, Res)
	end of
	{'EXIT',_} ->
	    fread_error(based);
	Other ->
	    Other
    end;
fread1([$#|Format], F, Sup, false, Line0, N, Res) ->
    case catch
	begin
	    {Line1,Cs1} = fread_chars(Line0, F, false),
	    {Line2,_,B2} = fread_base(reverse(Cs1), N),
	    true = ((B2 >= 2) and (B2 =< 1+$Z-$A+10)),
	    fread_based(Line2, B2, Sup, Format, Line1, N+F, Res)
	end	of
	{'EXIT',_} ->
	    fread_error(based);
	Other ->
	    Other
    end;
fread1([$s|Format], none, Sup, U, Line0, N0, Res) ->
    {Line,N,Cs} = fread_string_cs(Line0, N0, U),
    fread_string(Cs, Sup, U, Format, Line, N, Res);
fread1([$s|Format], F, Sup, U, Line0, N, Res) ->
    {Line,Cs} = fread_chars(Line0, F, U),
    fread_string(Cs, Sup, U, Format, Line, N+F, Res);
fread1([$a|Format], none, Sup, U, Line0, N0, Res) ->
    {Line,N,Cs} = fread_string_cs(Line0, N0, U),
    fread_atom(Cs, Sup, Format, Line, N, Res);
fread1([$a|Format], F, Sup, false, Line0, N, Res) ->
    {Line,Cs} = fread_chars(Line0, F, false),
    fread_atom(Cs, Sup, Format, Line, N+F, Res);
fread1([$c|Format], none, Sup, U, Line0, N, Res) ->
    {Line,Cs} = fread_chars(Line0, 1, U),
    fread_chars(Cs, Sup, U, Format, Line, N+1, Res);
fread1([$c|Format], F, Sup, U, Line0, N, Res) ->
    {Line,Cs} = fread_chars(Line0, F, U),
    fread_chars(Cs, Sup, U, Format, Line, N+F, Res);
fread1([$~|Format], _F, _Sup, _U, [$~|Line], N, Res) ->
    fread(Format, Line, N+1, Res);
fread1(_Format, _F, _Sup, _U, _Line, _N, _Res) ->
    fread_error(format).

%% fread_float(FloatChars, Suppress, Format, Line, N, Results)

fread_float(Cs, Sup, Format, Line, N, Res) ->
    case catch list_to_float(fread_skip_white(reverse(Cs))) of
	{'EXIT',_} ->
	    fread_error(float);
	Float ->
	    fread(Format, Line, N, fread_result(Sup, Float, Res))
    end.

%% fread_integer(IntegerChars, Base, Suppress, Format, Line, N, Results)

fread_integer(Cs, Base, Sup, Format, Line, N, Res) ->
    case catch erlang:list_to_integer(fread_skip_white(reverse(Cs)), Base) of
	{'EXIT',_} ->
	    fread_error(integer);
	Integer ->
	    fread(Format, Line, N, fread_result(Sup, Integer, Res))
    end.


%% fread_unsigned(IntegerChars, Base, Suppress, Format, Line, N, Results)

fread_unsigned(Cs, Base, Sup, Format, Line, N, Res) ->
    case catch erlang:list_to_integer(fread_skip_white(reverse(Cs)), Base) of
	{'EXIT',_} ->
	    fread_error(unsigned);
	Integer ->
	    fread(Format, Line, N, fread_result(Sup, Integer, Res))
    end.
    

%% fread_based(IntegerChars, Base, Suppress, Format, Line, N, Results)

fread_based(Cs0, B, Sup, Format, Line, N, Res) ->
    {Cs,Base} = if B < 0 -> {[$-|Cs0],-B};
		   true ->  {Cs0,B}
		end,
    I = erlang:list_to_integer(Cs, Base),
    fread(Format, Line, N, fread_result(Sup, I, Res)).
    

%% fread_sign_char(Suppress, Format, Line, N, Results)

fread_sign_char(Sup, Format, [$-|Line], N, Res) ->
    fread(Format, Line, N+1, fread_result(Sup, -1, Res));
fread_sign_char(Sup, Format, [$+|Line], N, Res) ->
    fread(Format, Line, N+1, fread_result(Sup, +1, Res));
fread_sign_char(Sup, Format, Line, N, Res) ->
    fread(Format, Line, N, fread_result(Sup, 1, Res)).


%% fread_string(StringChars, Suppress, Format, Line, N, Results)

fread_string(error, _Sup, _U, _Format, _Line, _N, _Res) ->
    fread_error(string);
fread_string(Cs0, Sup, U, Format, Line, N, Res) ->
    Cs = fread_skip_white(reverse(fread_skip_white(Cs0))),
    fread(Format, Line, N, fread_convert(fread_result(Sup, Cs, Res),U)).

%% fread_atom(AtomChars, Suppress, Format, Line, N, Results)

fread_atom(error, _Sup, _Format, _Line, _N, _Res) ->
    fread_error(atom);
fread_atom(Cs0, Sup, Format, Line, N, Res) ->
    Cs = fread_skip_white(reverse(fread_skip_white(Cs0))),
    fread(Format, Line, N, fread_result(Sup, list_to_atom(Cs), Res)).

%% fread_chars(Characters, Suppress, Format, Line, N, Results)

fread_chars(error, _Sup, _U, _Format, _Line, _N, _Res) ->
    fread_error(character);
fread_chars(Cs, Sup, U, Format, Line, N, Res) ->
    fread(Format, Line, N, fread_convert(fread_result(Sup, reverse(Cs), Res),U)).

%% fread_chars(Line, Count)

fread_chars(Line, C, U) ->
    fread_chars(C, Line, U, []).

fread_chars(0, Line, _U, Cs) -> {Line,Cs};
fread_chars(_N, [$\n|Line], _U, _Cs) -> {[$\n|Line],error};
fread_chars(N, [C|Line], true, Cs) ->
    fread_chars(N-1, Line, true, [C|Cs]);
fread_chars(N, [C|Line], false, Cs) when C >= 0, C =< 255 ->
    fread_chars(N-1, Line, false, [C|Cs]);
fread_chars(_N, L, _U, _Cs) ->
    {L,error}.
%%fread_chars(_N, [], _U,_Cs) -> 
%%    {[],error}.

%% fread_int_cs(Line, N)

fread_int_cs(Line0, N0) ->
    {Line1,N1} = fread_skip_white(Line0, N0),
    {Line,N,Cs} = fread_sign(Line1, N1, []),
    fread_digits(Line, N, Cs).

%% fread_float_cs(Line, N)
%%  A float is "[+|-][0-9]+.[0-9]+[[E|e][+|-][09-]+]

fread_float_cs(Line0, N0) ->
    {Line1,N1} = fread_skip_white(Line0, N0),
    {Line2,N2,Cs2} = fread_sign(Line1, N1, []),
    {Line,N,Cs} = fread_digits(Line2, N2, Cs2),
    fread_float_cs_1(Line, N, Cs).

fread_float_cs_1([$.|Line0], N0, Cs0) ->
    {Line,N,Cs} = fread_digits(Line0, N0+1, [$.|Cs0]),
    fread_float_cs_2(Line, N, Cs);
fread_float_cs_1(Line, N, Cs) ->
    {Line,N,Cs}.

fread_float_cs_2([$e|Line0], N0, Cs0) ->
    {Line,N,Cs} = fread_sign(Line0, N0+1, [$e|Cs0]),
    fread_digits(Line, N, Cs);
fread_float_cs_2([$E|Line0], N0, Cs0) ->
    {Line,N,Cs} = fread_sign(Line0, N0+1, [$E|Cs0]),
    fread_digits(Line, N, Cs);
fread_float_cs_2(Line, N, Cs) ->
    {Line,N,Cs}.

%% fread_string_cs(Line, N, Unicode)

fread_string_cs(Line0, N0, false) ->
    {Line,N} = fread_skip_white(Line0, N0),
    fread_skip_latin1_nonwhite(Line, N, []);
fread_string_cs(Line0, N0, true) ->
    {Line,N} = fread_skip_white(Line0, N0),
    fread_skip_nonwhite(Line, N, []).

%% fread_skip_white(Line)
%% fread_skip_white(Line, N)
%% fread_skip_nonwhite(Line, N, Characters)
%% fread_sign(Line, N, Characters)
%% fread_digits(Line, N, Characters)
%% fread_digits(Line, N, Base, Characters)
%%  Read segments of things, return "thing" characters in reverse order.

fread_skip_white([C|Line]) when ?is_whitespace(C) ->
    fread_skip_white(Line);
fread_skip_white(Line) -> Line.

fread_skip_white([C|Line], N) when ?is_whitespace(C) ->
    fread_skip_white(Line, N+1);
fread_skip_white(Line, N) -> {Line,N}.

fread_skip_latin1_nonwhite([C|Line], N, Cs) when ?is_whitespace(C) ->
    {[C|Line],N,Cs};
fread_skip_latin1_nonwhite([C|Line], N, []) when C > 255 ->
    {[C|Line],N,error};
fread_skip_latin1_nonwhite([C|Line], N, Cs) when C > 255 ->
    {[C|Line],N,Cs};
fread_skip_latin1_nonwhite([C|Line], N, Cs) ->
    fread_skip_latin1_nonwhite(Line, N+1, [C|Cs]);
fread_skip_latin1_nonwhite([], N, Cs) -> {[],N,Cs}.

fread_skip_nonwhite([C|Line], N, Cs) when ?is_whitespace(C) ->
    {[C|Line],N,Cs};
fread_skip_nonwhite([C|Line], N, Cs) ->
    fread_skip_nonwhite(Line, N+1, [C|Cs]);
fread_skip_nonwhite([], N, Cs) -> {[],N,Cs}.

fread_sign([$+|Line], N, Cs) -> {Line,N+1,[$+|Cs]};
fread_sign([$-|Line], N, Cs) -> {Line,N+1,[$-|Cs]};
fread_sign(Line, N, Cs) -> {Line,N,Cs}.

fread_base(Line0, N0) ->
    {[$#|Line1],N1,Cs1} = fread_int_cs(Line0, N0),
    B = list_to_integer(reverse(Cs1)),
    {Line1,N1+1,B}.

fread_digits([C|Line], N, Cs) when C >= $0, C =< $9 ->
    fread_digits(Line, N+1, [C|Cs]);
fread_digits(Line, N, Cs) -> {Line,N,Cs}.

fread_digits([C|Line], N, Base, Cs) when C >= $0, C =< $9 ->
    fread_digits(Line, N+1, Base, [C|Cs]);
fread_digits([C|Line], N, Base, Cs) when C >= $A, C < $A+Base-10 ->
    fread_digits(Line, N+1, Base, [C|Cs]);
fread_digits([C|Line], N, Base, Cs) when C >= $a, C < $a+Base-10 ->
    fread_digits(Line, N+1, Base, [C|Cs]);
fread_digits(Line, N, _Base, Cs) -> {Line,N,Cs}.



%% fread_result(Suppress, Value, Results)

fread_result(true, _V, Res) -> Res;
fread_result(false, V, Res) -> [V|Res].

-ifdef(UNICODE_AS_BINARIES).
fread_convert([L|R],true) when is_list(L) ->
    [unicode:characters_to_binary(L) | R];
fread_convert(Any,_) ->
    Any.
-else.
fread_convert(Any,_) ->
    Any.
-endif.
fread_error(In) ->
    {error,{fread,In}}.
