;;; -*- Mode:LISP; Package:LISP-INTERNALS; Base:10; Readtable:CL -*-
;;;
;;; LAMBDA-LIST.LISP
;;;
;;; Lambda list parsers
;;;
;;; The first set of procedures in this file parses lambda lists into their different
;;; kinds of parameters.
;;;
;;; The second set of procedures parses &OPTIONAL, &KEY, and &AUX parameters into
;;; their parts (var, initform, svar, etc.)


;(export '(
;	  parse-aux-parameter
;	  parse-key-parameter
;	  parse-lambda-list
;	  parse-let-binding
;	  parse-optional-parameter
;	  ))


;; reverting to a version which loads on the lambda #16
;; LAMBDA-LIST does not appear in  *COLD-FILES* *WARM-LOADED-FILES*  *HOT-LOADED-FILES*
;; and therefore is no longer loaded on the K
;; --pfc  5/3/88


;***note!! this tries to execute both on the lambda and on the K.  Unfortunately,
;  this causes extreme lossage.  To win for the lambda, put global before
;  MULTIPLE-VALUE-LIST PAIRLIS RPLACA and VALUES-LIST.  We will normally
;  leave the file without these so as to win for the K.
;MULTIPLE-VALUE-LIST has a definite problem: it is a macro for the k in LISP-INTERNALS
; which is incompatible with the Lambda.  There is probably no real conflict with the others.

(defun parse-lambda-list (lambda-list keyword-list)
  "Parse LAMBDA-LIST, only allowing the keywords given in KEYWORD-LIST.  Use the
bogus keyword :REQUIRED to indicate required arguments.  Return as many values as
there are elements in KEYWORD-LIST, each value being the corresponding part of
LAMBDA-LIST.
   In plainer English: If the KEYWORD-LIST is '(:REQUIRED :OPTIONAL :REST), then
three values will be returned: the required, optional, and rest args in LAMBDA-LIST.
If there are any other kinds of arguments, an error will be raised.
   The order of keywords in KEYWORD-LIST doesn't bother the parser, but duplications
will."
  (let* 
    ((keywords   '(:REQUIRED :OPTIONAL :REST :KEY :ALLOW-OTHER-KEYS :AUX :BODY :WHOLE :ENVIRONMENT))
     (parameters (global:multiple-value-list (moby-parse-lambda-list lambda-list)))
     (pairlist   (global:pairlis keywords parameters))
     (results    '()))
    (global:dolist (keyword keyword-list)
      (let ((pair (assoc keyword pairlist)))
	(if pair
	    (progn (push (cdr pair) results)
		   (global:rplaca pair 'DONE))
	    (ferror "~S is an invalid or duplicated keyword in PARSE-LAMBDA-LIST." keyword))))
    (global:dolist (pair pairlist)
      (unless (or (eq (car pair) 'DONE)
		  (eq (cdr pair) NIL))
	(ferror "~S keyword not permitted in lambda-list ~S." (car pair) lambda-list)))
    (global:values-list (reverse results))))
    

;;; MOBY PARSER
;;;
;;; This does all the work.  It takes a lambda list and returns nine values.
;;;
;;; (1) List of required parameters
;;; (2) List of optional parameters
;;; (3) Rest parameter, or NIL if not present
;;; (4) List of key parameters
;;; (5) T if &allow-other-keys appears, NIL otherwise
;;; (6) List of aux parameters
;;; (7) Body parameter, or NIL if not present
;;; (8) Whole parameter, or NIL if not present
;;; (9) Environment parameter, or NIL if not present

;;; The &whole parameter must be the first element of the lambda-list.

(defun moby-parse-lambda-list (lambda-list)
  (let (required optional rest key allow-other-keys aux body whole environment)
    (labels 
      (
       
       ;;; We parse the lambda-list sequentially by using this function only.
       (next-parameter ()
	 (cond ((null lambda-list) nil)
	       ((symbolp lambda-list)
		(raise-lambda-list-error "Dotted tail notation for &REST arg is illegal in Common LISP."))
	       (t
		(let ((p (first lambda-list)))
		  (if (null p)
		      (raise-lambda-list-error "~S is not allowed in a lambda list." p)
		      p)))))

       (pop-parameter ()    
	 (prog1 (next-parameter)
		(setq lambda-list (cdr lambda-list))))

       (more-parameters-p () (not (null lambda-list)))

       (available-parameter-p ()
	 (and (more-parameters-p)
	      (not (lambda-list-keyword-p (next-parameter)))))

       (set-environment-if-its-there ()
	 (when (eq (next-parameter) '&environment)
	   (setq environment (pop-keyword-and-one-argument))))

       (pop-keyword-and-one-argument ()
	 (let ((parameter (pop-parameter)))
	   (if (more-parameters-p)
	       (let ((argument (pop-parameter)))
		 (if (lambda-list-keyword-p argument)
		     (raise-lambda-list-error "An argument must follow a ~S in lambda list." parameter)
		     argument))
	       (raise-lambda-list-error "No arguments follow ~S in lambda list." parameter))))

       (pop-until-next-keyword ()
	 (if (available-parameter-p)
	     (cons (pop-parameter) (pop-until-next-keyword))
	     '()))

       (test-parameter (what if-true)
	 (let ((parameter (next-parameter)))
	   (cond ((eq parameter what) (funcall if-true))
		 ((eq parameter '&allow-other-keys)
		  (raise-lambda-list-error "~S in illegal position." parameter))
		 (t nil))))
       )

      (test-parameter '&whole #'(lambda () (setq whole (pop-keyword-and-one-argument))))
      (set-environment-if-its-there)

      (setq required (pop-until-next-keyword))
      (set-environment-if-its-there)

      (test-parameter '&optional
	#'(lambda ()
	    (pop-parameter)
	    (setq optional (pop-until-next-keyword))))
      (set-environment-if-its-there)

      (let ((p (next-parameter)))
	(when (member p '(&rest &body &allow-other-keys))
	  (case p
	    (&rest (setq rest (pop-keyword-and-one-argument)))
	    (&body (setq body (pop-keyword-and-one-argument)))
	    (&allow-other-keys (raise-lambda-list-error "~s in illegal position." p)))
	  (when (available-parameter-p)
	    (raise-lambda-list-error "Only one argument may follow a ~S in the lambda list." p ))))
      (set-environment-if-its-there)

      (test-parameter '&key
	#'(lambda ()
	    (pop-parameter)
	    (setq key (pop-until-next-keyword))
	    (when (eq (next-parameter) '&allow-other-keys)
	      (pop-parameter)
	      (setq allow-other-keys T)
	      (when (available-parameter-p)
		(raise-lambda-list-error
		  "An argument may not follow a &allow-other-keys in the lambda list.")))))
      (set-environment-if-its-there)

      (test-parameter '&aux
		     #'(lambda ()
			 (pop-parameter)
			 (setq aux (pop-until-next-keyword))))
      (set-environment-if-its-there)

      (if (more-parameters-p)
	  (raise-lambda-list-error "I've lost.  I didn't expect to see a ~s" (next-parameter))
	  (values required optional rest key allow-other-keys aux body whole environment)))))

(defun lambda-list-keyword-p (sym)
  (member sym '(&optional &rest &key &allow-other-keys &aux &body &whole &environment)))

(defun raise-lambda-list-error (message &rest args)
  (error "~?" message args))


;;; PARAMETER PARSERS
;;;
;;; These procedures parse &OPTIONAL, &KEY, and &AUX parameters within a lambda list.
;;; Each takes two arguments: the parameter, and an optional error procedure to call if the
;;; parameter does not have a valid syntax.
;;;
;;; PARSE-OPTIONAL-PARAMETER returns three values: the variable, the initform, and the svar
;;; in the optional parameter.
;;;
;;; PARSE-KEY-PARAMETER returns four values: the variable, the initform, the svar, and the
;;; keyword in the key parameter.  
;;;
;;; PARSE-AUX-PARAMETER returns two values: the variable and the value in the aux parameter.
;;;
;;; All values not supplied in the parameter default to NIL, except for the keyword in a
;;; key parameter, which defaults to the parameter's variable interned in the keyword
;;; package.
;;;
;;; A fourth procedure, PARSE-LET-BINDING, parses a let binding.  It returns two values:
;;; the variable and the value.  It is almost equivalent to PARSE-AUX-PARAMETER.


(defun parse-optional-parameter (optional-parameter &optional error-proc)	
  "Return three values: VARIABLE, INITFORM, and SVAR"
  (let*
    ((err-proc (if error-proc
		   error-proc
		   #'(lambda ()
		       (ferror "~S is not valid syntax for an optional parameter."
			       optional-parameter))))
     (parsed-expr (parse-expr optional-parameter
			      '(:var
				(:var)
				(:var :initform)
				(:var :initform :svar))
			      err-proc
			      '(:initform))))
    (values (getf parsed-expr :var)
	    (getf parsed-expr :initform)
	    (getf parsed-expr :svar))))


(defun parse-key-parameter (key-parameter &optional error-proc)
  "Return four values: VARIABLE, INITFORM, SVAR, and KEYWORD"
  (let*
    ((err-proc (if error-proc
		   error-proc
		   #'(lambda ()
		       (ferror "~S is not valid syntax for a key parameter."
			       key-parameter))))
     (parsed-expr (parse-expr key-parameter
			      '(:var
				(:var)
				(:var :initform)
				(:var :initform :svar)
				((:keyword :var))
				((:keyword :var) :initform)
				((:keyword :var) :initform :svar))
			      err-proc
			      '(:initform)))
     (var      (getf parsed-expr :var))
     (initform (getf parsed-expr :initform))
     (svar     (getf parsed-expr :svar))
     (keyword  (getf parsed-expr :keyword)))
    (values var
	    initform
	    svar
	    (if keyword
		keyword
		(intern (symbol-name var) (global:find-package 'keyword))))))


(defun parse-aux-parameter (aux-parameter &optional error-proc)
  "Return two values: VAR and VALUE"
  (let*
    ((err-proc (if error-proc
		   error-proc
		   #'(lambda ()
		       (ferror "~S is not valid syntax for an &AUX parameter."
			       aux-parameter))))
     (parsed-expr (parse-expr aux-parameter
			      '(:var
				(:var)
				(:var :value))
			      err-proc
			      '(:value))))
    (values (getf parsed-expr :var)
	    (getf parsed-expr :value))))


(defun parse-let-binding (let-binding &optional error-proc)
  "Return two values: VAR and VALUE"
  (let*
    ((err-proc (if error-proc
		   error-proc
		   #'(lambda ()
		       (ferror "~S is not valid syntax for a let binding."
			       let-binding))))
     (parsed-expr (parse-expr let-binding
			      '(:var
				(:var)
				(:var :value))
			      err-proc
			      '(:value))))
    (values (getf parsed-expr :var)
	    (getf parsed-expr :value))))


;;; Pattern matcher.
;;;
;;; EXPR is the expression to parse,
;;; PATTERN-LIST is a list of allowed patterns
;;; ERROR-PROC is a procedure to call if none of the patterns match
;;; MATCH-ANYTHING-LIST is a list of atoms in pattern-list that match anything
;;;
;;; Returns a list of the form (pattern-atom-1 matched-object-1 pattern-atom-2 ...)
;;; using the first pattern on PATTERN-LIST that matches EXPR.
;;;
;;; Examples:
;;;   EXPR = ((a b) c)
;;;   pattern = ((:foo :bar) :baz)
;;;   result = (:foo a :bar b :baz c)
;;;
;;;   EXPR = ((a b) c)
;;;   pattern = (:foo :bar)
;;;   result = (:foo (a b) :bar c) if MATCH-ANYTHING-LIST contains :foo
;;;            call to ERROR-PROC otherwise

(defun parse-expr (expr pattern-list error-proc match-anything-list)
  (when (null pattern-list)
    (funcall error-proc))
  (let ((trial-match (pattern-match (car pattern-list) expr match-anything-list)))
    (if trial-match
	trial-match
	(parse-expr expr (cdr pattern-list) error-proc match-anything-list))))
  
(defun pattern-match (pattern expr match-anything-list)
  (cond
    ((and (symbolp pattern) (member pattern match-anything-list))
     (list pattern expr))
    ((and (null pattern) (null expr))
     'nil-match)
    ((or (null pattern) (null expr))
     nil)
    ((and (symbolp pattern) (symbolp expr))
     (list pattern expr))
    ((and (consp pattern) (consp expr))
     (let ((car-match (pattern-match (car pattern) (car expr) match-anything-list))
	   (cdr-match (pattern-match (cdr pattern) (cdr expr) match-anything-list)))
       (if (and car-match cdr-match)
	   (if (eq cdr-match 'nil-match)
	       car-match
	       (append car-match cdr-match))
	   nil)))
    (t
     nil)))