/*  FILE_TO_TERMS.PL  */
 
 
/*
This program defines the predicate
    lesson(N)
which reads the script file called LOGIC$LIB:LESSON<N>. and converts it
to LOGIC$LIB:LESSON<N>.SCR.
 
The idea is that the input file contains one or more 'sections'. Each
section is of the form
    Line1
    Other lines
where Line1 starts with a ~ (tilde) and continues with what I shall call
a tag. This is usually a short name or number identifying the section.
None of the OtherLines may start with ~ .
 
The chunk can be terminated either by end-of-file, or by the first line
of another section.
 
'lesson' will translate the file into a sequence of clauses:
    script_text( Tag, Text )
and write them to the output file. Here, Tag is the tag on Line1. For
script files, it's the section number. Text is the OtherLines, in a form
outputtable by 'output'. Essentially, Text will be
    Line2 <>~
    Line3 <>~
    ...
    LineN ~
 
 
'lesson' calls file_to_terms( InputFileName, OutputFileName, Functor ).
Using file_to_terms you can specify that the functor for each clause be
something other than 'script_text'.
 
 
The predicates do not catch file errors.
*/
 
 
/*
For the purpose of the code below, see MASTER.PL. Some of it can be
removed if not using Poplog.
*/
 
 
:- library(macro).
:- killmac("prolog").
:- killmac("teach").
:- killmac("help").
 
:- current_op(Prec, xfx, ':-'),
    op(Prec, fx, [module, public, dynamic]).
 
pop_compile(F) :-
    prolog_eval(compile(><(F,''))).
 
:- reconsult( 'lib.pl' ).
 
:- lib( chars ).
:- lib( output ).
:- lib( output_to_chars ).
:- lib( lists ).
:- lib( read_line ).
 
 
/*  lesson( N+ ):
        Main Predicate.
 
        N is an integer. Read file LOGIC$LIB:LESSON<N>. and convert to
        LOGIC$LIB:LESSON<N>.SCR.
*/
lesson(N) :-
    cos_to_chars( write(N), NumberChars ),
    append_n( [ "logic$lib:lesson", NumberChars, "." ], InputFileChars ),
    append_n( [ "logic$lib:lesson", NumberChars, ".SCR" ], OutputFileChars ),
    name( InputFileName, InputFileChars ),
    name( OutputFileName, OutputFileChars ),
    file_to_terms( InputFileName, OutputFileName, script_text ).
 
 
/*  file_to_terms( In+, Out+, Functor+ ):
        In and Out are input and output file names. Convert each section
        in In to a corresponding clause in Out, using Functor as the
        predicate.
*/
file_to_terms( Input, Output, Functor ) :-
    seeing( CIS ),
    telling( COS ),
    see( Input ), seen,
    /*  Ensure that if file was open, it's re-opened at the start. */
    see( Input ),
    tell( Output ),
    write( '/*  ' ), write( Output ), write( '  */' ), nl,
    file_to_terms( Functor ),
    seen, see( CIS ),
    told, tell( COS ).
 
 
/*  file_to_terms( Functor+ ):
        Called by file_to_terms/2. Convert each section read from the
        CIS in In to a corresponding clause on the COS, using Functor as
        the predicate.
*/
file_to_terms( Functor ) :-
    read_line( Line, skip_blanks ),
    rest_file_to_terms( Line, Functor ).
 
 
/*  rest_file_to_terms( TerminatingLine+, Functor+ ):
        Called by file_to_terms/1. TerminatingLine is the line
        terminating a section: it will be either end_of_file or the
        first line (as a character list) of the next section. Functor is
        the functor for clauses.
 
        If TerminatingLine marks the end of file, do nothing. Else read
        the rest of the section and write it as a clause, then repeat
        with all following sections.
*/
rest_file_to_terms( end_of_file, _ ) :- !.
 
rest_file_to_terms( Line, Functor ) :-
    is_tag_line( Line, Tag ),
    !,
    nl, nl,
    write( Functor ), write( '(' ), nl,
    write( Tag ), write( ',' ), nl,
    read_text( SectionText, TerminatingLine ),
    write_text( SectionText ), nl,
    write( ').' ), nl,
    rest_file_to_terms( TerminatingLine, Functor ).
 
rest_file_to_terms( Line, Functor ) :-
    write( '*** ERROR ***' ), nl,
    write( 'I expected a tag line (starting with ~), but found this:'), nl,
    output( puts(Line)~ ),
    write( 'I shall stop now.' ), nl.
/*  Must return from caller so can close files */
 
 
/*  read_text( SectionText-, TerminatingLine- ):
        We are on the second line of a section. Read the remaining lines
        into SectionText: this will become a list of lines, in reverse
        order. TerminatingLine becomes the line following the last one
        of the section.
*/
read_text( SectionText, TerminatingLine ) :-
    read_line( Line, dont_skip_blanks ),
    read_rest_of_text( Line, [], SectionText, TerminatingLine ).
 
 
/*  read_rest_of_text( CurrentLine+, SoFar+,
                       SectionText-, TerminatingLine-
                     ):
        CurrentLine is the line of section text just read. SoFar is the
        text of that part of the section before CurrentLine. Return the
        complete section text (in reverse order) in SectionText, and the
        line following the last line of the section in TerminatingLine.
*/
read_rest_of_text( Line, SoFar, SoFar, Line ) :-
    is_tag_line( Line, _ ),
    !.
 
read_rest_of_text( end_of_file, SoFar, SoFar, end_of_file ) :- !.
 
read_rest_of_text( Line, SoFar, SectionText, TerminatingLine ) :-
    read_line( NextLine, dont_skip_blanks ),
    name( LineAsAtom, Line ),
    read_rest_of_text( NextLine, [LineAsAtom|SoFar],
                       SectionText, TerminatingLine ).
 
 
/*  read_line( List-, WhetherSkipBlanks+ ):
        As read_line_as_chars/1, but skips past any lines beginning with
        a percent (%). If WhetherSkipBlanks = skip_blanks, also skips
        blank lines. List will become either 'end_of_file' or the
        characters making up the line read.
*/
read_line( List, WhetherSkipBlanks ) :-
    read_line_as_chars( Chars ),
    (
        Chars = [ C | _ ], is_percent_char( C )
    ->
        read_line( List, WhetherSkipBlanks )
    ;
        Chars = [], WhetherSkipBlanks = skip_blanks
    ->
        read_line( List, WhetherSkipBlanks )
    ;
        List = Chars
    ).
 
 
/*  write_text( SectionText+ ):
        SectionText is a list of lines in reverse order. If there is an
        run of blank lines at the end, remove it. Then write the rest in
        a form outputtable by output.
*/
write_text( [] ) :-
    !,
    write( '' ).
 
write_text( [''|T] ) :-
    !,
    write_text( T ).
 
write_text( [H|T] ) :-
    write_text_1( T ),
    write_atom_as_quoted_atom( H ), write( '~' ).
 
 
/*  write_text_1( Text+ ):
        As write_text, but the run of final blank lines (if any) has
        been removed.
*/
write_text_1( [H|T] ) :-
    !,
    write_text_1( T ),
    write_atom_as_quoted_atom( H ), write( '~<>' ), nl.
 
write_text_1( [] ) :- !.
 
 
/*  write_atom_as_quoted_atom( Atom+ ):
        Write Atom in such a form that if read back by 'read' it will
        produce the same atom. This means that we must escape funny
        characters. In Poplog, this entails preceding them by a
        backslash: your system may be different.
*/
write_atom_as_quoted_atom( Atom ) :-
    name( Atom, List ),
    write( '\'' ),
    write_list_as_quoted_atom( List ),
    write( '\'' ).
 
 
/*  write_list_as_quoted_atom( Chars+ ):
        As write_list_as_quoted_atom, but Chars is the characters making
        up the atom's name, and the enclosing quotes have been dealt
        with.
*/
write_list_as_quoted_atom( [] ).
 
write_list_as_quoted_atom( [H|T] ) :-
    write_char_inside_quoted_atom( H ),
    write_list_as_quoted_atom( T ).
 
 
/*  write_char_inside_quoted_atom( C+ ):
        C is a character. Write it, escaping if necessary.
*/
write_char_inside_quoted_atom( H ) :-
    is_single_quote_char( H ),
    !,
    write( '\\\'' ).
 
write_char_inside_quoted_atom( H ) :-
    is_backslash_char( H ),
    !,
    write( '\\\\' ).
 
write_char_inside_quoted_atom( H ) :-
    put( H ).
 
 
/*  is_tag_line( Line+, Tag- ):
        True if Line is a list of characters starting with tilde, and
        Tag is the rest of the line as an atom.
*/
is_tag_line( [ First | Rest ], Tag ) :-
    is_twiddle_char( First ),
    name( Tag, Rest ).
 
 
/*
Useful character codes.
-----------------------
 
Change this if your system's not ASCII.
*/
 
is_twiddle_char( 126 ).     /* ~ */
 
is_percent_char( 37 ).      /* % */
 
is_single_quote_char( 39 ). /* ' */
 
is_backslash_char( 92 ).    /* \ */
