/*  SCRIPT.PL  */
 
 
:- module script.
 
 
:- public next/1,
          again/0,
          script/2,
          section/2.
 
 
/*
SPECIFICATION
-------------
 
Scripts for the Tutor start life as text files:
    ~1
                         LOGIC PROGRAMMING LESSON 1
 
    Welcome to the Logic Programming Tutor. We're going to use it to
    teach you logic programming and Prolog. Because full Prolog looks
    ...
    the full-stop, and that you hit RETURN. Otherwise, the Tutor won't
    do anything.
 
    ~2
    You have now given your first command to the Tutor. COMMANDS are
    ...
(I've shown this indented four places to set it off from surrounding
text).
 
In their files, scripts are divided into sections, each of which begins
with a section number preceded by a tilde: ~<integer>. The numbers
should start at 1, go upwards consecutively, and be written at the
beginning of an otherwise empty line.
 
Abstractly, a script is a sequence of sections. There are four commands
operating on scripts:
    script FileName
    next
    again
    section N
 
For the purpose of this specification, let us model a script as a
quadruple
    < File, Section, Text, End >
where File names the file it lives in, Section is the section currently
being "considered", Text is its text, and End is the number of the final
section (>=1). Section must always lie between 1 and End. Assume further
that at any time, this module stores one such quadruple as the "current
script". (The Tutor _always_ has to have a script open: if it can't, it
aborts with a fatal error).
 
We can then implement the four commands as
    script File:
        if the file exists and can be opened, then
            script_name := < File, 1, T, E >
                where T and E are the text from File and the number
                of the final section
        else
            leave current script unchanged and return an error
 
    next:
        script_name is < File, S, T, E >
        if S = E then
            we are on the final section, and can't write the next
            one; leave script_name alone and return an error
        else
            write T[S]
            script_name := < File, S+1, T, E >
 
    again:
        script_name is < File, S, T, E >
        write T[S]
 
    section N:
        script_name is < File, S, T, E >
        if N > E then
            the section doesn't exist; leave script_name alone
            and return an error
        else
            script_name := < File, N, T, E >
            write T[N]
 
This module exports predicates which implement these specifications.
Each has a Status argument indicates whether a predicate managed to do
as requested. Errors may be caused by invalid arguments (section number
outside the script), or by external circumstances (script file missing).
The predicates do not themselves report the errors: they leave that to
the calling code in COMMANDS.PL.
 
    TYPE
    FindScriptStatus = { ok, file_not_found }
    ScriptStatus = { no_more_sections } u OpenInputStatus
    SectionStatus = { ok, no_section_found }
    NextStatus = { ok, no_section_found, eof_script }
    ScriptName
    FullFileName
 
The predicates are as below. They don't require the Tutor's editor, so
you can use them independently.
 
Portability note: you will have to change the clauses for
    script_extension_is/1
    script_directories_are/1
below to conform to where you keep the scripts.
 
 
PUBLIC again:
Display again the last-writeed section of the current script.
 
PUBLIC next( Status:NextStatus- ):
Display the next section of the current script.
 
PUBLIC script( ScriptName+, Status:ScriptStatus- ):
Attempts to switch to the script denoted by ScriptName (an atom such as
'lesson1').
 
PUBLIC section( N:Int+, Status:SectionStatus- ):
Display section number N of the current script.
*/
 
 
/*
IMPLEMENTATION
--------------
 
In the original version of the Tutor, this module read the script file
directly. I've since changed things. There's now a separate program,
FILE_TO_TERMS, which converts a text file to a file of Prolog clauses.
Each script is stored as a set of assertions
    script_text( S, T )
where S is a section number, and T its text, in a form outputtable by
"output".
 
Given this, the implementation is easy. We open a script by reconsulting
its file: this will automatically discard text from the old script. If
your Prolog stores modules in compiled form, as ESL Prolog-2 does, you
can make this even quicker by pre-compiling all your scripts and opening
the module when reading. If you have direct access files, you could
store the text in these instead.
 
This module maintains two assertions:
    script_name( FileName, FullFileName )
    script_position( Section )
There is always one and only one clause for each. script_name stores the
undecorated name of the script as passed to the "script" command (no
extension, directory, etc.) and the full file name. script_position
stores the current section. "write_current_section" writes this.
 
 
Storage.
--------
 
If you would rather not store the clauses in main memory, there are
several alternatives:
 
(1) Use the clause files as they are, but read clauses as required. This
will require you to note your position within the file, so that if
(e.g.) you have just read section 20, and are asked to go back to
section 10, you can re-open the file and read forward.
 
If your Prolog implements modules that store the executable code on disc
(as in Prolog-2 virtual modules), load the clause files as disc modules.
 
Store the text in direct access files, if you have them.
 
Write a module that reads from the original raw text. It would be
interesting to develop it systematically by partially evaluating
"scripts" with respect to "file_to_terms": I do not know how easy that
would be.
 
Finally, in conjunction with this and the discussion of hypertext below,
it's worth suggesting that if you are going to handle a lot of large
chunks of text (the Tutor doesn't, but extensions might), it would be a
good idea to encapsulate these inside a library module. Such a module
might provide operations such as
 
(A) write_chunk(C): Write a chunk to the COS.
 
(B) append_chunks(C1,C2,C3): Append C1 to C2 giving C3.
 
(C) cos_to_chunk(Goal,C): Obey Goal, capturing all its output as a
character stream and saving it in a new chunk C. (Compare cos_to_chars).
 
Some hints on how one might do this are given by the DEC-10 library
module PUTSTR. Unfortunately, it was never finished. I suspect that the
Quintus Prolog module big_text is based on it.
 
The idea in PUTSTR is that chunks are represented either as atoms, or
as structures s(SourceFile,StartOfChunk,LengthOfChunk). The code follows:
    %   File   : PUTSTR.PL
    %   Author :
    %   Updated: 24 February 1984
    %   Purpose: Write out a string which may be held in another file.
 
    /*  This file forms part of a package for taking large blocks of
        text (such as help information) out of Prolog programs into
        ordinary text files.  This is the bit that prints a string
        regardless of where it lives.  The other part was going to be
        written in Pascal.  It hasn't been written at all yet.
    */
 
    :- public
        putstr/1.
 
    :- mode
        putstr(+),
        copy_chars(+),
        skip_chars(+).
 
    putstr(s(File,Start,Length)) :-
        seeing(Old),
        see(File),
        skip_chars(Start),
        copy_chars(Length),
        seen,
        see(Old).
    putstr(Atom) :-
        atomic(Atom),
        write(Atom).
 
    skip_chars(0) :- !.
    skip_chars(N) :-
        get0(_),
        M is N-1,
        skip_chars(M).
 
    copy_chars(0) :- !.
    copy_chars(N) :-
        get0(C),
        put(C),
        M is N-1,
        copy_chars(M).
 
 
An approach to hypertext.
-------------------------
 
Please look at COMMAND_HELP.PL. command_help adds some clauses for
output. Some of these, such as the one defining q_, are merely
shorthands. The clause for cex_ is more interesting:
    :- add_user_output( command_help_output ).
 
    command_help_output( cex_(Command) ) :-
        !,
        command_example( Command, Text ),
        output( Text ).
 
This modifies output so that the call (for example)
    output( qtokens_(Rem)...'is not the name of a printer.'<>cex_(C) )
would end by writing out some explanatory text for C, assumed to be a
command name. The argument to output could be viewed as a very simple
plan (in the A.I. sense) for writing help messages, with cex_(C)
representing the action ``Cross reference the explanation concerning
command C''. If taken to its logical conclusion, such an approach could
lead to a system that treats scripts and help messages on the same
footing, with a natural representation of links from one to another. The
word ``hypertext'' has been coined for systems that link chunks of text
in a network in this way, allowing users to navigate between chunks.
 
Scripts are also stored as clauses, but no attempt is made to integrate
them with the help messages. This is because of the way in which I
developed the Tutor. If starting again, it would be sensible to have the
same representation for all the kinds of explanatory text, so that a
command-error message could point back to the section of script that
explains how to use the command. Extra script-navigation commands could
be added: perhaps:
 
first_use T: Display the section where topic T was first introduced.
 
example T:   Display brief examples of T's use.
 
run T:       If T is something like a command, that can be executed,
             find a safe instance of it, and obey this, demonstrating it
             in action.
*/
 
 
:- needs
    bug / 1,
    find_file / 5,
    open_and_reconsult / 2,
    output / 1.
 
 
:- dynamic
    script_position / 1,
    script_name / 2,
    script_text / 2.
 
 
again :-
    write_current_section.
 
 
next( Status ) :-
    script_position( Position ),
    Next is Position + 1,
    (
        script_text( Next, _ )
    ->
        Status = ok,
        note_script_position( Next ),
        write_current_section
    ;
        Status = no_section_found
    ).
 
 
script( ScriptName, Status ) :-
    find_script( ScriptName, ScriptsFullName, Status1 ),
    (
        Status1 = ok
    ->
        read_script( ScriptName, ScriptsFullName, Status ),
        write_current_section
    ;
        Status = Status1
    ).
 
 
section( N, Status ) :-
    (
        script_text( N, _ )
    ->
        note_script_position( N ),
        write_current_section,
        Status = ok
    ;
        Status = no_section_found
    ).
 
 
/*  OPENING AND READING THE SCRIPT FILE  */
 
 
/*  find_script( ScriptName+, FullFileName-, FindScriptStatus- ):
        Translates the script name as seen by the user into a full
        filename, with directory and extension added.
 
        At Oxford, all scripts have the extension .SCR. (e.g.
        LESSON1.SCR). These are Prolog clause files constructed from the
        files LESSON1. etc. To find the files, we look first in the
        current directory (in case the student has been given her own
        private version), and then in the library of standard scripts,
        whose directory is identified by the VMS logical name
        LOGIC$LIB:.
*/
find_script( ScriptFile, Fullname, Status ) :-
    script_extension_is( Ext ),
    script_directories_are( Dirs ),
    find_file( ScriptFile, Dirs, Ext, Fullname, Status ).
 
 
/*      The extension for all script clause files.
*/
script_extension_is( '.SCR' ).
 
 
/*      The list of directories to search, in order. '' denotes
        the current directory.
*/
script_directories_are( [ '', '[popx.book]', 'logic$lib:' ] ).  
 
 
/*  read_script( ScriptName:FileName+,
                 ScriptFile:FullFileName+,
                 Status:OpenInputStatus u {no_section_found}-
               ):
        ScriptFile+ is the full name of a script file, complete with
        directory, extension and any other additions. read_script tries
        to open it for reading.
 
        On return, if reading went OK, the current position will be 1,
        the current script will be ScriptFile, and Status will be
        'ok'. Otherwise Status will contain an error status and the
        name and position will be unchanged.       
*/
read_script( ScriptName, ScriptFile, Status ) :-
    open_and_reconsult( ScriptFile, Status ),
    (
        Status = ok
    ->
        note_script_name( ScriptName, ScriptFile ),
        note_script_position( 1 )
    ;
        /*  We are as we were, in the old script.  */
        true
    ).
 
 
/*  RECORDING SECTION NUMBER AND SCRIPT NAME  */
 
 
/*  note_script_position( N:Int+ ):
        Set the position to N.
*/
note_script_position( N ) :-
    retractall( script_position(_) ),
    asserta( script_position( N ) ).
 
 
/*  note_script_name( Name:FileName+, FullName:FullFileName+ ):
        Set the current script name.
*/
note_script_name( Name, FullName ) :-
    retractall( script_name(_,_) ),
    asserta( script_name( Name, FullName ) ).
 
 
/*  WRITING THE SCRIPT  */
 
 
/*  write_current_section:
        Display the current section, headed by its number, on the
        screen. Should never fail, since the position is guaranteed to
        be in range.
*/
write_current_section :-
    script_position( P ),
    output( '*** Section number'...P...' ***'~~ ),
    script_text( P, Text ),
    output( Text ).
 
write_current_section :-
    bug( 'write_current_section: unexpected failure' ).
 
 
:- endmodule.
