home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-06-24 | 71.1 KB | 1,523 lines | [TEXT/MACA] |
- ; (c) Copyright 1990 by University of Massachusetts. All rights reserved.
- ; This software was conceived, designed, and written by Dan Suthers
- ; while supported by the National Science Foundation under grant number
- ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
- ; CA. Partial support was also received from the Office of Naval Research
- ; under a University Research Initiative Grant, contract N00014-86-K-0764.
- ; Mr. Suthers created this software under his own initiative while in an
- ; academic relationship with the University of Massachusetts. The above
- ; copyright notice was a condition placed by University lawyers on approval
- ; of distribution of this software by Apple Computer, and is not meant to
- ; imply that this software was created in an employment or "work for hire"
- ; relationship between the University and Mr. Suthers.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- ; File: DNET.LISP
- ; Author: Dan Suthers
- ; Created: 10-Apr-88 01:48:11
- ; Modified: 22-Jun-90 02:31:07 (Dan Suthers)
- ; Language: LISP
- ; Package: DNET
- ;
- ; Description: Simple discrimination net for uniquifying list expressions
- ; and associating information with those expressions.
- ; Includes pattern matching and context switching mechanisms.
- ;
- ; (c) Copyright 1988, by Daniel D. Suthers
- ; Department of Computer and Information Science
- ; University of Massachusetts
- ; Amherst, Massachusetts 01003
- ;
- ; This software was conceived, designed, and written by Dan Suthers
- ; while supported by the National Science Foundation under grant number
- ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
- ; CA. Partial support was also received from the Office of Naval Research
- ; under a University Research Initiative Grant, contract N00014-86-K-0764.
- ; I wish to acknowledge the generous support of Beverly Woolf, who obtained
- ; the above grants and encouraged me to pursue my own research interests in
- ; her lab. This work would not have been possible without the resources and
- ; stimulating environment of the Computer and Information Science department.
- ;
- ; Permission to use, modify, and distribute this software is granted subject
- ; to the following restrictions and understandings:
- ; 1. The file header, including this notice, shall be retained, and may be
- ; extended to include documentation of modifications to the software.
- ; 2. This material is for nonprofit educational and research purposes only.
- ; Users are requested, but not required, to inform Mr. Suthers of any
- ; noteworthy uses of this software.
- ; 3. Mr. Suthers and the University of Massachusetts make no warrantee or
- ; representation that the operation of this software will be error free,
- ; and are under no obligation to provide any services.
- ; 4. Any user of such software agrees to indemnify and hold harmless Mr.
- ; Suthers and the University of Massachusetts from all claims arising
- ; out of the use or misuse of this software, or arising out of any
- ; accident, injury, or damage whatsoever, and from all costs, counsel
- ; fees, and liabilities incurred in or about any such claim, action, or
- ; proceeding brought thereon.
- ; 5. All materials and reports developed as a consequence of the use of
- ; this software shall duly acknowledge such use, in accordance with
- ; the usual standards of acknowledging credit in academic research.
- ;
- ; Status: Stable and well tested.
- ;
- ; Tested: Hewlett Packard 9000 02-Nov-88 Dan Suthers
- ; Macintosh II Coral/Allegro 20-Apr-89 Dan Suthers
- ; Texas Instruments Explorer 02-Nov-88 Dan Suthers
- ; VAX/VMS 20-Apr-89 Dan Suthers
- ;
- ; Changes:
- ; 25-Jun-88 Type checking, type declarations, and optimizing.
- ; 30-Jun-88 New-expr-hook now specific to each dnet.
- ; 02-Jul-88 BIND conflicts with TI compiler; renamed to BIND-VARS.
- ; 13-Jun-88 Update to new version of SM.
- ; 15-Jun-88 Added SUBSTITUTE-VARS.
- ; 20-Jul-88 SAVE-DNET now saves with the explicit name; no *dnet*.
- ; Dotted lists checked for by INDEXPR, since we optimize out any
- ; type checking once past the argument checking. Dnet-Terminals
- ; no longer in SM.
- ; 30-Jul-88 DNET-ROOT -> DNET (this was an artifact); DNET-TERMINALS
- ; eliminated to save space; INDEXPR-HOOK and DELEXPR-HOOK instead of
- ; NEW-EXPR-HOOK; changes to MAKE-DNET syntax; VARIABLE -> DEFVARIABLE;
- ; proclamations.
- ; 01-Nov-88 Update to reflect SM changes.
- ; 16-Nov-88 Added MATCH and UNIFY; made optimize declarations better;
- ; made internal versions of functions that don't check args.
- ; 20-Nov-88 MATCH-PATTERN handles &rest "dotted variables": (a b . ?:x);
- ; match-x-internal takes previous-bindings argument (for client's sake).
- ; 24-Nov-88 Added MAP-DNET-TERMINALS.
- ; 10-Dec-88 Added RESET-DNET; SAVE-DNET now finds variables in INFO too.
- ; 17-Dec-88 ? package now does not use any other package, so symbols in
- ; package LISP won't screw it up when declared as variables in ?.
- ; DEFVARIABLE now takes strings as well as symbols.
- ; 23-Dec-88 SUBSTITUTE-VARS renamed SUBSTITUTE-BINDINGS. Added version
- ; called SUBSTITUTE-TRANSITIVE-BINDINGS for ((?:x . 3) (?:y . ?:x)).
- ; 16-Jan-89 SAVE-DNET uses INDEXPR-INTERNAL for faster load.
- ; 22-Mar-89 Added PATTERN-P; Made UNIFY and MATCH return 3 values to
- ; separate the bindings in each direction.
- ; 24-Nov-89 Updating documentation only.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- ; USER'S DOCUMENTATION
- ;
- ; A discrimination net facility lets one do several things:
- ;
- ; - 'Uniquify' list expressions under EQ by allowing one to retrive a stored
- ; expression using an EQUAL expression as the key.
- ; - Associate properties with list expressions (using the above capability).
- ; - Ask whether a particular expression has been stored in a data base.
- ; - Retrieve expressions by pattern matching using variables in either the
- ; retrieval key or in the stored expressions.
- ; - Change contexts efficiently by changing the discrimination net in use.
- ;
- ; A discrimination net may be used to implement a simple data base, or to
- ; support more sophisticated systems for deductive retrieval, forward and
- ; backward chaining of rules, and/or truth maintenance. The present package
- ; attempts to be simple, general, and efficient by providing the most basic
- ; operations efficiently implemented without frills.
- ;
- ; Most operations take the discrimination net as their last argument. This
- ; facilitates context switching. Storage of dotted lists is not allowed,
- ; due to the indexing method used. Pattern matching retrieval processes
- ; variables in either the query pattern (MATCH-PATTERN) or the network
- ; (MATCH-EXPRESSION), or both (MATCH). The general MATCH function is less
- ; efficient, and most applications only need to match in one direction.
- ;
- ; When a new expression is added via INDEXPR, some applications will need
- ; to do special processing of the expression and/or its dnet-terminal. If
- ; this were left to the application after the INDEXPR call returned, this
- ; processing could not be done on expressions loaded from a file saved by
- ; SAVE-DNET, since the latter writes calls to INDEXPR with no surrounding
- ; application-specific forms. The solution is to have an INDEXPR-HOOK,
- ; which when non-nil is a lambda called on all newly indexed expressions
- ; and their dnet-terminals. There is also a corresponding DELEXPR-HOOK.
- ;
- ; Discrimination Net Operations:
- ; MAKE-DNET makes new ones.
- ; RESET-DNET resets to empty and modifies associated info.
- ; DNET-INFO associates information with a dnet.
- ; ALL-EXPRESSIONS returns all expressions in a dnet (slow).
- ; MAP-DNET-TERMINALS maps a function across all dnet-terminals in a dnet.
- ; DESTROY-DNET undefines and deallocates a dnet.
- ; SAVE-DNET saves a dnet to a file (slow).
- ; (Functions requiring retrieval of all expressions are slow because DNET is
- ; optimized for balancing fast access to single expressions with space economy.)
- ;
- ; Expression-Based Operations:
- ; These do not process variables (treat variables like any other atom).
- ; INDEXPR puts an expression in a dnet.
- ; GETEXPR retrieves an expression from a dnet.
- ; DELEXPR deletes an expression from a dnet.
- ; EXPR-INFO associates information with an expression in a dnet.
- ;
- ; Pattern-Based Operations:
- ; A pattern is a symbolic expression which may contain variables.
- ; Variables are symbols in the package ?. It is recommended that
- ; one declare all variables before use with the (defvariable <sym>) form.
- ; No symbol should ever be uninterned from ? by another program.
- ; DEFVARIABLE defines a variable.
- ; VARIABLE-P tests whether an object is a variable.
- ; PATTERN-P tests whether an object is an expression containing a variable.
- ; VARIABLES-IN-PATTERN returns a list of variables in a pattern.
- ; MATCH retrieves patterns (or expressions) matching a given pattern (expression).
- ; MATCH-PATTERN retrieves expressions matching a given pattern.
- ; MATCH-EXPRESSION retrieves patterns matching a given expression.
- ; BIND-VARS does the variable binding (restricted unification) for one-way
- ; match candidates, and is exported for its potential usefulness.
- ; UNIFY does bidirectional variable binding (for MATCH), and is exported.
- ; SUBSTITUTE-BINDINGS substitutes for variables in a pattern to give an
- ; expression, given a binding list.
- ;
- ; NOTE ON BEHAVIOR -- The following behavior is CORRECT:
- ;
- ; ? (dnet:make-dnet :test-dnet)
- ; ? (dnet:indexpr '(a b c) :test-dnet)
- ; ? (dnet:indexpr '(a ?:x c) :test-dnet)
- ; ? (dnet:indexpr '(a ?:y c) :test-dnet)
- ; ? (dnet:indexpr '(a (?:x y z) c) :test-dnet)
- ;
- ; ? (dnet:match-pattern '(a ?:x c) :test-dnet)
- ; ((A (?:X Y Z) C) (A ?:Y C) (A ?:X C) (A B C))
- ; (((?:X ?:X Y Z)) ((?:X . ?:Y)) ((?:X . ?:X)) ((?:X . B)))
- ;
- ; ? (dnet:match-expression '(a ?:x c) :test-dnet)
- ; ((A ?:X C) (A ?:Y C))
- ; (((?:X . ?:X)) ((?:Y . ?:X)))
- ;
- ; ? (dnet:match '(a ?:x c) :test-dnet)
- ; ((A ?:Y C) (A ?:X C) (A B C))
- ; (((?:X . ?:Y)) NIL ((?:X . B)))
- ; (NIL NIL NIL)
- ;
- ; MATCH-PATTERN ignores variables in the DNET. Thus, (?:X Y Z) is a
- ; constant list as far as it is concerned, and there is no contradiction
- ; to binding ?:X to (?:X Y Z). This feature may be useful when trying
- ; to retrieve patterns without processing their variables.
- ; MATCH-EXPRESSION treats the ?:x in the query as a constant, so only
- ; returns the patterns in the DNET which match it. One happens to have
- ; a variable which is the same as the constant; the other matches the
- ; variable ?:y in DNET to the constant ?:x.
- ; MATCH will only return patterns which logically unify with the query
- ; pattern. Thus, it is correctly more restrictive than MATCH-PATTERN
- ; above, as ?:X cannot be bound to an expression containing itself.
- ; While MATCH returns bindings in both directions (the second and third
- ; values), (?:Y . ?:X) does not appear in the second binding list for
- ; (A ?:Y C) because (?:X . ?:Y) already expressed the binding, "using
- ; up" ?:Y. I.e., there is no redundancy across the binding lists.
- ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- ; Implementation Notes
- ;
- ; The implementation relies on the SM package, which contains a Structure
- ; manager for Common Lisp structures. Discrimination nets are implemented
- ; as SM objects of type DNET, and the data structures in which expressions
- ; are stored use CL structures of type DNET-TERMINAL. While DNET may be used
- ; by client programs ignorant of SM, it provides access to these SM objects
- ; in a manner which a SM-knowledgable client may use to increase efficiency.
- ;
- ; This version uses nested association lists to represent the discrimination
- ; net. This network will be quite big if printed, so this should be avoided.
- ; Use of an SM object name to identify DNETs prevents printing the network
- ; inadvertently when tracing function calls.
- ;
- ; Each DNET has a LINK slot containing the discrimination net. An INFO slot
- ; allows the client program to record related information.
- ;
- ; Terminals in the network are CL structures of type DNET-TERMINAL. The EXPR
- ; slot gives the expression indexed, and the INFO slot is used by the client
- ; to associate properties with the expression. These are represented as vector
- ; structures for efficiency, and they are un-named for economy of space.
- ;
- ; Why don't I just store the INFO at the terminal, and not bother saving the
- ; EXPR, which presumably we already know anyway by virtue of having found a
- ; path to the terminal? The pattern matchers, which may reach a multitude of
- ; terminals via variable matching, have to return all the expressions which
- ; got them there. The matching code is much easier to write if it doesn't
- ; have to keep track of how it got to the terminal in order to cons up and
- ; return the expression corresponding to the path traversed. By storing the
- ; expression, we avoid having to cons up a new version of it every time its
- ; terminal is reached. So at a small space penalty the code is simpler,
- ; faster, and costs less garbage collection.
- ;
- ; ----------------------------------
- ; Traversing the Discrimination Net:
- ;
- ; At any given time we need a handle on a place in the dnet which allows us
- ; to add new branches. The fundamental unit is called a LINK, and consists
- ; of a cons of the key that got us to where we are and the association list
- ; which takes us out of where we are:
- ; (key . ((k1 . ...) ... (kn . ....))).
- ; In most of the code, if a function is called with the above link, it may
- ; assume that we have already "consumed" the <key>, and the association list
- ; in the cdr represents the branches in the dnet which may be taken.
- ; Traversal of a link consists of selecting one of the (ki . ...) items to
- ; become the new link. Since each function sees the entire link, it can
- ; push new items onto or replace its alist.
- ;
- ; -----------------
- ; Pattern Matching:
- ;
- ; Pattern matching works by first retrieving candidate matches by processing
- ; variables without checking consistency of bindings, then checking this
- ; consistency with a unifier or partial unifier. (Only MATCH uses the more
- ; expensive full unifier.) The reasons for taking this simple approach,
- ; rather than doing retrieval and unification all at once, are:
- ; 1. Code Complexity: Recording bindings dictates continuation in recursive
- ; call with extended binding list. But continuing at a continuation link
- ; after consuming an item requires that we know what followed a variable;
- ; i.e. works best after we have returned from the recursive call that
- ; consumed items. A possible solution is to continue in recursive calls,
- ; but add a parameter passed the list the variable was found in, but ...
- ; 2. Space: To record bindings so they may be checked and returned, you must
- ; cons up their bindings to the relevant subexpressions, and save these
- ; in an alist. Many of these consed bindings will fail. In contrast, the
- ; approach implemented here conses up only bindings very likely to succeed.
- ; 3: Speed: Solutions to 1 will probably require more parameter passing, or
- ; some other speed tradeoff which reduces gain over re-processing the
- ; expressions in BIND-VARS. Garbage Collection is increased by 2.
- ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- ; Possible Improvements
- ;
- ; Specialized Unification for Speed:
- ;
- ; * Determine what has already been verified by the MATCH- internal functions,
- ; and modify UNIFY to take advantage of this. We loose export of general
- ; purpose UNIFY (without loss of logical generality), but gain efficiency.
- ;
- ; COMMENT: I did this for the match-X functions, but not for MATCH.
- ;
- ; Variable Uniqueness:
- ;
- ; Come to a decision on the variable uniqueness issue -- are variables
- ; assumed to be unique between expressions by not writing code which deals
- ; with the case when they are not, *forced* to be by writing code to treat
- ; the same ? symbol in different expressions as different, or do I write
- ; a more general unifier? Not sure if this will help anything.
- ;
- ; DNET Optimization:
- ;
- ; The next two ideas have to do with ways to optimize the space/time
- ; performance of DNETs. I don't plan on on-line transformations because
- ; it would be too expensive to decide if they are needed every time we
- ; do an insertion or deletion. Instead, a single function, called
- ; OPTIMIZE-DNET, could perform the desired transformations based on the
- ; current contents of the DNET, and could be called by the user after
- ; loading a static DNET, or periodically or after major changes.
- ;
- ; * Hash Tables for Speed:
- ; - Modify the DNET functions to use either hash tables or alists depending
- ; on which is found in the CDR of a link.
- ; - Then write a routine that recasts all a-lists above a certain length as
- ; hash tables. (Hash tables are empirically faster for even tables/lists
- ; of 3 in CCL. However, CCL and the HP both have a minimum table size of
- ; 37, which should be considered when deciding at what threshold to
- ; convert.
- ;
- ; COMMENT: I tried this. Unfortunately the minimum hash table size makes
- ; this a space-hog. Worse, speed tests showed the mixed representation
- ; was SLOWER, presumably because one must test at each link for whether
- ; a hash table or alist is used. I wanted to test a pure hash table
- ; representation, but ran out of memory under this rep (in 4 meg).
- ;
- ; * Compressing Linear Paths for Space Reduction:
- ; - Come up with a representation for linear paths, eg. a distinguishable
- ; CDR object in the links.
- ; - Modify search and deletion functions to use it.
- ; - Write a transformation routine for compression.
- ; - Note that here we must violate strict off-line transformations: an
- ; insertion may force decompression of a (formerly) linear path.
- ;
- ; Child DNETs for Context Hierarchies:
- ;
- ; Implement a facility where a DNET may have children who inherit the
- ; expressions in the parent DNET during retrieval. Insertion occurs
- ; in the specified DNET only. (Perhaps assume the child does not exist
- ; independent of the parent, so insertion into child occurs only if the
- ; expression is not already in the parent. But expressions not in the
- ; parent can be inserted into either parent or child.)
- ;
- ; TMS:
- ;
- ; Write additional code in a separate file that implements a TMS, using
- ; the EXPR-INFO to store justifications and INDEXPR/DELEXPR-HOOKs to
- ; do the truth maintenance.
- ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (in-package :DNET)
-
- (export '(
-
- *?-package*
- *dnet-package*
-
- ;; For dnets
-
- all-expressions
- destroy-dnet
- dnet-info
- make-dnet
- map-dnet-terminals
- reset-dnet
- save-dnet
-
- ;; For expressions
-
- delexpr
- getexpr
- indexpr
- expr-info
- not-a-dotted-list
-
- ;; Pattern matching
-
- bind-vars
- defvariable
- match
- match-expression
- match-pattern
- pattern-p
- substitute-bindings
- substitute-transitive-bindings
- unify
- variable-p
- variables-in-pattern
-
- ;; For clients wishing to access internal SM representations:
-
- dnet
- dnet-info-place
- dnet-delexpr-hook
- dnet-indexpr-hook
- dnet-compiled-delexpr-hook
- dnet-compiled-indexpr-hook
- dnet-terminal-expr
- dnet-terminal-info
-
- ))
-
- (require :MAPPINGS)
- (require :SM)
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; OPTIMIZING IN ...
- ;;; ----------------
- ;;; CORAL'S ALLEGRO:
- ;;; - The safety 1 space 2 speed 2 setting lets the compiler trust all
- ;;; type declarations, and eliminates event-processing in iterative loops.
- ;;; - We crank this up to safety 1 space 2 speed 3 for heavy computation,
- ;;; so fixnum operations are guaranteed to return fixnums, and car and
- ;;; cdr don't check types (but an error would crash Allegro).
- ;;; - A drop to safety 0 would eliminate number of argument and stack
- ;;; overflow checks, skip some event processing, and make slot access
- ;;; open coded with no type checking. Risky.
- ;;; -----------------
- ;;; HP's Common Lisp:
- ;;; Only safety and speed are used. This only affects compiled code.
- ;;; - Safety 0 supresses argument count check.
- ;;; - Speed 2 does constant folding, "safe tranforms" on function calling,
- ;;; conversion of &rest and &keyword to positional, and open coding where declared.
- ;;; - Speed 3 supresses argument count check (as if safety 0); makes structure slot
- ;;; access and setf inline with no checking; and additional functional transforms.
-
- (proclaim '(optimize (safety 1) (space 2) (speed 2)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; DATA STRUCTURES
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (deftype boolean () '(or T null))
-
- (defstruct (DNET-TERMINAL (:type vector) ; don't waste space with label
- (:constructor make-dnet-terminal (expr info)))
- (EXPR nil :type T :read-only t)
- (INFO nil :type T :read-only nil))
-
- (defparameter *DNET-PACKAGE* (find-package "DNET"))
-
- ;;; CCL Belched without this eval-when.
-
- (eval-when (eval load compile)
- (defparameter *?-PACKAGE* (or (find-package "?") (make-package "?")))
- ;;; Need to un-use all packages (eg LISP) so a symbol declared as a variable
- ;; in ? will indeed be a symbol in ?, and hence treated as a variable.
- (unuse-package (package-use-list *?-package*) *?-package*))
-
- (sm:dst (DNET
- (:reusable nil)
- (:sort-instances t)
- (:comments "
- Discrimination NETwork root node, which provides a handle on an entire DNET.
- Discrimination networks are used to manage a database of arbitrary list and
- symbol expressions, and do retrieval using pattern matching on expressions
- with variables in them: see documentation in the source file. Instances are
- not reusable since there are many conses in the LINK slot to be reclaimed.
- DNETs saved to a file by SM:PRINTS will be empty when loaded: use SAVE-DNET."))
-
- (LINK (list :head)
- :type list
- :computed t
- :comments "
- This slot contains the entire discrimination network, in the form of a nested
- association list (can be large). Called a link since, like all its recursive
- components, it consists of a cons of a key (in this case, the name of the DNET)
- and a list of other links (consequently, an alist).")
-
- (INDEXPR-HOOK nil
- :type list
- :comments "
- If non-NIL, contains a lambda form. The first time an expression is indexed
- into the DNET, if this is non-nil its compiled version (see next slot) is
- called on two arguments: the expression, and its dnet-terminal structure.")
-
- (COMPILED-INDEXPR-HOOK
- nil
- :type (or null function)
- :computed T
- :comments "
- If non-NIL, contains the compiled functional version of the lambda form
- stored in INDEXPR-HOOK.")
-
- (DELEXPR-HOOK nil
- :type list
- :comments "
- If non-NIL, contains a lambda form. When an expression is successfully
- deleted from the DNET, if this is non-nil its compiled version (see next slot)
- is called on two arguments: the expression, and its dnet-terminal structure.")
-
- (COMPILED-DELEXPR-HOOK
- nil
- :type (or null function)
- :computed T
- :comments "
- If non-NIL, contains the compiled functional version of the lambda form
- stored in DELEXPR-HOOK.")
-
- (INFO-PLACE nil
- :type T
- :comments "
- The user may associate arbitrary information with the DNET by storing it
- in this slot, SETF accessable using DNET-INFO."))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; INTERNAL FUNCTIONS AND MACROS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Many of these are not needed by the user. To save space, the macros
- ;;; are not loaded in compiled files.
-
- (defun NOT-A-DOTTED-LIST (expr)
- "not-a-dotted-list <expr> [Function]
- Returns T iff there is no non-nil atomic CDR in <expr>."
- (declare (optimize (safety 1) (space 2) (speed 3)))
- (cond ((null expr) T)
- ((atom expr) T)
- ((and (cdr expr) (atom (cdr expr))) nil)
- (t (and (not-a-dotted-list (car expr))
- (not-a-dotted-list (cdr expr))))))
-
- ;;; Basic link operations.
- (eval-when (compile eval)
-
- (defmacro TRAVERSE-LINK (key link)
- ;; The cdr is the association list.
- `(assoc ,key (cdr ,link) :test #'equal)) ; (the list (cdr ,link)) but CCL has bug
-
- (defmacro ADD-LINK (key link)
- ;; Must return the link just created (not the link it is added to),
- ;; so construction may continue from the returned result.
- `(or (assoc ,key (cdr ,link) :test #'equal) ; (the list (cdr ,link)) but CCL has bug
- (let ((newlink (cons ,key nil)))
- (declare (cons newlink))
- (push newlink (cdr ,link)) ; (the list (cdr ,link)) but CCL has bug
- newlink)))
- )
-
- ;;;------------------------------------------
- ;;; Finding the node of something in the net.
-
- ;;; This is not eval-when eval compile because EXPR-INFO-INTERNAL, used by other
- ;;; DNET files, expands into a form which contain this. So it needs to be loaded.
-
- (defmacro FIND-TERMINAL-LINK (expr link)
- `(cond ((null ,link) nil)
- ((atom ,expr) (traverse-link ,expr ,link)) ; nil caught here ...
- ((list-find-terminal-link ,expr ,link)))) ; so never passed here ...
-
- (defun LIST-FIND-TERMINAL-LINK (list link)
- (declare (list list link) (optimize (safety 1) (space 2) (speed 3)))
- (do ((lptr list (cdr lptr))
- (curlink (traverse-link :begin-list link)))
- ((null lptr) (traverse-link :end-list curlink)) ; relies on list never nil
- (declare (list lptr curlink))
- (if (null curlink) (return nil))
- (setf curlink (find-terminal-link (car lptr) curlink))))
- (proclaim '(function list-find-terminal-link (list list) list))
-
- ;;;----------------------------------------------
- ;;; Inserting links to a new terminal in the net.
-
- (eval-when (compile eval)
- (defmacro LINK-TO-TERMINAL (expr link)
- `(if (atom ,expr)
- (add-link ,expr ,link)
- (list-link-to-terminal ,expr ,link))))
-
- (defun LIST-LINK-TO-TERMINAL (list link)
- ;; Iterates down list adding items.
- (declare (list list link) (optimize (safety 1) (space 2) (speed 3)))
- (do ((lptr list (cdr lptr))
- (curlink (add-link :begin-list link)))
- ((null lptr) (add-link :end-list curlink))
- (declare (list lptr curlink))
- (setf curlink (link-to-terminal (car lptr) curlink))))
- (proclaim '(function list-link-to-terminal (list list) list))
-
- ;;;-----------------------------------------
- ;;; Deleting links to a terminal in the net.
-
- ;;; Returns DNET-TERMINAL structure (not link) if it was there. Removes links
- ;;; which are no longer needed. That is, removes that sub-branch of the tree
- ;;; which ends in the terminal and is linear (has no branches other than the
- ;;; links to the terminal).
-
- (eval-when (compile eval)
- (defmacro UNLINK-RETURNING-TERMINAL (expr link)
- ;; Use of *terminal* and catch/throw simplifies the recursive function.
- `(let ((*terminal* nil))
- (declare (special *terminal*) (atom *terminal*))
- (catch :not-found
- (unlink-using-token-list (list-of-tokens ,expr) ,link)
- *terminal*))))
-
- ;; This was difficult to write until I resorted to using a list of tokens.
- ;; Now the pattern is simple. The tokens direct tree traversal. Two base
- ;; conditions: failure (throw to bypass return code) or success (record the
- ;; terminal and initiate pruning). Else, recurse on the next link, then
- ;; determine if that link is to be pruned by seeing if its subtree was. A
- ;; linear branch has the property that if you delete a link, the enclosing
- ;; alist goes to nil, since it only had one link in it (was linear). Thus
- ;; a test of whether the alist of the link is nil determines whether to
- ;; prune it, assuming the recursive pruning was done correctly in the link.
-
- (defun UNLINK-USING-TOKEN-LIST (tokens link)
- (declare (special *terminal*) (list tokens link)
- (optimize (safety 1) (space 2) (speed 3)))
- (cond
- ;; Failure: bypass return code.
- ((null link) (throw :not-found nil))
-
- ;; We have consumed the original expression. If success, save the terminal,
- ;; and initiate pruning by setting the alist of the link to nil. (This meets
- ;; the assumption of the tests for linearity to be made on the way back up.)
- ((null tokens)
- (if (not (atom (cdr link))) (throw :not-found nil)) ; atom = dnet-terminal
- (setf *terminal* (cdr link))
- (setf (cdr link) nil))
-
- ;; Still more traversing to do. Traverse the next token/link; recurse
- ;; from there; and prune the branch if it is linear on return. Test the
- ;; latter condition by seeing if it has a null alist.
- ((let ((next-link (traverse-link (car tokens) link))) ; next place
- (declare (list next-link))
- (unlink-using-token-list (cdr tokens) next-link) ; recursive work
- (if (null (cdr next-link)) ; now prune if linear
- (setf (cdr link) (delete next-link (cdr link))))))))
- (proclaim '(function unlink-using-token-list (list list) list))
-
- (defun LIST-OF-TOKENS (expr)
- ;; Constructs a list of atoms or :begin-list and :end-list keywords
- ;; which uniquely encodes the expression. These are the keys used
- ;; to index in a discrimination net down to the corresponding terminal.
- (declare (optimize (safety 1) (space 2) (speed 3)))
- (cond
- ((atom expr) (list expr))
- (t
- (nconc (list :begin-list)
- (reduce #'nconc (mapcar #'list-of-tokens expr))
- (list :end-list)))))
- (proclaim '(function list-of-tokens (T) list))
-
- ;;;--------------------
- ;;; Variable Primitives
-
- (defmacro DEFVARIABLE (sym)
- "defvariable <sym> [Macro]
- Interns the name of <sym> in the variable package ?, and exports it. Use
- to ensure the variable exists before using it in a pattern. For example,
- (defvariable x) lets us use ?:x as a variable in patterns."
- `(export (intern
- ,(if (stringp sym) sym (symbol-name sym))
- *?-package*) *?-package*))
-
- (defmacro VARIABLE-P (thing)
- "variable-p <thing> [Macro]
- Returns T iff <thing> is a variable (symbol in the ? package)."
- `(and (symbolp ,thing)
- (eq (symbol-package ,thing) *?-package*)))
-
- (defun VARIABLES-IN-PATTERN (pattern)
- "variables-in-pattern <pattern> [Function]
- Returns a list of variables occuring in <pattern>."
- (declare (optimize (safety 1) (space 2) (speed 3)))
- (cond ((null pattern) nil)
- ((atom pattern) (if (variable-p pattern) (list pattern)))
- (T
- (nunion (variables-in-pattern (car pattern))
- (variables-in-pattern (cdr pattern))))))
- (proclaim '(function variables-in-pattern (T) list))
-
- (defun PATTERN-P (expr)
- "pattern-p <expr> [Function]
- Returns non-NIL value iff <expr> is or contains a variable."
- (if (atom expr)
- (variable-p expr)
- (or (pattern-p (first expr)) (pattern-p (rest expr)))))
- (proclaim '(function pattern-p (T) T))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Internal Expression Functions
-
- (defun INDEXPR-INTERNAL (expr dnet info)
- (declare (symbol dnet) (optimize (safety 1) (space 2) (speed 3)))
- (let* ((dnet-struct (sm:gets 'dnet dnet))
- (terminal-link (link-to-terminal expr (dnet-link dnet-struct)))
- (dnet-terminal (cdr terminal-link))
- (added (not dnet-terminal))
- (indexpr-hook (dnet-compiled-indexpr-hook dnet-struct)))
- (declare (type dnet dnet-struct) (list terminal-link) (symbol added)
- (type (or null dnet-terminal) dnet-terminal)
- (type (or null function) indexpr-hook))
- (when added
- (setf dnet-terminal (make-dnet-terminal expr info))
- (setf (cdr terminal-link) dnet-terminal)
- (if indexpr-hook (funcall indexpr-hook expr dnet-terminal)))
- (values added dnet-terminal)))
- (proclaim '(function indexpr-internal (t symbol t) (values boolean dnet-terminal)))
-
- (defun GETEXPR-INTERNAL (expr dnet)
- (declare (symbol dnet) (optimize (safety 1) (space 2) (speed 3)))
- (let* ((dnet-struct (sm:gets 'dnet dnet))
- (dnet-terminal (cdr (find-terminal-link expr (dnet-link dnet-struct)))))
- (declare (type dnet dnet-struct)
- (type (or null dnet-terminal) dnet-terminal))
- (values
- (if dnet-terminal (dnet-terminal-expr dnet-terminal))
- dnet-terminal)))
- (proclaim '(function getexpr-internal (t symbol) (values t dnet-terminal)))
-
- (defun DELEXPR-INTERNAL (expr dnet)
- (declare (symbol dnet) (optimize (safety 1) (space 2) (speed 3)))
- (let* ((dnet-struct (sm:gets 'dnet dnet))
- (dnet-terminal (unlink-returning-terminal expr (dnet-link dnet-struct)))
- (delexpr-hook (dnet-compiled-delexpr-hook dnet-struct)))
- (declare (type dnet dnet-struct)
- (type (or null dnet-terminal) dnet-terminal)
- (type (or null function) delexpr-hook))
- (when dnet-terminal
- (if delexpr-hook (funcall delexpr-hook expr dnet-terminal))
- dnet-terminal)))
- (proclaim '(function delexpr-internal (t symbol) (or null dnet-terminal)))
-
- ;;; NOTE this is used by RULE package.
-
- (defmacro EXPR-INFO-INTERNAL (expr dnet)
- `(dnet-terminal-info
- (cdr (find-terminal-link ,expr
- (dnet-link (the dnet
- (sm:gets 'dnet ,dnet)))))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; INTERNAL PATTERN MATCHING CODE
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (eval-when (compile eval)
- (defmacro CONTINUATIONS-AFTER-PATTERN-VARIABLE (link)
- ;; Returns a list of links reached by consuming one item from <link>.
- ;; The car of <link> has already been consumed, so we are concerned
- ;; with the branches represented by its alist.
- `(let ((alist (cdr ,link)))
- (unless (atom alist)
- (do ((alptr alist (cdr alptr))
- (continuations nil))
- ((null alptr) continuations)
- (declare (list alptr continuations))
- (if (eq (caar alptr) :begin-list)
- (setf continuations
- (nconc continuations
- (the list (continuations-after-consuming-list (car alptr)))))
- (push (car alptr) continuations)))))))
-
- (defun CONTINUATIONS-AFTER-CONSUMING-LIST (link)
- ;; Called when <link> has :begin-list as its car, or when processing dotted
- ;; variables, its job is to return a list of links whose cars are the matching
- ;; :end-lists.
- (declare (list link) (optimize (safety 1) (space 2) (speed 3)))
- (do ((frontier (cdr link))
- (continuations nil))
- ((null frontier) continuations)
- (declare (list frontier continuations))
- ;; A frontier is a point where we are searching a branch for a corresponding
- ;; :end-list. As long as no new :begin-lists are encountered, the frontier
- ;; is extended just by cdr-ing until :end-list found. Otherwise recurse.
- (do ((fptr frontier (cdr fptr))
- (new-frontier nil))
- ((null fptr) (setf frontier new-frontier))
- (declare (list fptr new-frontier))
- (cond
- ;; Found matching endlist: record continuation.
- ((eq (caar fptr) :end-list) (push (car fptr) continuations))
- ;; New list: get recursive continuations, take one step on each to
- ;; knock off recursive :end-lists, yielding new frontier links.
- ((eq (caar fptr) :begin-list)
- (dolist (rcont-link (the list (continuations-after-consuming-list (car fptr))))
- (declare (list rcont-link))
- (setf new-frontier
- (append new-frontier (cdr rcont-link))))) ; 1st copied, not 2nd.
- ;; Otherwise move one step down list.
- (T (setf new-frontier
- (append new-frontier (cdar fptr))))))))
- (proclaim '(function continuations-after-consuming-list (list) list))
-
- (eval-when (compile eval)
- (defmacro TRAVERSE-LINKS (key frontier)
- ;; Given a search frontier of links, expands that frontier to the links
- ;; reached via the key, pruning any links that can't be traversed.
- `(mapcan
- #'(lambda (link &aux (new-link nil))
- (declare (list link new-link))
- (if (and (listp (cdr link))
- (setf new-link (traverse-link ,key link)))
- (list new-link)
- nil))
- ,frontier)))
-
- (defun PATTERN-MATCH-LINKS (remaining-pattern frontier)
- ;; Frontier is a list of links called "continuations": these are the
- ;; locations in the DNET from which we match to the remaining-pattern.
- ;; This function performs a search, where remaining-pattern directs
- ;; where to go. The frontier expands when multiple matches to a variable
- ;; in the ARGUMENT is found. A new list of links (the final frontier)
- ;; is returned.
- (declare (list frontier) (optimize (safety 1) (space 2) (speed 3)))
- (cond
-
- ;; Remaining pattern is a variable: return list of continuations reached
- ;; by consuming one item from each of the current continuations.
- ((variable-p remaining-pattern)
- (do ((lptr frontier (cdr lptr))
- (new-frontier (list :head)))
- ((null lptr) (cdr new-frontier))
- (declare (list lptr new-frontier))
- (nconc new-frontier
- (the list (continuations-after-pattern-variable (car lptr))))))
-
- ;; Remaining pattern is an atom: replace each link in the frontier
- ;; with the link reached after traversing the branch for that atom,
- ;; or eliminate the link's branch from the search frontier if no match.
- ((atom remaining-pattern)
- (traverse-links remaining-pattern frontier))
-
- ;; Remaining pattern is a list. Traverse a :begin-list token, and
- ;; iterate over the items in the pattern list, extending the search
- ;; frontier along matching paths until the list is consumed. Return
- ;; the frontier reached by traversing :end-list links from the result.
- ;; Exception: if the pattern ends in a dotted variable, consume what
- ;; ever is required to finish the list in the DNET.
- (T
- (setf frontier (traverse-links :begin-list frontier))
- (do ((pptr remaining-pattern (cdr pptr)))
- ((atom pptr)
- (if pptr
- (if (variable-p pptr)
- (mapcan #'continuations-after-consuming-list frontier)
- (error "[DNET:MATCH-PATTERN] Nonvariable dotted ending is illegal:~%~S"
- remaining-pattern))
- (traverse-links :end-list frontier)))
- (declare (list pptr))
- (setf frontier (pattern-match-links (car pptr) frontier))))))
- (proclaim '(function pattern-match-links (t list) list))
-
- (defun EXPRESSION-MATCH-LINKS (remaining-expression frontier)
- ;; Frontier is a list of links, the locations in the DNET from which
- ;; we match to the remaining-expression. This function performs a
- ;; search from frontier, with remaining-expression directing where to go.
- ;; A node in the frontier expands when multiple matches to variables and
- ;; constants in the NET are found. A list of links (the "final frontier")
- ;; is returned.
- (declare (list frontier) (optimize (safety 1) (space 2) (speed 3)))
- (let ((new-frontier (list :head)))
- (declare (list new-frontier))
-
- ;; Each link on the frontier represents the last token consumed (the car)
- ;; and where we are now (the cdr). If the cdr is an alist, it has links
- ;; whose cars are to be matched to the remaining-expression. If any of
- ;; these cars is a variable, the entire remaining expression is consumed.
- ;; Thus, we may return without further processing the places which matching
- ;; to these variables get us -- namely, the cdrs of the variable links.
- (dolist (flink frontier)
- (declare (list flink))
- (nconc new-frontier
- (mapcan #'(lambda (link)
- (declare (list link))
- (if (variable-p (car link)) (list link) nil))
- (cdr flink))))
-
- ;; Now we have to do literal matching, to be combined with the variable
- ;; continuations computed above.
- (cond
-
- ;; Atomic expression: For each frontier link, search the continuations
- ;; available in its cdr for a continuing link with an EQUAL atom. These
- ;; are added to the links to be returned without further processing.
- ((atom remaining-expression)
- (dolist (flink frontier)
- (declare (list flink))
- (nconc new-frontier
- (mapcan #'(lambda (link)
- (declare (list link))
- (if (equal (car link) remaining-expression)
- (list link)
- nil))
- (cdr flink)))))
-
- ;; List expression: In addition to the variable-generated continuations
- ;; computed above, we need to recurse to get the literal matches. Do this
- ;; by expanding the frontier in parallel with iterating over the list.
- (T
- (setf frontier (traverse-links :begin-list frontier))
- (do ((eptr remaining-expression (cdr eptr)))
- ((null eptr) (nconc new-frontier (traverse-links :end-list frontier)))
- (declare (list eptr))
- (setf frontier (expression-match-links (car eptr) frontier)))))
-
- ;; Return all the continuations we have collected.
- (cdr new-frontier)))
- (proclaim '(function expression-match-links (t list) list))
-
- (defun MATCH-LINKS (remaining-pattern frontier)
- ;; This is the combined version, which processes variables in both the
- ;; remaining-pattern and the dnet.
- (declare (list frontier) (optimize (safety 1) (space 2) (speed 3)))
- (let ((new-frontier (list :head)))
- (declare (list new-frontier))
-
- ;; Each link on the frontier represents the last token consumed (the car)
- ;; and where we are now (the cdr). If the cdr is an alist, it has links
- ;; whose cars are to be matched to the remaining-pattern. If any of
- ;; these cars is a variable, the entire remaining expression is consumed.
- ;; Thus, we may return without further processing the places which matching
- ;; to these variables get us -- namely, the cdrs of the variable links.
- (dolist (flink frontier)
- (declare (list flink))
- (nconc new-frontier
- (mapcan #'(lambda (link)
- (declare (list link))
- (if (variable-p (car link)) (list link) nil))
- (cdr flink))))
-
- ;; Now we have to do matching to variables in the pattern, and to literals,
- ;; to be combined with the dnet variable continuations computed above.
- (cond
-
- ;; Remaining pattern is a variable: add list of continuations reached
- ;; by consuming one item from each of the current continuations.
- ((variable-p remaining-pattern)
- (do ((lptr frontier (cdr lptr))
- (continuations (list :head)))
- ((null lptr) (nconc new-frontier (cdr continuations)))
- (declare (list lptr continuations))
- (nconc continuations
- (the list (continuations-after-pattern-variable (car lptr))))))
-
- ;; Atomic expression: For each frontier link, search the continuations
- ;; available in its cdr for a continuing link with an EQUAL atom. These
- ;; are added to the links to be returned without further processing.
- ((atom remaining-pattern)
- (dolist (flink frontier)
- (declare (list flink))
- (nconc new-frontier
- (mapcan #'(lambda (link)
- (declare (list link))
- (if (equal (car link) remaining-pattern)
- (list link)
- nil))
- (cdr flink)))))
-
- ;; List expression: In addition to the variable-generated continuations
- ;; computed above, we need to recurse to get the literal matches. Do this
- ;; by expanding the frontier in parallel with iterating over the list.
- (T
- (setf frontier (traverse-links :begin-list frontier))
- (do ((eptr remaining-pattern (cdr eptr)))
- ((null eptr)
- (nconc new-frontier (the list (traverse-links :end-list frontier))))
- (declare (list eptr))
- (setf frontier (match-links (car eptr) frontier)))))
-
- ;; Return all the continuations we have collected. Duplicates arise when
- ;; variables match to variables.
- (delete-duplicates (cdr new-frontier))))
- (proclaim '(function match-links (t list) list))
-
- ;;; --------------------------------------------------------------------------
- ;;; NOTE the next three functions are "internal" but used by the RULE package.
- ;;; In particular, previous-bindings is used for consistency filtering.
-
- (defun MATCH-PATTERN-INTERNAL (pattern dnet previous-bindings)
- (declare (symbol dnet) (inline pattern-match-links) (list previous-bindings)
- (optimize (safety 1) (space 2) (speed 3)))
- (let ((matching nil) (bindings nil))
- (declare (list matching bindings))
- (dolist (link (the list
- (pattern-match-links
- pattern
- (list (dnet-link
- (the dnet (sm:gets 'dnet dnet)))))))
- (declare (list link))
- (multiple-value-bind
- (success-p binding)
- (bind-vars pattern (dnet-terminal-expr (cdr link)) previous-bindings)
- (declare (symbol success-p) (list binding))
- (when success-p
- (push (dnet-terminal-expr (cdr link)) matching)
- (push binding bindings))))
- (values matching bindings)))
- (proclaim '(function match-pattern-internal (t symbol list) (values list list)))
-
- (defun MATCH-EXPRESSION-INTERNAL (expression dnet previous-bindings)
- (declare (inline expression-match-links) (symbol dnet) (list previous-bindings)
- (optimize (safety 1) (space 2) (speed 3)))
- (let ((matching nil) (bindings nil))
- (declare (list matching bindings))
- (dolist (link (delete-duplicates ; in case expression has variables.
- (the list
- (expression-match-links
- expression
- (list (dnet-link
- (the dnet (sm:gets 'dnet dnet))))))))
- (declare (list link))
- (multiple-value-bind
- (success-p binding)
- (bind-vars (dnet-terminal-expr (cdr link)) expression previous-bindings)
- (declare (symbol success-p) (list binding))
- (when success-p
- (push (dnet-terminal-expr (cdr link)) matching)
- (push binding bindings))))
- (values matching bindings)))
- (proclaim '(function match-expression-internal (t symbol list) (values list list)))
-
- (defun MATCH-INTERNAL (pattern dnet)
- (declare (symbol dnet) (inline match-links)
- (optimize (safety 1) (space 2) (speed 3)))
- (let ((matching nil) (bindings-1 nil) (bindings-2 nil))
- (declare (list matching bindings-1 bindings-2))
- (dolist (link (the list
- (match-links pattern
- (list (dnet-link
- (the dnet (sm:gets 'dnet dnet)))))))
- (declare (list link))
- (multiple-value-bind
- (success-p binding-1 binding-2)
- (unify pattern (dnet-terminal-expr (cdr link)) nil nil)
- (declare (symbol success-p) (list binding-1 binding-2))
- (when success-p
- (push (dnet-terminal-expr (cdr link)) matching)
- (push binding-1 bindings-1)
- (push binding-2 bindings-2))))
- (values matching bindings-1 bindings-2)))
- (proclaim '(function match-internal (t symbol) (values list list list)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; EXPORTED OPERATIONS ON DNETs
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (defun MAKE-DNET (name &key indexpr-hook delexpr-hook info)
- "make-dnet <name> &key <indexpr-hook> <delexpr-hook> <info> [Function]
- Returns a new (empty) discrimination net. A name is generated unless
- a symbol <name> is provided. <Indexpr-hook> and <delexpr-hook> are
- assumed to be lambda forms of two arguments which may be compiled.
- These functions are applied to an expression and its corresponding
- DNET-TERMINAL the first time it is indexed into or deleted from the
- DNET, respectively. If the optional <info> is provided, the DNET's
- associated information is initialized to this value."
- (check-type name symbol)
- (check-type indexpr-hook list)
- (check-type delexpr-hook list)
- (let ((dnet-name (or name (gentemp "DNET-"))))
- (declare (symbol dnet-name))
- (create-dnet dnet-name indexpr-hook delexpr-hook info)
- (setf (dnet-link (the dnet (sm:gets 'dnet dnet-name)))
- (list dnet-name))
- (when indexpr-hook
- (setf (dnet-compiled-indexpr-hook
- (the dnet (sm:gets 'dnet dnet-name)))
- (compile nil indexpr-hook)))
- (when delexpr-hook
- (setf (dnet-compiled-delexpr-hook
- (the dnet (sm:gets 'dnet dnet-name)))
- (compile nil delexpr-hook)))
- dnet-name))
- (proclaim '(function make-dnet (symbol &key list list t) symbol))
-
- (defmacro DNET-INFO (dnet)
- "dnet-info <dnet> [Macro]
- Setf-able access to the information associated with <dnet>."
- `(dnet-info-place (the dnet (sm:gets 'dnet ,dnet))))
-
- (defun RESET-DNET (dnet &key (indexpr-hook nil)
- (delexpr-hook nil)
- (info nil info-supplied))
- "reset-dnet <dnet> &key <indexpr-hook> <delexpr-hook> <info> [Function]
- Empties an existing discrimination net. Also enables one to modify
- the hooks and associated info. See MAKE-DNET."
- (check-type dnet symbol)
- (check-type indexpr-hook list)
- (check-type delexpr-hook list)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:RESET-DNET] ~S is not a known DNET." dnet)
- (setf (dnet-link (the dnet (sm:gets 'dnet dnet))) (list dnet))
- (when indexpr-hook
- (setf (dnet-compiled-indexpr-hook (the dnet (sm:gets 'dnet dnet)))
- (compile nil indexpr-hook)))
- (when delexpr-hook
- (setf (dnet-compiled-delexpr-hook (the dnet (sm:gets 'dnet dnet)))
- (compile nil delexpr-hook)))
- (when info-supplied (setf (dnet-info dnet) info))
- dnet)
-
- (defun DESTROY-DNET (dnet)
- "destroy-dnet <dnet> [Function]
- Destroys and undefines the entire <dnet>."
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:DESTROY-DNET] Unknown DNET ~S" dnet)
- (sm:destroys 'dnet dnet))
- (proclaim '(function destroy-dnet (symbol) symbol))
-
- (defvariable expr) ; used below
-
- (defun ALL-EXPRESSIONS (dnet)
- "all-expressions <dnet> [Function]
- Returns a list of all expressions in the indicated <dnet>. The outer
- list is constructed fresh and may be hacked. This is time consuming."
- (declare (inline pattern-match-links) (optimize (safety 1) (space 2) (speed 3)))
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:ALL-EXPRESSIONS] Unknown DNET ~S" dnet)
- ;; I don't use match-pattern-internal since that conses up un-needed bindings.
- (mapcar #'(lambda (link)
- (declare (list link))
- (dnet-terminal-expr (cdr link)))
- (the list
- (pattern-match-links
- '?:expr
- (list (dnet-link
- (the dnet (sm:gets 'dnet dnet))))))))
- (proclaim '(function all-expressions (symbol) list))
-
- (defun MAP-DNET-TERMINALS (f dnet)
- "map-dnet-terminals <f> <dnet> [Function]
- Maps <f> across dnet-terminals in the indicated <dnet>. Returns NIL."
- (declare (inline pattern-match-links) (optimize (safety 1) (space 2) (speed 3)))
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:MAP-DNET-TERMINALS] Unknown DNET ~S" dnet)
- ;; I don't use match-pattern-internal since that conses up un-needed bindings.
- ;; It is important to ensure that an <f> that calls DELEXPR won't crash this.
- (map nil #'(lambda (link) (declare (list link)) (funcall f (cdr link)))
- (the list
- (pattern-match-links
- '?:expr
- (list (dnet-link
- (the dnet (sm:gets 'dnet dnet))))))))
- (proclaim '(function map-dnet-terminals (function symbol) null))
-
- (defun SAVE-DNET (dnet path &optional
- (write-in-package *dnet-package*) &aux (vars ()))
- "save-dnet <dnet> <path> &optional (write-in-package :dnet) [Function]
- Saves expressions required to recreate <dnet> to a file at <path>.
- Returns the <path>. The file is written in package <write-in-package>,
- or DNET if the optional argument is unspecified. All associated info
- is also saved. Give <vars> a list of known variables. This function
- is slow."
- (declare (list vars))
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:SAVE-DNET] Unknown DNET ~S" dnet)
- (check-type path (or simple-string pathname))
- (when (not (typep write-in-package 'package))
- (setf write-in-package (find-package (string write-in-package))))
- (assert write-in-package (write-in-package)
- "[DNET:SAVE-DNET] Bad package specified.")
- (let ((*package* write-in-package) (*print-pretty* t) (*print-escape* t)
- (*print-circle* nil) (*print-case* :upcase) (*print-array* t)
- #+:ccl (ccl::*print-structure* t)
- (dnet-struct (sm:gets 'dnet dnet))
- (dnet-terminals
- (mapcar #'cdr
- (pattern-match-links
- '?:expr
- (list (dnet-link
- (the dnet (sm:gets 'dnet dnet))))))))
- (declare (type dnet dnet-struct) (list dnet-terminals)
- (optimize (safety 1) (space 2) (speed 3)))
- (with-open-file (stream path
- :direction :output
- :if-exists :supersede)
- (format stream ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Discrimination Net ~S~%;;; Saved by SAVE-DNET ~A~%;;; On ~A, a ~A"
- dnet
- (multiple-value-bind
- (second minute hour date month year)
- (get-decoded-time)
- (declare (integer second minute hour date month year))
- (format nil "~2,'0D-~A-~2,'0D ~2,'0D:~2,'0D:~2,'0D"
- date
- (case month
- ((1) "Jan") ((2) "Feb") ((3) "Mar") ((4) "Apr")
- ((5) "May") ((6) "Jun") ((7) "Jul") ((8) "Aug")
- ((9) "Sep") ((10) "Oct") ((11) "Nov") ((12) "Dec"))
- (- year 1900)
- hour minute second))
- (machine-instance)
- (machine-type))
- (format stream "~%~%(in-package ~S)~%" (package-name write-in-package))
- (dolist (term dnet-terminals)
- (declare (simple-vector term)) ; since dnet-terminal is this :type
- ;; Variables may be in INFO as well as EXPR.
- (setf vars
- (nunion (nunion (variables-in-pattern (dnet-terminal-expr term))
- (variables-in-pattern (dnet-terminal-info term)))
- vars)))
- (dolist (v (sort vars #'(lambda (s1 s2)
- (string< (symbol-name s1) (symbol-name s2)))))
- (format stream "~%(dnet:defvariable ?::~A)" v))
- (format stream "~%~%(dnet:make-dnet '~S~
- ~% :indexpr-hook '~A~
- ~% :delexpr-hook '~A~
- ~% :info '~S)~%"
- dnet
- (prin1-to-string (dnet-indexpr-hook dnet-struct))
- (prin1-to-string (dnet-delexpr-hook dnet-struct))
- (dnet-info-place dnet-struct))
- (dolist (term dnet-terminals)
- (declare (simple-vector term)) ; since dnet-terminal is this :type
- (format stream "~%(dnet::indexpr-internal '~S '~S '~S)"
- (dnet-terminal-expr term) dnet (dnet-terminal-info term)))
- (format stream "~&~%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; EOF"))
- path))
- (proclaim '(function save-dnet
- (symbol (or simple-string pathname)
- &optional (or null string package))
- pathname))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; EXPORTED OPERATIONS ON EXPRESSIONS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (defun INDEXPR (expr dnet &optional info)
- "indexpr <expr> <dnet> &optional <info> [Function]
- Ensures that <expr> is stored in <dnet>. If the <expr> was not already
- in <dnet>, initializes the associated information to <info> (if it was
- provided), and calls compiled-new-expr-hook (if non-nil) on <expr> and
- its dnet-terminal. The latter allows application-specific processing of
- new expressions. Returns two values: the first is a predicate, T iff <expr>
- was newly added by this call; and the second is the dnet-terminal structure."
- (declare (inline indexpr-internal))
- (assert (not-a-dotted-list expr) (expr)
- "[DNET:INDEXPR] Dotted lists not allowed in DNET: ~S" expr)
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:INDEXPR] Unknown DNET ~S" dnet)
- (indexpr-internal expr dnet info))
- (proclaim '(function indexpr (t symbol &optional t) (values boolean dnet-terminal)))
-
- (defun GETEXPR (expr dnet)
- "getexpr <expr> <dnet> [Function]
- Use to query whether <expr> has been stored in <dnet>, and to obtain the
- name of its dnet-terminal. Returns two values: the expression originally
- stored in <dnet> (and is EQUAL to <expr>), and the dnet-terminal structure.
- Both values are Nil if <expr> is not found. Variables are not processed."
- (declare (inline getexpr-internal))
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet) "[DNET:GETEXPR] Unknown DNET ~S" dnet)
- (getexpr-internal expr dnet))
- (proclaim '(function getexpr (t symbol) (values t dnet-terminal)))
-
- (defun DELEXPR (expr dnet)
- "delexpr <expr> <dnet> [Function]
- Deletes the expression <expr> from <dnet>, calling DELEXPR-HOOK if it
- is defined for the DNET. Returns a DNET-TERMINAL iff it was deleted."
- (declare (inline delexpr-internal))
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:DELEXPR] Unknown DNET ~S" dnet)
- (delexpr-internal expr dnet))
- (proclaim '(function delexpr (t symbol) (or null dnet-terminal)))
-
- ;;; Had to be a function for safety (multiple evaluation, ...) but need setf.
-
- (defun EXPR-INFO (expr dnet)
- "expr-info <expr> <dnet> [Function]
- Setf-able access to the information associated with <expr> in <dnet>."
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:EXPR-INFO] Unknown DNET ~S" dnet)
- (expr-info-internal expr dnet))
- (proclaim '(function expr-info (t symbol) t))
-
- ;;; Internal users will setf the internal macro directly, but externals need this.
- (defun set-expr-info (expr dnet value)
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:EXPR-INFO] Unknown DNET ~S" dnet)
- (setf (expr-info-internal expr dnet) value))
- (defsetf expr-info set-expr-info)
- (proclaim '(function set-expr-info (t symbol t) t))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; EXPORTED OPERATIONS ON PATTERNS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (defun MATCH-PATTERN (pattern dnet)
- "match-pattern <pattern> <dnet> [Function]
- For retrieving all expressions matching a pattern which may contain
- variables. Returns two values: a list of all expressions in <dnet>
- matching the <pattern>, and a list of respective unifications. The
- latter is a list of lists containing pairs (<pat-var> . <dnet-exp>)
- representing the binding of <pat-var> to <dnet-exp>, a component of
- the corresponding returned expression. Variables in the dnet are
- treated as constants. A special facility provided only by this
- function is dotted variables: any sublist of <pattern> may end in
- a dot followed by a variable. This is like &rest binding."
- (declare (inline match-pattern-internal))
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:MATCH-PATTERN] Unknown DNET ~S" dnet)
- (match-pattern-internal pattern dnet nil))
- (proclaim '(function match-pattern (t symbol) (values list list)))
-
- (defun MATCH-EXPRESSION (expression dnet)
- "match-expression <expression> <dnet> [Function]
- For retrieving all patterns (which may contain variables) matching an
- expression. Returns two values: a list of all patterns in <dnet>
- matching the <expression>, and a list of respective unifications. The
- latter is a list of lists containing pairs (<exp-part> . <pat-var>)
- representing the binding of <pat-var> to <exp-part>, a component of
- <expression>. Variables in <expression> are treated as constants."
- (declare (inline match-expression-internal))
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:MATCH-EXPRESSION] Unknown DNET ~S" dnet)
- (match-expression-internal expression dnet nil))
- (proclaim '(function match-expression (t symbol) (values list list)))
-
- (defun MATCH (pattern dnet)
- "match <pattern> <dnet> [Function]
- For retrieving all patterns unifying with a pattern: variables in both
- are processed. Returns three values: a list of all patterns in <dnet>
- unifying with the <pattern>, a list of bindings of variables in <pattern>
- to those in the matched patterns, and a list of bindings of variables in
- the matched patterns to those in <pattern>. The latter two values are
- lists of lists containing pairs (<var> . <exp>), each representing the
- binding of <var> to <exp> (ditto). Note this is less efficient than
- MATCH-PATTERN and MATCH-EXPRESSION, and should only be used when needed.
- See also documentation for UNIFY."
- (declare (inline match-internal))
- (check-type dnet symbol)
- (assert (sm:gets 'dnet dnet) (dnet)
- "[DNET:MATCH] Unknown DNET ~S" dnet)
- (match-internal pattern dnet))
- (proclaim '(function match (t symbol) (values list list list)))
-
- (defun BIND-VARS (pattern expression bindings)
- "bind-vars <pattern> <expression> <bindings> [Function]
- Variables are processed in <pattern> but treated as atoms in <expression>.
- <Bindings> should be existing bindings (usually nil). Dotted endings in
- <pattern> are assumed to be variables, and are processed. Returns two
- values: T or NIL to flag whether the pattern matches the expression, and
- a list of bindings which achieve this matching."
- (declare (optimize (safety 1) (space 2) (speed 3)))
- (cond
- ;; Variable: See if a previous binding for the variable exists. If so,
- ;; the expression must be equal. Otherwise add the new binding.
- ((variable-p pattern)
- (let ((binding (assoc pattern bindings)))
- (declare (list binding))
- (if binding
- (if (equal (cdr binding) expression)
- (values T bindings)
- (values nil nil))
- (values T (push (cons pattern expression) bindings)))))
-
- ;; If the pattern is a non-variable atom, then it must be equal to
- ;; the expression, because the matcher tests for this.
- ((atom pattern) (values T bindings))
-
- ;; Otherwise both are lists: iterate over items in lists in parallel
- ;; (avoiding double recursion, empirically faster), seeing if the
- ;; corresponding items bind, and adding any necessary bindings.
- (T ; Allegro Common Lisp won't return two values if DO put first.
- (do ((pattern-ptr pattern (cdr pattern-ptr))
- (expression-ptr expression (cdr expression-ptr)))
- ;; Matcher guarantees the lists are the same length; don't have
- ;; to test (null expression-ptr).
- ((null pattern-ptr) (values T bindings))
- (declare (list pattern-ptr expression-ptr))
- (if (atom pattern-ptr) ; won't be nil
- (let ((binding (assoc pattern-ptr bindings)))
- (declare (list binding))
- (if binding
- (if (not (equal (cdr binding) expression-ptr))
- (return (values nil nil)) ; blow out of loop
- (setf pattern-ptr nil
- expression-ptr nil)) ; exit normally next pass
- (progn
- (push (cons pattern-ptr expression-ptr) bindings)
- (setf pattern-ptr nil))))
- (multiple-value-bind
- (success-p new-bindings)
- (bind-vars (car pattern-ptr) (car expression-ptr) bindings)
- (declare (symbol success-p) (list new-bindings))
- (if success-p
- (setf bindings new-bindings)
- (return (values nil nil)))))))))
- (proclaim '(function bind-vars (t t list) (values boolean list)))
-
- (defun SUBSTITUTE-BINDINGS (bindings pattern)
- "substitute-bindings <bindings> <pattern> [Function]
- Given <bindings> is an association list as returned by one of the match
- functions, creates an expression from <pattern> where all the variables
- have been replaced by their bindings. New list structure is used.
- Only makes one pass -- see substitute-transitive-bindings if variables
- may be bound to each other."
- (check-type bindings list)
- (labels ((substitute-bindings-r (bindings pattern &aux binding)
- (declare (list bindings binding)
- (optimize (safety 1) (space 2) (speed 3)))
- (cond ((null pattern) nil)
- ((atom pattern)
- (if (and (variable-p pattern)
- (setf binding (assoc pattern bindings)))
- (cdr binding)
- pattern))
- (t
- (cons (substitute-bindings-r bindings (car pattern))
- (substitute-bindings-r bindings (cdr pattern)))))))
- (substitute-bindings-r bindings pattern)))
- (proclaim '(function substitute-bindings (list t) t))
-
- (defun SUBSTITUTE-TRANSITIVE-BINDINGS (bindings pattern)
- "substitute-bindings <bindings> <pattern> [Function]
- Given <bindings> is an association list as returned by one of the match
- functions, creates an expression from <pattern> where all the variables
- have been replaced by their bindings. New list structure is used.
- Makes as many passes are needed to eliminate transitivities which may
- be returned by UNIFY when both patterns have variables, such as
- ((?:y . 3) (?:x . ?:y)) which unifies (?:x ?:x) and (?:y 3)."
- (check-type bindings list)
- (let ((changed nil))
- (labels ((substitute-bindings-r (bindings pattern &aux binding)
- (declare (list bindings binding)
- (optimize (safety 1) (space 2) (speed 3)))
- (cond ((null pattern) nil)
- ((atom pattern)
- (if (and (variable-p pattern)
- (setf binding (assoc pattern bindings)))
- (progn (setq changed t) (cdr binding))
- pattern))
- (t
- (cons (substitute-bindings-r bindings (car pattern))
- (substitute-bindings-r bindings (cdr pattern)))))))
- (let ((new-pattern (substitute-bindings-r bindings pattern)))
- (if changed
- (substitute-transitive-bindings bindings new-pattern)
- new-pattern)))))
- (proclaim '(function substitute-transitive-bindings (list t) t))
-
- (defun UNIFY (pattern-1 pattern-2 &optional (bindings-1 nil) (bindings-2 nil))
- "unify <pattern-1> <pattern-2> &optional <bindings-1> <bindings-2> [Function]
- Given two list/atom patterns (each of which may contain variables), and
- a (usually nil) set of initial bindings, returns three values:
- 1. T or NIL to flag whether the patterns unify;
- 2. Bindings of variables in <pattern-1> to elements of <pattern-2>;
- 3. Bindings of variables in <pattern-2> to elements of <pattern-1>.
- (The flag is returned first so UNIFY can be used as a predicate. Since
- unification can succeed with no bindings, the other values will not serve
- this function.) Example:
- (unify '(a ?:x c) '(a (b) ?:y)) ==> T; ((?:X B)); ((?:Y . C))
- Transitivity of bindings is not used for simplification, e.g. you could
- get back T; ((?:X . ?:Y)); ((?:Y . C)). Modified from version by Ken Forbus.
- This function is logically general, and hence checks for a variety of
- conditions. It may be inefficient for specialized tasks: I recommend
- writing a specialized version if any assumptions may be made about the
- patterns to be unified."
- (declare (list bindings-1 bindings-2) (optimize (safety 1) (space 2) (speed 3)))
-
- (cond ((equal pattern-1 pattern-2) (values t bindings-1 bindings-2))
- ((variable-p pattern-1)
- (unify-variable-1 pattern-1 pattern-2 bindings-1 bindings-2))
- ((variable-p pattern-2)
- (unify-variable-2 pattern-1 pattern-2 bindings-1 bindings-2))
- ((or (not (listp pattern-1)) (not (listp pattern-2)))
- (values nil nil nil))
- (t
- (multiple-value-bind
- (success new-bindings-1 new-bindings-2)
- (unify (first pattern-1) (first pattern-2) bindings-1 bindings-2)
- (if success
- (unify (rest pattern-1) (rest pattern-2) new-bindings-1 new-bindings-2)
- (values nil nil nil))))))
- (proclaim '(function unify (T T &optional list list) (values boolean list list)))
-
- ;;; These three were lexically scoped in UNIFY, but the VAX compile warned of
- ;;; "not declared or defined" (I suppose you have to declare lexically scoped
- ;;; functions as such?). Besides, tests show lexical scoping is slower!!!
-
- (defun FREE-IN? (variable pattern bindings)
- ;; Determines whether a variable is free in a pattern.
- (cond ((null pattern) t)
- ((eq variable pattern) nil)
- ((variable-p pattern)
- (free-in? variable (utils:image pattern bindings) bindings))
- ((not (listp pattern)) t)
- ((free-in? variable (first pattern) bindings)
- (free-in? variable (rest pattern) bindings))))
-
- (defun UNIFY-VARIABLE-1 (variable pattern bindings-1 bindings-2 &aux value)
- ;; Deals with case where pattern-1 is a variable.
- (cond ((setq value (utils:image variable bindings-1))
- (unify value pattern bindings-1 bindings-2))
- ((free-in? variable pattern bindings-2)
- (values t (cons (cons variable pattern) bindings-1) bindings-2))
- (t (values nil nil nil))))
-
- (defun UNIFY-VARIABLE-2 (pattern variable bindings-1 bindings-2 &aux value)
- ;; Deals with case where pattern-2 is a variable.
- (cond ((setq value (utils:image variable bindings-2))
- (unify pattern value bindings-1 bindings-2))
- ((free-in? variable pattern bindings-1)
- (values t bindings-1 (cons (cons variable pattern) bindings-2)))
- (t (values nil nil nil))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (provide :DNET)
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; EOF
-
-