/*  EDITOR.PL  */
 
 
:- module editor.
 
 
:- public no_facts_stored/0,
          prepend_fact/2,
          append_fact/2,
          insert_or_replace_fact/3,
          delete_fact/3,
          delete_final_fact/2,
          delete_fact_strictly_matching/2,
          delete_fact_matching/2,
          for_all_facts/3,
          for_in_range/4,
          number_to_fact/2,
          range_to_facts/2,
          final_fact/2,
          sync_database/0,
          initialise_editor/0,
 
          editor_clause/4,
 
          no_question_stored/0,
          store_question/1,
          the_question/1,
 
          range_is_all/1,
          range_is_lu/3,
          range_is_pred/2,
          range_is_empty/1,
          range_is_question/1,
          range_type/2,
 
          is_fact/1,
          is_good_fact/1,
          fact_vs_clause_and_vars/3,
          fact_vs_clause/2,
          fact_vs_vars/2,
          fact_vs_clause_pred_arity/4,
          fact_vs_pred_arity/3,
          fact_vs_text/2,
 
          is_question/1,
          is_good_question/1,
          question_vs_goal_and_vars/3,
          question_vs_text/2,
 
          standardise_clause/2.
 
 
/*
SPECIFICATION
-------------
 
The idea is that the editor maintains a sequence of facts (the
textbase), making them accessible by their number. Mathematically, it
implements a partial function from the integers to facts. To use this
module, you don't need to know how such dictionaries are implemented
(actually, it's as a binary tree), which is the business of module
arrays, or how the editor represents the textbase (which is the
business of this module).
 
This module exports predicates for
1)  Putting facts into the textbase - appending to the end, prepending
    to the beginning, or inserting/replacing a fact at a specified
    number.
 
2)  Deleting facts from the textbase - deleting the fact at the end,
    a fact with a specified number, or a fact that matches (to various
    degrees of strictness) another fact.
 
3)  Making a list of facts within a specified range - all facts defining
    a specified predicate, all facts between two numbers, or just all
    the facts there are.
 
4)  Applying a goal to a set of facts - either to all the facts there
    are, or to all those in a given range.
 
5)  Accessing the components of fact and question structures and of ranges.        
 
6)  Initialising the editor for startup.
 
7)  Last, but very important: ensuring that the Prolog database is in
    sync with the textbase. I.e. that all those facts that are
    syntactically valid have been asserted in the correct position,
    and that there are no other facts for the same predicates.
 
 
There is a related, but much smaller, set of predicates for handling
questions. The Tutor stores the previous question asked so it can be
edited and re-asked, and so it can be re-traced using the predicates in
WHY.PL.
 
 
PUBLIC no_facts_stored:
True if the textbase is empty.
 
 
PUBLIC append_fact( NewFact+, NewFactNumber- ):
Append NewFact to the textbase. NewFactNumber will become its number,
equal to (the largest number so far) + 10.
 
The point of generating a new number automatically is that it's useful
when allocating numbers to facts that just been typed in on their own
(as when we give
    john loves mary.
a new number). It's also useful from DB.PL, to simulate 'assert'.
 
 
PUBLIC prepend_fact( NewFact+, NewFactNumber- ):
Prepend NewFact to the textbase. NewFactNumber will become its
number, equal to (the lowest number so far) - 10. This is called from
DB.PL, to simulate 'asserta'.
 
 
PUBLIC insert_or_replace_fact( FactNumber+, F+, ChangeType? ):
Replace or insert the fact that is at FactNumber. Unify ChangeType with
'inserted' or 'replaced(OldF)' accordingly.
 
ChangeType can be instantiated on call. If it is, this controls how the
predicate is to act if it finds a fact with the same number. If it does,
and ChangeType is 'inserted', then insert_or_replace_fact fails,
otherwise inserts it. If ChangeType is 'replaced(OldF)',
insert_or_replace_fact will fail should there _not_ be such a fact, and
otherwise instantiate OldF to the old fact.
 
 
PUBLIC delete_fact( FactNumber+, F-, Status? ):
Remove fact number FactNumber from textbase, unifying F with it.
Status = ok if the fact is there, fact_not_found otherwise.
 
 
PUBLIC delete_final_fact( FactNumber-, Fact- ):
Remove final fact from facts textbase, failing if there isn't one.
Unify Fact with it and FactNumber with its number.
 
 
PUBLIC delete_fact_strictly_matching( FactNumber-, Clause+ ):
Remove from facts textbase, the fact whose clause C strictly matches
Clause, in the sense that
    numbervars( Clause, 1, _ ), numbervars( C, 1, _ )
would produce identical ground terms. Unify FactNumber with its number.
Fail if there's no such fact.
 
 
PUBLIC delete_fact_matching( FactNumber-, Clause+ ):
Remove from facts textbase, the fact whose clause matches (by
unification) Clause, and unify FactNumber with its number. Fail if
there's no such fact.
 
 
PUBLIC number_to_fact( N+, Fact- ):
Unify Fact with the fact numbered N, and fail if there isn't one.
 
 
PUBLIC range_to_facts( Range+, List- ):
Make List a list of all the facts in Range, in number order.
 
 
PUBLIC final_fact( FactNumber-, Fact- ):
Unify FactNumber and Fact with the number of the final fact and the
fact itself, failing if there are no facts.
 
 
PUBLIC for_in_range( FactNumberVar-, FactVar-, Range+, Goal+ ):
Apply Goal to each fact within Range in the textbase. If Goal contains
variables FactNumberVar and FactVar, they should be uninstantiated:
for_in_range will bind them to the line number and the fact
respectively.
 
 
PUBLIC for_all_facts( FactNumberVar-, FactVar-, Goal+ ):
Apply Goal to each fact in the textbase. If Goal contains variables
FactNumberVar and FactVar, they should be uninstantiated: for_all_facts
will bind them to the line number and the fact respectively.
 
 
PUBLIC sync_database:
Synchronise the Prolog database so that it contains all the (valid)
facts in the textbase. The database is NOT guaranteed to be in sync with
the textbase unless this is done; the synchronisation may disappear when
the textbase is next updated.
 
 
PUBLIC initialise_editor:
Initialise the textbase to be empty.
 
 
PUBLIC editor_clause( N-, Head?, Body?, Vars-  ):
As the standard predicate clause/2, but takes its clauses from the
textbase and not the database. N will be unified with the fact number
and Vars with the variable-name list.
 
 
PUBLIC no_question_stored:
True if there is no question stored.
 
 
PUBLIC store_question( Q+ ):
Store Q as the current question, replacing any other.
 
 
PUBLIC the_question( Q- ):
Unify Q with the current question.
 
 
PUBLIC range_is_all( Range? ):
True if Range represents the word 'all'.
 
 
PUBLIC range_is_lu( Range?, Lub?, Upb? ):
True if Range represents the range Lub--Upb.
 
 
PUBLIC range_is_pred( Range?, Pred? ):
True if Range represents the predicate name Pred.
 
 
PUBLIC range_is_question( Range+ ):
True if Range represents the question range 'q'.
 
 
PUBLIC range_type( Range+, Type? ):
Unifies Type with the type ('facts' or 'question') of Range.
 
 
PUBLIC is_fact( Fact+ ):
True if Fact is a fact.
 
 
PUBLIC is_good_fact( Fact+ ):
True if Fact is a correct (no errors) fact.
 
 
PUBLIC fact_vs_clause_and_vars( Fact?, Clause?, Vars? ):
True if Fact is a correct fact with clause Clause and variable name list
Vars.
 
 
PUBLIC fact_vs_clause( Fact?, Clause? ):
True if Fact is a correct fact with clause Clause.
 
 
PUBLIC fact_vs_vars( Fact?, Vars? ):
True if Fact is a correct fact with variable name list Vars.
 
 
PUBLIC fact_vs_clause_pred_arity( Fact?, Clause?, P?, A? ):
True if Fact is a correct fact with clause Clause, head predicate/arity
P and A.
 
 
PUBLIC fact_vs_pred_arity( Fact?, P?, A? ):
True if Fact is a correct fact with head predicate and arity P and A.
 
 
PUBLIC fact_vs_text( Fact?, Text? ):
True if Fact is an erroneous fact with token list Text.
 
 
PUBLIC is_question( Question+ ):
True if Question is a question structure.
 
 
PUBLIC is_good_question( Question+ ):
True if Question is a correct (no errors) question.
 
 
PUBLIC question_vs_goal_and_vars( Question?, Goals?, Vars? ):
True if Question has goal Goals and variable name list Vars.
 
 
PUBLIC question_vs_text( Question?, Text? ):
True if Question is an erroneous question with token list Text.
 
 
PUBLIC standardise_clause( Clause0+, Clause1- ):
Clause0 is a clause as entered by the user, via add/adda or by typing
a fact. Clause1 will become a standardised form for storage in the
textbase. Always call this to process a user's clause before storing it.
*/
 
 
/*
IMPLEMENTATION
--------------
 
The editor maintains an assertion
   textbase( T )
representing the current textbase. This is updated whenever a fact is
added or deleted. The textbase is implemented by module 'arrays'.
 
Facts are represented as structures with functor 'fact'. If the fact is
syntactically valid, it has two arguments:
    fact( Clause, Vars )
Clause being its translation into Prolog, and Vars the variable name
textbase. If the fact is not syntactically valid, it has one argument:
    fact( Tokens )
where Tokens is the original list of tokens it came from (without the
terminal full stop).
 
Clauses are stored in the form H:-T, but H:-true is reduced to H. It's
important to be consistent about this, otherwise some fact matches will
not work.
 
The editor has to ensure that, at certain times, the database is
in sync with the textbase. The simplest way to do this would be,
every time the student enters a new fact, to assert it there and then. This
gives problems. Suppose we have facts
    10 john loves mary.
    20 fred loves miranda.
and the student types
    15 bert loves madonna.
We need to maintain ordering: it's an invariant of the Tutor that
clauses within any predicate/arity are in the same order as their
numbers. So fact 15 must be asserted between the clauses for 10 and 20.
The only way to do this (in most Prologs) is this: Every time a fact is
asserted, we would retract all facts for the same predicate/arity, and
then re-assert them _and the new fact_, making sure that the new fact
goes in the correct relative position. Thus, for the facts above, we'd
first do
    retractall( loves(_,_) )
and then
    assert( loves(john,mary) ).
    assert( loves(bert,madonna) ).
    assert( loves(fred,miranda) ).
 
In general, we would add the fact to the editor's textbase, then retract
all the clauses from the database, and then re-assert what's in the
textbase. The textbase is sorted by fact number, so this guarantees that
the facts will be re-asserted in the correct sequence.
 
Such a process is not cheap, so we want to avoid it if possible. It is
in fact not necessary to update the database every time a fact is added
or deleted, because the only time we need it is when the student asks a
question. (Commands like 'show' rely on the textbase, not the database).
So we export sync_database. The user calls this when the database is to
be in sync with the textbase; at other times, it need not be.
 
To represent the state of synchronisation, there is another assertion:
    change_set( ChangeSet )
ChangeSet is a set of Predicate/Arity pairs. It is represented as an
array: Predicate/Arity is the key, and the entry is 1 (it could be
anything). It is an invariant that
    (desired state of database) =
     (current state of database) -
     (all clauses whose predicate/arities are in ChangeSet) +
     (all clauses in the textbase whose predicate/arities are in ChangeSet).
 
There is a derivation of this representation in ``The Logic Programming
Tutor''.
 
If the student types a fact to be appended:
    alf loves maria.
then we can update the database efficiently, because we have 'assert'
available. We don't have to retract and re-assert all the facts, we just
assert the new one, and this makes sure it goes after all the others. Of
course, before doing this, we have to make sure the database is in sync.
 
 
Question structures are also defined here. Syntactically valid questions
are represented as
    question( Goal, Vars )
and erroneous ones as
    question( Tokens )
as for facts.
 
The previous question is stored as the assertion
    question( Q ).
There are a few predicates for accessing and replacing it.
 
 
Finally, some miscellaneous points.
 
1) Strict matching of clauses.
The need for this arises from one form of the 'delete' command, the one
that takes a fact as argument:
    delete john loves X.
If 'delete' were an imitation of Prolog 'retract', then this would
delete any unconditional 'loves' fact with a first argument 'john'. The
second argument could be anything that matched a variable - i.e.
anything.
 
However, that's probably not what a naive student intends. She probably
wants to delete the fact which is textually identical, and would be
surprised if 'delete john loves X' were also to delete 'john loves
mary'. (Remember that we introduce the delete command right near the
beginning of the course, before we've taught about matching, retract,
etc.). So I've defined 'delete' in a way which - hope - corresponds with
a new student's intentions, and said that
    delete <fact>
deletes any fact that's textually identical with its argument.
 
The obvious way to implement textual identity is to store the text of
every fact, good or bad (we already store it for bad facts). That's
not necessary. Consider the terms
    a(A,B,C)
    a(A,B,A)
Suppose that we replace each variable by a unique integer, going through
each term from the left, and starting with 1. We then get
    a(1,2,3)
    a(1,2,1)
and these don't unify. However, a(A,B,C) and a(A,B,C) after replacement
both become a(1,2,3), and these do unify!
 
We can implement delete's matching in this way. Given an argument of
delete, and a clause against which to compare it, we replace the
variables in both, and then unify them. To do this, we use
'strict_match_clause'. It does its variable replacements by calling
'numbervars'. This may be built in to your system - if not, see
NUMBERVARS.PL, which defines it for you. Note that numbervars (mine, at
least) replaces variables by '$var'(N) rather than N. This avoids
confusion between replaced variables and integers. Note also that this
scheme doesn't quite enforce textual identity. The command
    delete X loves X
would also delete Y loves Y, Z loves Z, etc. However, since these will
always mean the same (and hence the student should only have one clause
anyway), I've chosen not to worry.
*/
 
 
:- needs
    array_delete / 4,
    array_filtered_to_list / 5,
    array_first / 3,
    array_insert / 4,
    array_insert_or_replace / 5,
    array_is_empty / 1,
    array_last / 3,
    array_member / 3,
    array_ranged_to_list / 4,
    array_to_list / 2,
    array_value / 3,
    clause_vs_head_tail / 3,
    clause_vs_pred_arity / 3,
    for_in_array / 4,
    for_ranged_in_array / 6,
    forall / 2,
    new_array / 1,
    non_binding_call / 1,
    once / 1.
 
 
dynamic
    textbase/1,
    question/1,
    change_set / 1.
 
 
/*  Lowest level fact predicates.  */
 
 
no_facts_stored :-
    textbase( FactsSoFar ),
    array_is_empty( FactsSoFar ).
 
 
initialise_editor :-
    (
        change_set( ChangeSet )
    ->
        empty_database( ChangeSet ),
        retractall( change_set(_) )
    ;
        true
    ),
    retractall( textbase(_) ),
    new_array( T ),
    assert( textbase(T) ),
    new_array( NewChangeSet ),
    assert( change_set(NewChangeSet) ).
 
 
/*  encapsulate_state_change( T0,ChangeSet0, Goal, T,ChangeSet ):
        Goal will normally contain arguments sharing with the other
        arguments of encapsulate_state_change.
        This predicate binds T0 and ChangeSet0 to the current
        textbase and change set; calls Goal; and then replaces
        them by T and ChangeSet.
        It is assumed that if Goal fails, the textbase and change set
        should remain unchanged.
*/
encapsulate_state_change( T0,ChangeSet0, Goal, T,ChangeSet ) :-
    textbase( T0 ),
    change_set( ChangeSet0 ),
    Goal,
    retractall( textbase(_) ),
    assert( textbase(T) ),
    (
        ChangeSet0 \= ChangeSet
    ->
        retractall( change_set(_) ),
        assert( change_set(ChangeSet) )
    ;
        true
    ).
 
 
prepend_fact( FS, N ) :-
    encapsulate_state_change(
        T0, ChangeSet0,
        prepend_fact( FS, T0,T, ChangeSet0,ChangeSet, N ),
        T, ChangeSet
    ).
 
 
/*  prepend_fact( FS+, T0+,T-, ChangeSet0+,ChangeSet-, N- ):
        T is the result of prepending fact structure FS to T0; FS
        will be allocated number N.
        ChangeSet is the new change set, if the current one is ChangeSet0.
*/
prepend_fact( FS, T0,T, ChangeSet0,ChangeSet, N ) :-
    new_lowest_number( T0, N ),
    array_insert( T0, N, FS, T ),
    (
        is_good_fact( FS )
    ->
        fact_vs_clause( FS, Clause ),
        sync_database( ChangeSet0, T0, ChangeSet ),                    
        asserta( Clause )
    ;
        ChangeSet = ChangeSet0
    ).
 
 
append_fact( FS, N ) :-
    encapsulate_state_change(
        T0, ChangeSet0,
        append_fact( FS, T0,T, ChangeSet0,ChangeSet, N ),
        T, ChangeSet
    ).
 
 
/*  append_fact( FS+, T0+,T-, ChangeSet0+,ChangeSet-, N- ):
        T is the result of appending fact structure FS to T0; FS
        will be allocated number N.
        ChangeSet is the new change set, if the current one is ChangeSet0.
*/
append_fact( FS, T0,T, ChangeSet0,ChangeSet, N ) :-
    new_highest_number( T0, N ),
    array_insert( T0, N, FS, T ),
    (
        is_good_fact( FS )
    ->
        fact_vs_clause( FS, Clause ),
        sync_database( ChangeSet0, T0, ChangeSet ),
        assert( Clause )
    ;
        ChangeSet = ChangeSet0
    ).
 
 
insert_or_replace_fact( N,FS, ChangeType ) :-
    encapsulate_state_change(
        T0, ChangeSet0,
        insert_or_replace_fact( N,FS, T0,T, ChangeSet0,ChangeSet, ChangeType ),
        T, ChangeSet
    ).
 
 
/*  insert_or_replace_fact( N+,FS+, T0+,T-, ChangeSet0+,ChangeSet-,
                            ChangeType?
                          ):
        T is the result of inserting or replacing fact structure FS to
        T0 at N. ChangeSet is the new change set, if the current one is
        ChangeSet0. ChangeType indicates whether there was a fact at N
        already (see specification part).
*/
insert_or_replace_fact( N,FS, T0,T, ChangeSet0,ChangeSet, ChangeType ) :-
    array_insert_or_replace( T0, N, FS, T, ChangeType ),
    (
        is_good_fact( FS )
    ->
        fact_vs_clause( FS, Clause ),
        add_promise( ChangeSet0, Clause, ChangeSet1 )
    ;
        ChangeSet1 = ChangeSet0
    ),
    (
        ChangeType = replaced(OldFact),
        is_good_fact( OldFact )
    ->
        fact_vs_clause( OldFact, Clause_ ),
        add_promise( ChangeSet1, Clause_, ChangeSet )
    ;
        ChangeSet = ChangeSet1
    ).
    /*  When updating the change set, we must take account of two
        changes:
        1) We have added a fact: if it's good, note it in the change set.
        2) We may have deleted a fact, if there was one at N already. If
           it was good, note _it_ in the change set (this will ensure
           it gets retracted).
    */
 
 
delete_fact( N,FS, Status ) :-
    encapsulate_state_change(
        T0, ChangeSet0,
        delete_fact( N,FS, T0,T, ChangeSet0,ChangeSet, Status ),
        T, ChangeSet
    ).
 
 
/*  delete_fact( N+,FS-, T0+,T-, ChangeSet0+,ChangeSet-, Status? ) :-
        T is the result of deleting the fact at N in T0. ChangeSet is
        the new change set, if the current one is ChangeSet0. Status is
        'ok' if there was a fact there, no_fact_found otherwise.
        already (see specification part).
*/
delete_fact( N,FS, T0,T, ChangeSet0,ChangeSet, Status ) :-
    array_delete( T0, N, FS, T ),
    !,
    (
        is_good_fact( FS )
    ->
        fact_vs_clause( FS, Clause ),
        add_promise( ChangeSet0, Clause, ChangeSet )
    ;
        ChangeSet = ChangeSet0
    ),
    Status = ok.
 
delete_fact( N,FS, T,T, ChangeSet,ChangeSet, fact_not_found ).
 
 
/*  add_promise( ChangeSet0+, Clause+, ChangeSet- ):
        ChangeSet is ChangeSet0 union {P/A}, where Clause has head
        predicate and arity P/A.
*/
add_promise( ChangeSet0, Clause, ChangeSet ) :-
    clause_vs_pred_arity( Clause, P, A ),
    array_insert_or_replace( ChangeSet0, P/A, 1, ChangeSet, _ ).
 
 
sync_database :-
    change_set( ChangeSet0 ),
    textbase( T0 ),
    sync_database( ChangeSet0, T0, ChangeSet ),
    retractall( change_set(_) ),
    assert( change_set(ChangeSet) ).
 
 
sync_database( ChangeSet, T, NewChangeSet ) :-
    empty_database( ChangeSet ),
    assert_textbase( T, ChangeSet ),
    new_array( NewChangeSet ).
 
 
/*  empty_database( ChangeSet+ ):
        Retracts from the database all clauses with head
        predicate/arities in ChangeSet.
*/
empty_database( ChangeSet ) :-
    forall(
            array_member( P/A, _, ChangeSet )
          ,
            abolish( P, A )
          ).
 
 
/*  assert_textbase( T+, ChangeSet+ ):
        Asserts into the database all correct facts from T whose head
        predicate/arities are in ChangeSet.
*/
assert_textbase( T, ChangeSet ) :-
    forall(
            ( array_member( N, FS, T ),
              fact_vs_clause_pred_arity( FS, Clause, P, A ),
              array_member( P/A, _, ChangeSet )
            )
          ,
            assert( Clause )
          ).
 
 
/*  new_lowest_number( T+, N- ):
        N is the number that would be allocated if a fact were prepended
        to T.
*/
new_lowest_number( T, N ) :-
    array_first( T, N0, _ ),
    !,
    N is N0 - 10.
 
new_lowest_number( T, 10 ).
 
 
/*  new_highest_number( T+, N- ):
        N is the number that would be allocated if a fact were appended
        to T.
*/
new_highest_number( T, N ) :-
    array_last( T, N0, _ ),
    !,
    N is N0 + 10.
 
new_highest_number( T, 10 ).
 
 
/*  Higher level predicates.  */
 
 
delete_final_fact( FactNumber, Fact ) :-
    textbase( FactsSoFar ),
    array_last( FactsSoFar, FactNumber, Fact ),
    delete_fact( FactNumber, _, ok ).
 
 
delete_fact_strictly_matching( FactNumber, Clause ) :-
    textbase( FactsSoFar ),
    array_member( FactNumber, Fact, FactsSoFar ),
    is_good_fact(Fact),
    fact_vs_clause(Fact,C),
    strict_match_clause(Clause,C),
    /*  array_member is a generator. This will visit FactsSoFar in
        ascending order until a strictly-matching fact is found.
    */
    !,
    delete_fact( FactNumber, _, ok ).
 
 
delete_fact_matching( FactNumber, Clause ) :-
    textbase( FactsSoFar ),
    array_member( FactNumber, Fact, FactsSoFar ),
    fact_vs_clause(Fact,Clause),
    /*  Visits FactsSoFar in ascending order until a fact with
        matching clause is found.
    */
    !,
    delete_fact( FactNumber, _, ok ).
 
 
/*  Looping predicates.  */
 
 
for_all_facts( N, Fact, Goal ) :-
    textbase( FactsSoFar ),
    for_in_array( N, Fact, FactsSoFar, Goal ).
 
 
for_in_range( N, Fact, Range, Goal ) :-
    range_is_all( Range ),
    !,
    for_all_facts( N, Fact, Goal ).
 
for_in_range( N, Fact, Range, Goal ) :-
    range_is_lu( Range, Lub, Upb ),
    !,
    textbase( FactsSoFar ),
    for_ranged_in_array( N, Fact, Lub, Upb, FactsSoFar, Goal ).
 
for_in_range( N, Fact, Range, Goal ) :-
    range_is_pred( Range, Pred ),
    for_in_pred( N, Fact, Pred, Goal ).
 
 
for_in_pred( N, Fact, Pred, Goal ) :-
    textbase( FactsSoFar ),
    array_member( N, Fact, FactsSoFar ),
    fact_vs_pred_arity( Fact, Pred, _ ),
    once( Goal ),
    fail.
for_in_pred( _, _, _, _ ).
 
 
number_to_fact( N, Fact ) :-
    textbase( FactsSoFar ),
    array_value( FactsSoFar, N, Fact ).
 
 
final_fact( FactNumber, Fact ) :-
    textbase( FactsSoFar ),
    array_last( FactsSoFar, FactNumber, Fact ).
 
 
range_to_facts( Range, List ) :-
    range_is_all( Range ),
    !,
    textbase( FactsSoFar ),
    array_to_list( FactsSoFar, List ).
 
range_to_facts( Range, List ) :-
    range_is_lu( Range, Lub, Upb ),
    !,
    textbase( FactsSoFar ),
    array_ranged_to_list( FactsSoFar, Lub, Upb, List ).
 
range_to_facts( Range, List ) :-
    range_is_pred( Range, Pred ),
    textbase( FactsSoFar ),
    array_filtered_to_list( FactsSoFar,
                          _, Fact,
                          fact_vs_pred_arity(Fact,Pred,_),
                          List
                        ).
 
 
range_is_empty( Range ) :-
    range_to_facts( Range, L ),
    L = [].
 
 
/*  Clause matching.  */
 
 
/*  strict_match_clause( F+, G+ ):
      Succeed if clauses F and G are identical
*/
strict_match_clause( F, G ) :-
 
    /*  First, a (hopefully) quick check by unification. If this
        fails, F and G are certainly different. If it succeeds,
        they _may_ be the same.
    */
    non_binding_call(F=G),
 
    /*  If they unified, a more stringent test. They have the same
        pattern of variables only if, when 'numbervar'ed, they
        come out identical.
    */
    non_binding_call( (numbervars( F, 1, _ ), numbervars( G, 1, _ ), F=G ) ).
 
 
/*  Seeking individual clauses.  */
 
 
editor_clause( N, Head, Body, Vars ) :-
    textbase( T ),
    array_member( N, F, T ),
    fact_vs_clause_and_vars( F, Clause, Vars ),
    clause_vs_head_tail( Clause, Head, Body ).
 
 
/*  Question predicates.  */
 
 
no_question_stored :-
    not( question(_) ).
 
 
store_question( Q ) :-
    retractall( question(_) ),
    assert( question(Q) ).
 
 
the_question( Q ) :-
    question( Q ).
 
 
/*  Selectors.  */
 
 
/*
A range denotes a set of facts as requested, for instance, by the
argument to show:
    show lives_in.
    show all.
    show 10--200.
It can be one of:
    pred(Predicate_Name)  --- all facts for that predicate.
    all                   --- all facts.
    range( Lub, Upb )     --- all facts between Lub and Upb inclusive.
 
It can also denote a question, as in
    show q
in which case it is
    q                     --- the question.
*/
 
 
range_is_all( all ).
 
range_is_lu( range(Lub,Upb), Lub, Upb ).
 
range_is_pred( pred(Pred), Pred ).
 
range_is_question( q ).
 
range_type( all, facts ) :- !.
range_type( range(_,_), facts ) :- !.
range_type( pred(_), facts ) :- !.
range_type( q, question ) :- !.
 
 
/*  Facts and questions. */
 
 
standardise_clause( (H:-true), H ) :- !.
standardise_clause( (H:-T), (H:-T) ) :- !.
standardise_clause( H, H ).
 
 
is_fact( fact(_,_) ).
is_fact( fact(_) ).
 
is_good_fact( fact(_,_) ).
 
 
fact_vs_clause_and_vars( fact(Clause,Vars), Clause, Vars ).
 
fact_vs_clause( fact(Clause,_), Clause ).
 
fact_vs_vars( fact(_,Vars), Vars ).
 
fact_vs_clause_pred_arity( FS, Clause, P, A ) :-
    fact_vs_clause( FS, Clause ),
    clause_vs_pred_arity( Clause, P, A ).
 
fact_vs_pred_arity( FS, P, A ) :-
    fact_vs_clause_pred_arity( FS, _, P, A ).
 
fact_vs_text( fact(Text), Text ).
 
 
is_question( question(_,_) ).
is_question( question(_) ).
 
is_good_question( question(_,_) ).
 
 
question_vs_goal_and_vars( question(Goals,Vars), Goals, Vars ).
 
question_vs_text( question(Text), Text ).
 
 
:- endmodule.
