/*  OUTPUT_TO_CHARS.PL  */
 
 
:- module output_to_chars.
 
 
:- public output_to_chars/2,
          cos_to_chars/2.
 
 
/*
SPECIFICATION
-------------
 
The module implements a version of 'output' that sends its output to a
list of characters rather than to the COS. It also defines a more
general predicate for capturing as a list the output of an arbitrary
goal.
 
In the Tutor, the most obvious use for this is that it allows one to
turn the student's facts into character lists and edit those lists.
 
 
 
PUBLIC cos_to_chars( Goal+, L? ):
---------------------------------
 
Calls Goal, and unifies L with a list of the character codes sent along
Goal's current output stream. Action is undefined (calls 'bug') if
Goal fails. Newlines are represented as characters C such that
    is_newline_char(C)
where is_newline_char is defined in CHARS.PL.
 
Example:
   ?- cos_to_chars( (write(1),nl,write(2)), L ).
gives
    L = [49, 10, 50]
 
 
PUBLIC output_to_chars( T+, L? ):
---------------------------------
 
As output/1, but the character representation of T is converted to a
list of ASCII codes (integers) and unified with L. Newlines are
represented in L as ASCII 13.
 
Examples:
    ?- output_to_chars( fred, L ).
    L = [102, 114, 101, 100]
 
    ?-  N = 100,
        output_to_chars( 'Fact number'...N...'does not exist!'~~, L ).
        L = [70, 97, 99, 116, 32, 110, 117, 109, 98, 101, 114, 32,
             49, 48, 48, 32, 100, 111, 101, 115, 32, 110, 111, 116,
             32, 101, 120, 105, 115, 116, 33, 10, 10 ]
*/
 
 
/*
IMPLEMENTATION
--------------
 
There is no good way of doing this portably. One bad way is to write the
output to a scratch file and read the file back as a list of character
codes:
 
    cos_to_chars( Goal, LResult ) :-
        telling( COS ),
        tell( 'scratch' ),
        Goal,
        told,
        tell( COS ),
        seeing( CIS ),
        see( 'scratch' ), seen,
        see( 'scratch' ),
        read_file_as_chars(L),
        LResult = L,
        /* to make it steadfast */
        seen,
        ... need to delete the scratch file here ...,
        see( CIS ).
 
 
    output_to_chars( T, L ) :-
        cos_to_chars( output(T), L ).
 
 
    read_file_as_chars( L ) :-
        get0(C),
        read_file_as_chars( C, L ).
 
 
    read_file_as_chars( C, [] ) :-
        is_end_of_file_char(C),
        !.
 
    read_file_as_chars( Char, [Char|Chars] ) :-
        get0(C),
        read_file_as_chars( C, Chars ).
 
which reads all characters from the CIS, upto but not including the
end-of-file character, into list L.
 
This works, but is going to be slow. Use it only if you can find nothing
better. If you are lucky, your system may have in-memory "files" (like
Fortran encode and decode), which let you write in effect to a string
and read it back. This is a good solution. If not, perhaps you can call
an external language and grab "write"'s output buffer, returning the
characters as a list somehow.
 
Another way is to write your own predicates for converting terms to
character lists. This is easy for integers and atoms, tricky for reals
unless you know something about numerical analysis, and horrid for
general terms, because you have to take into account the possibility of
operator declarations. Besides, your implementor has already done the
work: why shouldn't you have the benefit of it?
 
So I am afraid I have not provided any predicates for doing this both
portably and quickly. If your system lacks this facility, I can't help.
If anyone _has_ a portable version of "write", please let me know, and I
will include it on the source disc.
 
To do it non-portably, I call into Pop-11, and implement
    cos_to_chars( Goal )
by redirecting the current output stream to a character consumer that
dumps its argument on the stack, and then calling Goal. This way, we
build up a sequence of all the characters emitted, and can then convert
it into a list.
 
 
There is a horrible bug in this: the original
    output_to_chars( a((a,b)), L )
inserted a spurious <true> in L and lost one of the brackets. Similar
things happened with other structures whose principal functor is comma.
I can't see why: bug in Poplog? To fix this, I put the test on true
in cos_to_chars in OUTPUT_TO_CHARS.P. It's really nasty (and not
guaranteed, since I don't know when else this <true> might be produced),
but I can't see why it happens.
*/
 
 
:- needs output/1.
 
 
/*  Load the Pop-11 code in OUTPUT_TO_CHARS.P.  */
:- pop_compile( 'output_to_chars.p' ).
 
 
cos_to_chars( Goal, L ) :-
    prolog_eval( cos_to_chars(quote(Goal)), L ).
 
 
output_to_chars( T, L ) :-
    prolog_eval( cos_to_chars(quote(output(T))), L ).
 
 
:- endmodule.
