Building Elf.   --> see file INSTALL
======================================================================
Emacs Support.  --> see file emacs/README
======================================================================
Documentation.  --> none, but see the note at the end of this file.
======================================================================
Examples.

A number of example programs can be found in examples/*/*, see the README
files there.  They are now distributed separately in the elf-examples.tar.Z
file.  To load them (after they have been installed), do, for example,

% bin/elfsml
- cd "examples/lam";
- use "load.sml";
- load ();
- top ();
?- ... queries here ...
?- ^D  % Control-D to drop back to SML
- cd "../fol";
- use "load.sml";
... etc ...

Check the file "examples.quy"; in each directory for example queries.
They can be run with

- batch_top "examples.quy";

======================================================================
Top-level utilities.

Elf is currently not a separately exported core image, but one uses SML
to set up a top-level which executes queries in a manner familiar from
Prolog.  This interaction is a temporary measure: we expect a future
release to allow exportation of a stand-alone core image.

The top-level maintains a top-level environment of signatures.  Each
file defines a signature, and the association between the filename and
the signature is remembered.  There is also a global signature, which
contains declarations for all constants.  This global signature is used
by type reconstruction.

Each signature is declared as either dynamic or static.  A dynamic
signature will be used for search, and free variables whose type is
dynamic (formerly: closed) will be solved during execution of a query.
A static signature will only be used for type checking.  Another
intuition: a static signature contains data type and constructor
declarations, a dynamic signature contains predicate declarations and
clauses.

help ()         --- print a short help message.

initload [sfile1,...,sfilen] [dfile1,...,dfilem]
                --- reset & load, sfilei static, dfilej dynamic.
top ()          --- invoke Elf top-level

sload [file1,...,filen] --- static load.
dload [file1,...,filen] --- dynamic load. 
reload file     --- reload with previous annotation (static or dynamic).
                    this will destructively replace a current entry.
toc ()          --- print information about top-level environment.
show_prog ()    --- print rules in top-level environment
print_sig file  --- print signature, elliding arguments
print_sig_full file --- print signature, with implicit arguments
reset ()        --- empty out all signatures and top-level environment.

batch_top file  --- take top-level queries from file

cd "directory"  --- change working directory.
pwd ()          --- print current working directory.
ls "pattern"    --- list current working directory contents.

trace (n)       --- trace execution with verbosity level n.
untrace ()      --- stop tracing (same as trace(0)).
chatter (n)     --- set verbosity of chatter (default: 2).

A typical interaction cycle might be

- initload ["logic.elf"] ["prover.elf"];
- top ();
?- ... working here, found bug, edited source file ...
?- ^D  ... this is ^D, to drop back to SML ...
- reload "prover.elf"; top ();
?- ... posing more queries here ...

The top-level works more or less like Prolog's top-level.  The query can be
either simply a type or a typing judgment.  A type "A" will be solved, that
is, the interpreter constructs an object M of type A such that all free
variables in M have static type, but M will not be shown.  A query of the
form "M : A" can be used for LF type-checking (if M is a closed object) or
to see the object resulting from search (if M is a free variable).

One can also directly take advantage of the constructed object and use
it as input to further goals.  For this purpose, a special top-level
form "sigma [x:A] B" is provided.  Operationally, this first solves A,
say with proof M, and then solves [M/x]B.  The corresponding deduction
is a pair of the witness M and deduction of [M/x]B, using the proof
constructor #pr#.  This form can be iterated (that is, B can start with
another sigma), but sigma is not properly part of the type theory and
not allowed in signatures or in other terms.

You can request further solutions with ";<Return>" after a solution
substitution has been printed.  Simply typing "<Return>" will return to
the top-level and prompt for the next query.

If the query consists of multiple lines, Elf will print a few spaces
after digesting one line of input.  This serves as an indication that
further input is expected.  A query must be terminated by a period (.).
Some syntax errors lead to a hanging parser, and only further input will
lead to an appropriate error message.  I have been told this is a
property of LALR parsers (the Elf parser is generated by ml-yacc and
ml-lex).  In order to avoid any ambiguity the interpreter prints
"Solving ..." after it has type-checked the query and before it starts
executing the query.

There are further utilities available as part of the Elf Tools
(distribution file elf-tools.tar.Z).  These currently offer the
following additional top-level functions:

families ()  --- lists all type families available in the current top-level
                 together with the file in which they are defined and their
                 hierarchy level.
deps ()      --- prints the dependency classes for all current type families
cdeps ()     --- prints the dependencies between type families induced by
                 each clause in the current top-level.

For more information please refer to the tools/README file of the
tools distribution.
======================================================================
Flags and Tracing Options.

There are a number of flags and tracing options which can be used to determine
the style of interaction.  Note that some of them are very low-level and might
produce reams of output!  Note that it is necessary to drop from the Elf
top-level to SML in order to query or change the flags.

The simplest way to trace execution of a query is provided by the function
Elf.trace : int -> unit.  See also BACKQUOTE below for more selective tracing.

Elf.trace(0)  --- no tracing (same as Elf.untrace ()).
Elf.trace(1)  --- tracing of goals during execution of the query.
Elf.trace(2)  --- also trace attempted clauses and reasons for failure.
                  Note that indexing implies that many clauses will never
                  be attempted.
Elf.trace(n)  --- for larger numbers it will provide additional information.
                  Maximally effective number is 5 right now.
Elf.untrace   --- Stop tracing (same as Elf.trace(0)).

When tracing goals, Elf will print "[" before any goal which is attempted.
When the goal finally fails, a matching "]" is printed.  This means that you
can wipe out any unsuccessful branch in the computation by deleting matching
brackets and all the text in between.  In Emacs, an appropriate command
(kill-sexp) is usually bound to M-C-k or, synonymously, <Esc> ^K.

If you type <Return> after the first solution, not all outstanding "[" will be
closed (since the usual control-flow is bypassed).  However, if you
continue to ask for more solutions (with ";<Return>") until no further
solutions exist, every "[" should be matched by a "]".

Elf echoes the internal representations of declarations after type
reconstruction.  This and other chattering of the Elf interpreter
can be controlled with the function Elf.chatter : int -> unit, which
sets/resets a combination of various flags.

Elf.chatter(0) --- very few messages.
Elf.chatter(1) --- show signatures and dynamic families when entering the
                   interactive top-level with top ().
Elf.chatter(2) --- print internal representations of all declarations after
                   type reconstruction [Default].

Individual flags are examined and assigned to at the SML top-level, that is,
they must be set to either true or false.  For example,

- S.trace "goal" := true;

A summary of all available flags and tracing options:

E.warn "redeclaration" % If true, redeclarations lead to a warning.
E.warn "redeclaration_initload"
                       % If false, redeclarations during initload do not lead
                       % to a warning.
E.warn "implicit"      % If false, implicit arguments which can be neither
                       % synthesized or inherited do not lead to a warning.

E.trace "echo_declarations"  % If false, declarations are not echoed.
E.trace "show_proof"         % If true, show proof objects.
E.trace "show_obligations"   % If true, show remaining proof obligations.
E.trace "show_dynamic"       % If false, dynamic families are not shown when
                             % entering the top-level.
E.trace "time_queries"       % If true, each query is timed.
E.trace "batch_show_subst"   % If false, substitutions and proof objects are
                             % not shown in batch mode.
E.trace "batch_echo_query"   % If false, the query is not echoed in batch mode.


E.control "all_solutions" % If true, enumerate solutions eagerly.

S.trace "type"         % Type considered as a goal.
S.trace "assume"       % Temporary assumptions made.
S.trace "clause"       % Clauses as they are tried.
S.trace "resolve"      % Clauses whose head unified with the goal.
S.trace "goal"         % Dynamic types when considered as goals.
S.trace "open"         % Open types as they are postponed.

T.trace "type_recon"   % Types inferred for subterms.
T.control "print_internal"
	  	       % If true, type error and trace messages will be shown
		       % in internal format with implicit arguments.

U.trace "instantiate"  % Instantiation of logic variables.
U.trace "dset"         % Maintenance of the disagreement set.
U.trace "simplify"     % Constraint simplification.
U.trace "failure"      % if true, the reason for unification failure is
                       % given (eg constant clash, cycle, etc).  This
                       % can be useful to determine why a clause is not
                       % applicable, even though it might look applicable.
U.trace "unify"        % Calls to unification.

% A few variables controlling printing are explained below

% The following flags are for development purposes only and should
% not be of interest to the ordinary user.

UnifySkeleton.omit_occurs_check
                       % if false, redundant(!) occurs-checks are not omitted.

U.control "use_redundancy_info"
                       % if false, redundant arguments are still unified.

U.control "unsafe_omit_occurs_check"
                       % Omit dependency and acyclicity check from unification
                       % even when it has not shown to be sound.
                       % Don't do this unless you REALLY know what you are
                       % doing, since it makes the interpreter unsound.
======================================================================
Concrete Syntax.

For precise information, you may need to check the files elf/grammar/elf.lex
and elf/grammar/elf.grm (these are ml-lex and ml-yacc sources).

Lexing.

The reserved characters are:

%  --- begins comment or pragma which extends to the end of the line.
       Delimited comments are enclosed in matching %{ ... }% .
:  --- type or kind declaration.
.  --- end of signature entry (type or kind declaration).
() --- parentheses, used to scope operators as usual.
[] --- lambda abstraction.
{} --- pi quantification.

All other characters can be included inside identifiers which are
separated by whitespace or reserved characters.  In particular,
  A->B  is an identifier, whereas  A -> B  stands for the type of
functions from A to B.

An uppercase identifier is one which begins with an underscore _ or a letter
in the range A through Z.  A lowercase identifier begins with any other
character except digits and reserved identifiers.  Numbers also count as
lowercase identifiers and are not interpreted specially.  Identifiers of the
form 'id' override the infix status of operators (see below).  Free variables
in a signature entry (declaration) must be uppercase, bound variables and
constants may be either uppercase or lowercase identifiers.  The identifiers
sigma, #pr#, ` (backquote), and #`# are not treated special lexically, but
predeclared (see below) and thus perhaps better not used in user programs.

Constants have static scope, which means that they can be shadowed by
later subsequent declarations.  A shadowed identifier (which can no
longer be parsed) is printed as %id%.

Parsing.

Here is the syntax for Elf in abbreviated form.  Term's form the join
of the three levels in the LF calculus, which are not distinguished
syntactically.  The comment approximates the meaning in LF.

sigentry ::= id : term.
query    ::= term.

term ::= type                % Type
       | term1 -> term2      % A -> B
       | term1 <- term2      % B -> A
       | {id : term1} term2  % \Pi x:A. K  or  \Pi x:A. B
       | [id : term1] term2  % \lambda x:A. B  or  \lambda x:A. M
       | term1 term2         % A M  or  M N
       | term : term         % explicit cast
       | _                   % hole, to be filled by term reconstruction
       | {id} term           % same as {id:_} term
       | [id] term           % same as [id:_] term
       
In the order of binding strength we disambiguate as follows:

Juxtaposition is left-associative and binds the strongest.
-> is right associative.
<- is left associative.
:  is left associative.
{} and [] are weak prefix operators.

For example, the following are parsed identically:
   d : a <- b <- {x} c x -> p x.
   d : ({x} c x -> p x) -> b -> a.
   d : ((a <- b) <- ({x:_} ((c x) -> (p x)))).

Infix, Prefix, Postfix, and Name Preference Declarations.

Fixity declarations have the form

%infix <assoc> <prec> c1 ... cn
%prefix <prec> c1 ... cn
%postfix <prec> c1 ... cn

Here <assoc> is left, right, or none;
<prec> is the precedence (the higher, the tighter) between 0 and 10000;
c1 ... cn are the declared symbols.  The declaration ends with the line.
For example:

%infix left 10 + -
%infix left 20 * /
%prefix 30 ~

%infix right 5 &
%infix right 4 =>
%prefix 3 |-

Name preference declarations (partially implemented)
%name <fam> x1 ... xn

x1 ... xn will be used as the name for variables whose type (family) is
<fam>.  Regular numbering will only be used when the list is exhausted.
For example:

i : type.
%name i  x x' x''

o : type.
%name o  A B C

|- : o -> type.
%name |-  P1 P2 P3 P4 P5

======================================================================
Term Reconstruction.

The model of term reconstruction employed by Elf is straightforward, although
it employs a relatively complex algorithm.  `Term reconstruction' includes
type reconstruction in the usual sense plus the reconstruction of implicit
arguments to functions and filling in wildcards (written as an underscore `_'
in the input).  The basic principle to keep in mind is the duality between
omitted quantifiers and implicit arguments.  For example,

o : type.
& : o -> o -> o.  %infix right 5 &
pf : o -> type.
andi : pf A -> pf B -> pf (A & B).

The last declaration will be printed back as one of the following two

andi : {A:o} {B:o} pf A -> pf B -> pf (A & B).
andi : {B:o} {A:o} pf A -> pf B -> pf (A & B).

which is the internal representation.  Note that the quantifiers on A and B
were omitted in the signature.  This means that the corresponding arguments to
andi remain implicit and will be reconstructed.  For example,

([p:o] [u:pf p] andi u u) : {p:o} pf p -> pf (p & p).

This is parsed into

([p:o] [u:pf p] andi _ _ u u) : {p:o} pf p -> pf (p & p).

and the two wildcards are determined to be p.  Note that the second line is
not a legal input, since the implicit arguments MUST be omitted.  This is a
necessary consequence of Elf's approach to term reconstruction, since the
order of the implicit quantifiers (and therefore of the implicit arguments) is
unspecified in the declaration of andi above.

Wildcards can also be explicitly given or they arise from implicit types
in abstractions [x] or quantifiers {x}.  For example,

([p] [u:pf p] andi u u) : {p} pf p -> pf (p & p).

is parsed into

([p:_] [u:pf p] andi _ _ u u) : {p:_} pf p -> pf (p & p).

and the values for the wildcards are determined as o, p, p, and o,
respectively.

The algorithm for reconstruction employs an algorithm for solving
constraints over dependently typed lambda-terms described in a
paper ``Unification and Anti-Unification in the Calculus of Constructions,''
LICS'91.  Effectively, the value of an omitted argument may be
determined from other arguments, or synthesized from the context
in which an expression occurs.  Here are some typical examples:

v : o -> o -> o.  %infix right 4 v
ori : pf A -> pf (A v B).

In an expression (ori M) which is parsed into (ori _ _ M), the value
of the first argument for, say, A, can be determined from M.  However,
the value of B can only be determined from the context it appears
in.  For example,

([p:o] [u:pf p] ori u) : {p:o} pf p -> pf (p v p).

determines both implicit argument to ori as p.  We refer to this situation
by saying that in the declaration of ori, A can be synthesized, and both A
and B can be inherited.

In some circumstances, an argument can neither be synthesized nor inherited.
For example, consider the additional declarations

i : type.
all : (i -> o) -> o.
alle : pf (all A) -> pf (A T).

Now T can not always be inherited (and clearly not synthesized).
For example,

([p:o] [u:pf (all [x:i] p)] alle u) : {p:o} pf (all [x:i] p) -> pf p.

but the implicit argument T to alle is inherently ambiguous (could be any
term).  Such a situation often leads to trouble during term reconstruction
later on and is flagged with a warning by the front-end.  In such a situation,
it is desirable to make the argument explicit, as in

alle : {T:i} pf (all A) -> pf (A T).

Note that A can be synthesized and can therefore safely remain implicit.

In some circumstances it is useful to directly ascribe a type in order
to disambiguate inherited arguments.  For example

[p:o] [u:pf p] ori p

has type {p:o} pf p -> pf (p v A p) for any A : o -> o.  We can
disambiguate by ascribing a type to a subexpression.  For example,

[p:o] [u:pf p] (ori p : pf (p v p))

In some circumstances, especially when arguments of some constants can be
neither synthesized or inherited, a principal reconstruction for a given
declaration does not exist even for expressions which have some type.  In such
a case the front-end issues an error message about remaining constraints after
type reconstruction.  The constraints are shown, but they are often difficult
to interpret.  As a general rule of thumb, (1) arguments which can be neither
synthesized nor inherited should be made explicit, and (2) variables which
occur only at the head of some application should be avoided.  In the latter
case the expression can often be disambiguated by explicitly ascribing types
which mention the variable in question in a synthesizable or inheritable
position.

When term reconstruction fails, the front-end issues an error message
with the line number of the declaration in which the problem occurred
and the disagreement encountered, printed in the form

<filename>:<location> Error:
Type checking failed on declaration of c
M : A <> B
Unification failure due to <reason>

which means that the type inferred for M was A but that some constraints
required it to be equal to B, and A and B are different (that is, do not
unify).  The reason why A and B do not unify is given in the next line.  The
<filename> and <location> information can be used by Elf's Emacs interface to
jump to the specified location in the given file for editing of the incorrect
declaration for the constant c.  The <location> has the form
line1.column1-line2.column2 and represent Elf's best guess as to the source of
the error.  Due to the propagation of non-trivial constraints
the source of a type reconstruction failure can sometimes not be
pinpointed.   In that case the error message has the form

<filename>:<location> Error:
Type checking failed on declaration of c
M : A 
is inconsistent with other constraints
Unification failure due to <reason>

When the reason for failure of type reconstruction is elusive, a recommended
strategy is to explicitly ascribe types to subterms occurring in the
declaration, using the form (M : A).  It is also possible to trace type
reconstruction using
  T.trace "type_recon" : bool ref
in which case the type inferred for every subterm is shown explicitly.

Normally, error and trace messages are printed in reparsable format (see
section on Printing below), but sometimes this is impossible---the printer
then falls back on internal form and surrounds every externally unprintable
term in double quotes.  The variable
  T.control "print_internal" : bool ref
can be set to true to force the printer for type messages to always use the
internal format.  Note that this variable affects only type reconstruction,
not the printing of proof objects or substitutions.

======================================================================
Printing.

In accordance with the description above, there are two ways of printing
terms.  One is a reparsable format, in which infix identifers are printed in
infix and implicit arguments are omitted.  The other shows the internal form
in order to allow the user to inspect the result of type reconstruction.
Normally, signatures are printed back in internal form after reconstruction,
and answer substitutions after a successful solution to a query are printed in
reparsable form.  Constants c which have been shadowed (and can therefore no
longer be parsed) are printed as %c%.  A signature (= file of declarations)
can be printed using
  print_sig "filename";
to see the reparsable version, and
  print_sig_full "filename";
to see all implicit arguments.

Different sorts of variables are printed differently at the moment to
aid in their recognition.  Logic variables are simply printed as free
variables, the way they would occur in input.
 !u --- parameter (uvar) u.
 ^F --- free variable F in clause.

There are also a few variables controlling the appearance of the
terms as they are printed shown below.

% The ElfPrint.* variables apply to printing that omits implicit arguments

ElfPrint.printDepth  : int option ref   % eg ElfPrint.printDepth := SOME(5)
ElfPrint.printLength : int option ref  % eg ElfPrint.printLength := NONE

% The Print.* variables apply to full printing (with implicit arguments)

Print.printDepth  : int option ref
Print.printLength : int option ref

Formatter.Indent    : int ref         % eg Formatter.Indent := 3
Formatter.Pagewidth : int ref         % eg Formatter.Pagewidth := 80

======================================================================
Profiling and timing.

There are now a number of useful options to aid in the timing
of Elf queries.  These are available through the following functions:

 Elf.batch_top query_file;
    will read queries from query_file instead of interactively.  Queries are
echoed to std_out as they are read (but see flags below).  batch_top always
searches for just one solution and then reads the next query.

The following flags can be used to control the operation of the batch or
profiling top-level.

 E.trace "batch_show_subst" : bool ref
    if false, substitutions (and proof objects) are not shown in batch mode.

 E.trace "batch_echo_query" : bool ref
    if false, the query is not echoed in batch mode.

 E.trace "time_queries" : bool ref
    prints a raw timing statistics for the first solution of each query.
Cumulative times for subsequent solutions are not correct, since the
printing time for substitutions and proofs is included.
 

======================================================================
Helpful hints.

IMPORTANT: A common source of problems is that dynamic families are
loaded/interpreted statically, or static families are loaded/interpreted
dynamically.  Try to keep this distinction straight: each file should contain
only static or dynamic declarations.  Common symptoms:
 (1) the query succeeds when it should fail, because some families which
intuitively should be treated subgoals are static.  Remember that any
query A will succeed if A is static!
 (2) the query does not terminate, because some families which should
be treated as static are dynamic.  For example, if the declarations
  nat : type.
  s : nat -> nat.
  z : nat.
are loaded dynamically, then we may get non-terminating behavior in
circumstances where a free variable of type nat should remain in the answer.
For example,
  eq : nat -> nat -> type.
  id : eq N N.
  ?- eq N N.
does not terminate if both eq and nat are dynamic.  One should also keep in
mind that it does not make sense for a static family to depend on a dynamic
one.  For example, if the query has the form a M1 ... Mn and a is a static
type family then no free variable in M1 ... Mn will be solved, regardless of
whether it is static or dynamic.

IMPORTANT: Note that even symbolic identifiers must be surrounded by
whitespace.  For example, a->b is one identifier.  The only exceptions are the
characters .:()[]{} which terminate identifiers.

IMPORTANT: Wild cards (_) are existentially quantified when a clause is
read, free variables (undeclared identifiers beginning with upper case)
are universally quantified!  Wild cards should be used sparingly, and only
if the omitted argument is uniquely determined by the context.

IMPORTANT: Scoping is static.  So after reloading an intermediate file,
other files which depend on it must also be reloaded.
======================================================================
Special families.

There are two type families which are given a special interpretation during
execution of a program: sigma and `.  The first is generally useful at
the top-level.

SIGMA: use ?- sigma [x:A] B. to get the operational behavior of solving A with
proof M, then solving [M/x]B.  The corresponding deduction is a pair of the
witness M and deduction of [M/x]B, using the proof constructor #pr#.  This
will be replaced by the "solve" declaration in the module system.  Sigma's can
be nested, but they can only be used at the top-level and cannot be embedded
in signatures, since they are not properly part of the type theory.

BACKQUOTE: use ` A (backquote A) for tracing invocations of the goal A.  Note
that this will break proof transformation code, since the type of the
constants now involves backquote.  The constructor #`# coerces an object of
type A into an object of type backquote A and will thus appear in proof
objects if a subgoal of the form ` A was successful.

======================================================================
Known Bugs.  (Reports of unknown bugs are welcome!)

- BUG: Currently, there is no acyclicity check for universes in the
  type inference engine.

  Consequence: The system behaves as if "type:type".  This does not
  matter for the applications, but after some hard work one could
  obtain non-normalizing terms and wreak havoc on the system.

  Workaround:  Don't try to take advantage of the fact that the
  implementation considers type:type.

======================================================================
Planned improvements.  Further suggestions and bug reports welcome!

- Allow lowercase free variables, but give warning.
- Clean up and enhance tracing options.
- Single step execution mode.
- Module system, including internal specification of dynamic/static.
- Notational definition in Elf.
- Handling of integers.
- Find more reasonable way of printing of hard constraints.
- Make Elf separately exportable as a stand-alone program.
- Emacs support for editing using types.
======================================================================
Documentation.

There is currently no language manual.  However, you may consult the papers
[Michaylov91] and [Pfenning91] as the standard references for Elf.
[Michaylov91] is of a quasi-tutorial character, built around a major example,
[Pfenning91] is a more technical description.  Further papers dealing with
various aspects of the LF logical framework and Elf can be retrieved via
anonymous ftp.  Please check the README file for further information.

[Michaylov91]
    Spiro Michaylov and Frank Pfenning. Natural
    semantics and some of its meta-theory in Elf. In
    Lars Halln"as, editor, Extensions of Logic
    Programming. Springer-Verlag LNCS. To appear. A
    preliminary version is available as Technical
    Report MPI-I-91-211, Max-Planck-Institute for
    Computer Science, Saarbr"ucken, Germany, August
    1991.

[Pfenning91] Frank Pfenning. Logic programming in the LF logical
    framework. In Gerard Huet and Gordon Plotkin,
    editors, Logical Frameworks, pages 149-181.
    Cambridge University Press, 1991.
