Date: 25 November 1980 12:52-EST
From: Robert W. Kerns <RWK at MIT-MC>
Subject:  Feature double creature
To: KMP at MIT-MC
cc: LISP-FORUM at MIT-MC, "(FILE [LISP;SHARPC DOC])" at MIT-MC

Well, I suppose I should at this time (I've been postponing it) propose my
FEATURE-SET proposal.

First off, a couple notes about KMP's proposal:

1)  I think the need for a canonical way of determining which dialect the code
is running under is clear.

2)  I don't see any reason for it to be a STATUS mumble rather than a variable.

3) The issue is separate from issues of sharp-sign conditionalization and
feature-testing.  Sharp-sign conditionalization does not want to depend on what
features are PRESENT AT READ TIME, but rather WHAT FEATURES ARE PRESENT AT
TIME-OF-USE.  This last is the motivation for my proposal.

Some of you have seen this before.  I've long intended to bring this out as
an alternate mode for the existing MACLISP SHARPM package, but haven't had the
time for integrating my code into the current version.  But since it turns out
I need it for cross-compilation in NIL, I've just fixed it up.

1)  A single LISP environment often has need of reading forms intended for
different LISP environments.  For example, a compiler may be doing READ for
forms which may be EVAL'ed in its environment, or which may be EVAL'd in some
other LISP with possibly different features (probably not the COMPLR feature!)
with or without compilation.  I.e. the line #-FOO in a source file usually
refers to a feature FOO in the LISP that the compiled file will run in.  (I'll
get into exceptions to this later).  The LISP where it will eventually matter
whether the FOO feature exists I will call the TARGET LISP.  The target LISP
may or may not be the same LISP that is doing the READ that see's the #+FOO.

2)  It is not really enough to say that because a feature is not on a list of
features somewhere that a feature is not present.  Sometimes this is because
whoever told the compiler what features exist simply did not know all about the
target environment (I.e. I'm compiling this file for use with the RWK-HACKS
feature).  Sometimes you know that a feature is NOT present, like FRANZ in the
MACLISP compiler.  The obvious solution is to maintain TWO LISTS, of KNOWN
FEATURES and NON-FEATURES.  What do you do if a feature is not known?  Two
options are to signal an error, and to ask the user.  I prefer the later.

3)  It is not enough to have simply ONE set of features.  When doing a LOAD in
a COMPLR, for example, one generally intends for that code to be evaluated in
the compiler, so the LOCAL LISP is the target, and the LOCAL FEATURES should be
brought to bear.  When reading for compilation, COMPILATION-TARGET-FEATURES
should be what is used (by default) for FEATUREP.

So!  I define the following goodies:

[This stuff is available by loading ((LISP) SHARPCONDITIONALS) either on top of
 or instead of ((LISP) SHARPM)]

defun DEF-FEATURE-SET target features &optional nofeatures (query-mode ':QUERY)
  Defines a feature-set for a target environment named <target>, with
  the supplied features and non-features.  If <query-mode> is :QUERY, the
  user will be asked each time.  If <query-mode> is :ERROR, an error is
  signaled. If <query-mode> is T, FEATUREP of things unknown returns T, while
  if it is (), it returns ().

[This query-mode stuff is merely to satisfy any namby-pambies out there who
 don't like this winning stuff for one reason or another.  If nobody dislikes
 this stuff, maybe it should be flushed as being excess hair.]

defvar TARGET-FEATURES default LOCAL-FEATURES
  The default feature-set for FEATUREP and the sharp-sign macro.  This should
  be bound around calls to READ when the result of the READ will be understood
  in some other environment, such as compilers.

defun FEATUREP feature &optional (feature-set TARGET-FEATURES) (featurep T)
  Returns T if <feature> is known to be a feature in <feature-set>.
  <feature-set> is a symbol which has been defined to be a feature-set by
  DEF-FEATURE-SET.  If it the feature is not known to be either a feature or
  not a feature, action is taken according to the query-mode of the feature
  set.  (see DEF-FEATURE-SET)

  featurep is a purely internal flag which if () turns FEATURP into NOFEATUREP.

defun NOFEATUREP feature &optional (feature-set TARGET-FEATURES)
  like FEATUREP, except returns T if known NOT to be a feature.

FEATUREP will take not only a feature name, but a generalized FEATURE-SPEC.  A
feature-spec is a feature name, or (OR f1 f2 ... fn), meaning T iff f1, f2, ...
OR fn satisfy the FEATUREP test, (NOT f) [meaning the same as (nofeature f)],
(AND f1 f2 ... fn) meaning T iff f1, f2, ... AND fn satisfy the featurep test.
In addition to these familiar operators can be used any name of a FEATURE-SET.
I.e. (FEATUREP (AND (LOCAL-FEATURES F1) F2)) returns T iff F1 is a feature in
LOCAL-FEATURES and F2 is a feature in whatever feature set is the value of
TARGET-FEATURES.  This may have some limited application in conjunction with #+
and #-, but should not be used wantonly.

#+ and #- are followed by a feature-spec and another form.  #+ calls FEATUREP,
and #- calls NOFEATUREP on the feature-spec, and if () is returned, the next
form is gobbled and thrown away.  Note that it is gobbled via a recursive READ,
so if it contains illegal syntax or #.'s, an error may result.  Note that
using #- and #+ inside forms conditionalized with #- and #+ can quickly result
in unreadable and unmaintainable code.

A few functions for manipulating FEATURE-SETs:

defun COPY-FEATURE-SET old new
  This function makes a new feature-set having the same known features,
  non-features, and query mode as the old.  It is probably the right way
  to create new feature sets for related environments.

defun SET-FEATURE feature &optional (feature-set-name TARGET-FEATURES)
  This function makes <feature> be a feature in the feature-set named by
  <feature-set-name>.  (FEATUREP <feature> <feature-set-name>) will thereafter
  return T.

defun SET-NOFEATURE feature &optional (feature-set-name TARGET-FEATURES)
  This function makes <feature> be a non-feature in the feature-set named by
  <feature-set-name>.  (NOFEATUREP <feature> <feature-set-name>) will
  thereafter return T.

defun SET-FEATURE-UNKNOWN feature &optional (feature-set-name TARGET-FEATURES)
  This function makes <feature> be unknown in the feature set.  Depending on
  the query-mode, FEATUREP and NOFEATUREP may query, give an error, or assume
  the default values thereafter.

defun SET-FEATURE-SET-QUERY-MODE feature-set-name mode
  sets the feature-set query-mode to <mode>.  <mode> can be one of :QUERY,
  :ERROR, T, or (), as described above.

