-module( string_io ). -export( [ close/1, open/2 ] ). %% @spec close(Server::pid()) -> string() close( Server ) -> Self = erl.lang.proc:self(), send_to( Server, {stop, Self} ), case receive_all( ) of {stop, Self, String} -> String; Other -> erl.lang:throw( {error, Other} ) end. %% @spec open(String::string(), Modes::list()) -> tuple() % input % Modes : list() of atom(). atoms are => read, write, append % returns % {ok, pid()} % execption open( String, Modes ) when is_list( String ) -> case decode( Modes ) of error -> erl.lang:throw( {error, {mode, Modes}} ); Mode -> Caller = erl.lang.proc:self(), Fun = fun ( ) -> % the new process will be informed about the ''death'' of % the process that created it erl.lang.proc:monitor( process, Caller ), String_buffer = string_buffer( String, Mode ), loop( String_buffer ) end, {ok, erl.lang.proc:spawn( Fun )} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % input % Modes : list() of atom(). read, write, append are the atoms of interest. % returns % read | write | append | read_write | read_append | error % % check with string_buffer/2 if any return values are changed decode( Modes ) -> case {erl.lang.list:is_element( read, Modes ), erl.lang.list:is_element( write, Modes ), erl.lang.list:is_element( append, Modes )} of {true, false, false} -> read; {false, true, false} -> write; {true, true, false} -> read_write; {false, _, true} -> append; {true, _, true} -> read_append; _Other -> error end. % input % How_many : integer(), how many chars that should be read % String_buffer : #string_buffer{} % return % {Read, #string_buffer{}} % Read : string() | eof get_chars( How_many, String_buffer ) -> case string_buffer_remaining_length( String_buffer ) of 0 -> {eof, String_buffer}; Length -> case (Length > How_many) of true -> % take some of the remaining Read = string_buffer_remaining( String_buffer, How_many ), {Read, string_buffer_after_read( String_buffer, How_many )}; false -> % take all of the remaining Remaining = string_buffer_remaining( String_buffer ), {Remaining, string_buffer_after_read( String_buffer, all_read )} end end. % input % Module : module % Function : function % Arguments : arguments % Cont : the part of a result from previous % apply(Mod, Func, [Cont, String | Args]) % that Module:Function() wants to have again, % to be able to build the whole result % String_buffer : #string_buffer{} % return % {Result, #string_buffer{}} % Result : ok | {error, Reason} get_until( Module, Function, Arguments, Cont, String_buffer ) -> String = string_buffer_remaining( String_buffer ), % FIXME apply => erl.lang:apply case catch erlang:apply(Module, Function, [Cont, String | Arguments]) of {done, Result, eof} -> {Result, string_buffer_after_read( String_buffer, all_read )}; {done, Result, Chars_remaining} -> {Result, string_buffer_after_read( String_buffer, Chars_remaining )}; {more, Cont_New} -> % we do not have any more characters. % try again with emptied String_buffer get_until( Module, Function, Arguments, Cont_New, string_buffer_after_read( String_buffer, all_read ) ); _Other -> {{error, {apply, Module, Function, Arguments}}, String_buffer} end. % input % Request : tuple() % Acc : {Status, #string_buffer{}} % return % {Result, #string_buffer{}} % Result : ok | {error, Reason} io_request( {put_chars, Chars}, {ok, String_buffer} ) -> case string_buffer_is_writeable( String_buffer ) of true -> put_chars( Chars, String_buffer ); false -> {{error, {mode, is_readonly}}, String_buffer} end; io_request( {put_chars, Module, Func, Args}, String_buffer ) -> % FIXME apply => erl.lang:apply io_request( {put_chars, catch erlang:apply( Module, Func, Args )}, String_buffer ); io_request( {get_chars, _Prompt, How_many}, {ok, String_buffer} ) -> case string_buffer_is_readable( String_buffer ) of true -> get_chars( How_many, String_buffer ); false -> {{error, {mode, is_writeonly}}, String_buffer} end; io_request( {get_until, _Prompt, Module, Func, Args}, {ok, String_buffer} ) -> case string_buffer_is_readable(String_buffer) of true -> get_until( Module, Func, Args, [], String_buffer ); false -> {{error, {mode, is_writeonly}}, String_buffer} end; io_request( {requests, Requests}, {ok, String_buffer} ) -> Fun = fun( Request, Acc ) -> io_request( Request, Acc ) end, erl.lang.list:foldl( Fun, {ok, String_buffer}, Requests ); io_request( Request, {_Status, String_buffer} ) -> {{error, {request_unknown, Request}}, String_buffer}. loop( String_buffer ) -> case receive_all( ) of {stop, Sender} -> send_to( Sender, {stop, Sender, string_buffer_to_string( String_buffer )} ), erl.lang:exit(normal); {io_request, Sender, Reply_as, Request} -> {Result, New} = io_request( Request, {ok, String_buffer} ), send_to( Sender, {io_reply, Reply_as, Result} ), loop( New ); {'DOWN', _Ref, _Type, _Object, _Info} -> erl.lang:exit(normal); _Other -> loop( String_buffer ) end. % handle io reqest of type put chars % transform binary to list in input % return tuple % {Result, #string_buffer{}} put_chars( Binary, String_buffer ) when is_binary(Binary) -> put_chars( erl.lang.binary:to_list(Binary), String_buffer ); put_chars( Chars, String_buffer ) when is_list(Chars) -> {ok, string_buffer_after_write( String_buffer, Chars )}; put_chars( Error, String_buffer ) -> {{error, Error}, String_buffer}. receive_all( ) -> receive All -> %io:format( "receive_all ~w ~w~n", [All, erl.lang.proc:self()]), All end. send_to( Receiver, Data ) -> %io:format( "send_to ~w ~w ~w~n", [ Receiver, Data, erl.lang.proc:self() ]), Receiver ! Data. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -record( string_buffer, { is_readonly=false, % is it possible to write? is_writeonly=false, % is it possible to read? is_append=false, % should all writes go to the end of the string? remaining_length=0, % length of initial string that remains to be read/written remaining=eof, % part of initial string that remains to be read/written % eof is used by get_until() accumulator=[] % part of initial string that has been read/written % it can no longer change } ). % create new #string_buffer{} with String value % depending upon Mode the value goes in various places % return % #string_buffer{} string_buffer( String, Mode )-> case Mode of read -> #string_buffer{is_readonly=true, remaining=String, remaining_length=erl.lang.list:length( String )}; write -> % file:open() truncates file on write only. same here #string_buffer{is_writeonly=true}; append -> #string_buffer{is_writeonly=true, is_append=true, accumulator=String}; read_write -> #string_buffer{remaining=String, remaining_length=erl.lang.list:length( String )}; read_append -> % remember to put all of remaining into accumulator at first append #string_buffer{is_append=true, remaining=String, remaining_length=erl.lang.list:length( String )} end. % do the things neccessary to modify string buffer after a _read of Chars % return % #string_buffer{} % first all chars read string_buffer_after_read( String_buffer, all_read ) -> #string_buffer{remaining=Remaining} = String_buffer, string_buffer_modify( String_buffer, Remaining ); % second is a clause with How_many chars read string_buffer_after_read( String_buffer, How_many ) when is_integer( How_many ) -> #string_buffer{remaining=Remaining, remaining_length=Remaining_length} = String_buffer, {Read, Rest} = erl.lang.list:split( How_many, Remaining ), string_buffer_modify( String_buffer, Read, Rest, Remaining_length-How_many ); % last is a clause with New_remaining chars, % after read of some of the remaining chars. string_buffer_after_read( String_buffer, New_remaining ) -> #string_buffer{remaining=Remaining, remaining_length=Remaining_length} = String_buffer, New_remaining_length = erl.lang.list:length( New_remaining ), Read_length = Remaining_length - New_remaining_length, Read = erl.lang.list:take( Read_length, Remaining ), string_buffer_modify( String_buffer, Read, New_remaining, New_remaining_length ). % do the things neccessary to modify string buffer after a write of Chars % return % #string_buffer{} string_buffer_after_write( String_buffer, Chars ) -> #string_buffer{is_append=Append, remaining_length=Remaining_length} = String_buffer, case {Append, Remaining_length} of {true, 0} -> % append with nothing left in remaining string_buffer_modify( String_buffer, Chars ); {true, _Remaining_length} -> % append with some chars left in remaining % save what we have left in remaining first #string_buffer{remaining=Remaining} = String_buffer, string_buffer_modify( String_buffer, erl.lang.list:append(Remaining, Chars) ); {false, _Remaining_length} -> % no append. write over chars in remining Length = erl.lang.list:length( Chars ), case (Length > Remaining_length) of true -> % write over all (just ignore them) string_buffer_modify( String_buffer, Chars ); false -> % write over some. save the rest. #string_buffer{remaining=Remaining} = String_buffer, Rest = erl.lang.list:drop( Length, Remaining ), string_buffer_modify( String_buffer, Chars, Rest, Remaining_length-Length ) end end. % check if write is ok in string buffer % return % true | false string_buffer_is_writeable( String_buffer ) -> #string_buffer{is_readonly=Readonly} = String_buffer, not Readonly. % check if read is ok from string buffer % return % true | false string_buffer_is_readable( String_buffer ) -> #string_buffer{is_writeonly=Writeonly} = String_buffer, not Writeonly. % this is a simpler interface to string_buffer_modify/4 % Done is the new addition to the accumulator % if Done is eof we sanitise input % return % #string_buffer{} string_buffer_modify( String_buffer, eof ) -> string_buffer_modify( String_buffer, [], eof, 0 ); string_buffer_modify( String_buffer, Done ) -> string_buffer_modify( String_buffer, Done, eof, 0 ). % modify existing string buffer. % Done is the new addition to the accumulator % if Remaining is [] we sanitise input % if Done is [] we skip append/2 % return % #string_buffer{} string_buffer_modify( String_buffer, Done, [], _Remaining_length ) -> string_buffer_modify( String_buffer, Done, eof, 0 ); string_buffer_modify( String_buffer, [], Remaining, Remaining_length ) -> String_buffer#string_buffer{ remaining=Remaining, remaining_length=Remaining_length}; string_buffer_modify( String_buffer, Done, Remaining, Remaining_length ) -> #string_buffer{accumulator=Accumulator}=String_buffer, String_buffer#string_buffer{ accumulator=erl.lang.list:append( Accumulator, Done ), remaining=Remaining, remaining_length=Remaining_length}. % get remaining part from string_buffer. % return % string() | eof string_buffer_remaining( #string_buffer{remaining=String} ) -> String. % get How_many characters of remaining part from string_buffer. % return % string() string_buffer_remaining( #string_buffer{remaining=String}, How_many ) -> erl.lang.list:take( How_many, String ). % get length of remaining part from string_buffer. % return % integer() string_buffer_remaining_length( #string_buffer{remaining_length=Length} ) -> Length. % get all strings from string_buffer as one string. % return % string() string_buffer_to_string( #string_buffer{accumulator=A, remaining=R} ) -> Remaining = case R of eof -> []; String -> String end, erl.lang.list:flatten( erl.lang.list:append( A, Remaining ) ).