git.fiddlerwoaroof.com
Raw Blame History
;;; pattern.scm -- cfn processing of pattern-related AST structures
;;;
;;; author :  Sandra Loosemore
;;; date   :  27 Feb 1992
;;;
;;; This file contains specialized CFN walkers for lambda, case, and valdef
;;; structures.



;;;=====================================================================
;;; Top-level walkers
;;;=====================================================================


;;; The calls to remember-context are so an appropriate error message
;;; can be produced for pattern-matching failures.

(define-walker-method cfn lambda (object)
  (remember-context object
    (do-cfn-lambda (lambda-pats object) (lambda-body object))))


(define-walker-method cfn case (object)
  (remember-context object
    (do-cfn-case
      (case-exp object)
      (case-alts object))))




;;; Valdefs are always processed as a list.

(define (cfn-valdef-list list-of-valdefs)
  (if (null? list-of-valdefs)
      '()
      (nconc (cfn-valdef (car list-of-valdefs))
	     (cfn-valdef-list (cdr list-of-valdefs)))))

(define (cfn-valdef object)
  (remember-context object
    (if (null? (single-fun-def-args (car (valdef-definitions object))))
	;; This is a pattern binding.
	(do-cfn-pattern-def-top object)
	;; This is a function binding.
	;; Branch on single-headed/multi-headed definition.
	(list (add-dict-params
	        object
		(if (null? (cdr (valdef-definitions object)))
		    (do-cfn-function-def-simple object)
		    (do-cfn-function-def-general object))))
      )))


;;; This adds the dictionary parameters needed by the type system.  A valdef
;;; structure has a dictionary-args field which contains the variables to be
;;; bound to dicationary arguments.

(define (add-dict-params original-valdef generated-valdef)
  (let ((vars (valdef-dictionary-args original-valdef)))
    (when (not (null? vars))
      (let* ((sfd  (car (valdef-definitions generated-valdef)))
	     (rhs  (car (single-fun-def-rhs-list sfd)))
	     (exp  (guarded-rhs-rhs rhs))
	     (pats (map (function **var-pat/def) vars)))
	(if (is-type? 'lambda exp)
	    (setf (lambda-pats exp)
		  (nconc pats (lambda-pats exp)))
	    (setf (guarded-rhs-rhs rhs)
		  (**lambda/pat pats exp))))))
  generated-valdef)


;;;=====================================================================
;;; Lambda rewriting
;;;=====================================================================


;;; For lambda, make all the argument patterns into var pats.
;;; Rewrite the body as a CASE to do any more complicated pattern
;;; matching.
;;; The CFN output for lambda is a modified lambda expression with
;;; all var-pats as arguments.

(define (do-cfn-lambda pats body)
  (let ((new-args  '())
	(new-vars  '())
	(new-pats  '()))
    (dolist (p pats)
      (typecase p
	(wildcard-pat
	  (push (**var-pat/def (create-temp-var 'arg)) new-args))
        (var-pat
	  (push p new-args))
	(as-pat
	  (let ((var  (var-ref-var (as-pat-var p))))
	    (push (**var-pat/def var) new-args)
	    (push (**var/def var) new-vars)
	    (push (as-pat-pattern p) new-pats)))
	(else
	  (let ((var  (create-temp-var 'arg)))
	    (push (**var-pat/def var) new-args)
	    (push (**var/def var) new-vars)
	    (push p new-pats)))))
    (setf new-args (nreverse new-args))
    (setf new-vars (nreverse new-vars))
    (setf new-pats (nreverse new-pats))
    (**lambda/pat
      new-args
      (cond ((null? new-vars)
	     ;; No fancy pattern matching necessary.
	     (cfn-ast-1 body))
	    ((null? (cdr new-vars))
	     ;; Exactly one argument to match on.
	     (do-cfn-case (car new-vars)
			  (list (**alt/simple (car new-pats) body))))
	    (else
	     ;; Multiple arguments to match on.
	     (do-cfn-case-tuple
	       new-vars
	       (list (**alt/simple (**tuple-pat new-pats) body))))
	    ))))


;;;=====================================================================
;;; Function definitions
;;;=====================================================================


;;; The output of the CFN for function definitions is a simple 
;;; valdef which binds a variable to a lambda expression.


;;; The simple case:  there is only one set of arguments.

(define (do-cfn-function-def-simple object)
  (let* ((pat    (valdef-lhs object))
	 (sfd    (car (valdef-definitions object))))
    (**valdef/pat
      pat
      (do-cfn-lambda
        (single-fun-def-args sfd)
	(rewrite-guards-and-where-decls
	  (single-fun-def-where-decls sfd)
	  (single-fun-def-rhs-list sfd)
	  '#f)))))


;;; The general case:  generate new variables as the formal parameters 
;;; to the resulting lambda, then use case to do the pattern matching.

(define (do-cfn-function-def-general object)
  (let ((pat   (valdef-lhs object))
	(vars  (map (lambda (p)
		      (declare (ignore p))
		      (create-temp-var 'arg))
		    (single-fun-def-args (car (valdef-definitions object)))))
	(alts  (map (lambda (sfd)
		      (**alt (**tuple-pat (single-fun-def-args sfd))
			     (single-fun-def-rhs-list sfd)
			     (single-fun-def-where-decls sfd)))
		    (valdef-definitions object))))
    (**valdef/pat
      pat
      (**lambda/pat
        (map (function **var-pat/def) vars)
	(if (null? (cdr vars))
	    ;; one-argument case
	    (do-cfn-case (**var/def (car vars)) alts)
	    ;; multi-argument case
	    (do-cfn-case-tuple (map (function **var/def) vars) alts))))
    ))


;;;=====================================================================
;;; Case
;;;=====================================================================


;;; For case, add failure alt, then call helper function to generate
;;; pattern matching tests.
;;; The CFN output for case is a case-block construct.

(define (do-cfn-case exp alts)
  (setf alts
	(append alts
		(list (**alt/simple (**wildcard-pat) (make-failure-exp)))))
  (let ((list-of-pats  	(map (lambda (a) (list (alt-pat a))) alts)))
    (if (is-type? 'var-ref exp)
	(match-pattern-list (list exp) list-of-pats alts)
	(let ((temp  (create-temp-var 'cfn)))
	  (**let (list (**valdef/def temp (cfn-ast-1 exp)))
		 (match-pattern-list
		   (list (**var/def temp))
		   list-of-pats
		   alts)))
      )))



;;; Here's a special case, for when the exp being matched is a tuple
;;; of var-refs and all the alts also have tuple pats.

(define (do-cfn-case-tuple exps alts)
  (setf alts
	(append alts
		(list
		  (**alt/simple
		    (**tuple-pat
		      (map (lambda (e) (declare (ignore e)) (**wildcard-pat))
			   exps))
		    (make-failure-exp)))))
  (match-pattern-list
    exps
    (map (lambda (a) (pcon-pats (alt-pat a))) alts)
    alts))


(define (match-pattern-list exps list-of-pats alts)
  (let ((block-name  (gensym "PMATCH")))
    (**case-block
      block-name
      (map (lambda (a p) (match-pattern exps p a block-name))
	   alts
	   list-of-pats))))


;;; Produce an exp that matches the given alt against the exps.
;;; If the match succeeds, it will return-from the given block-name.

(define (match-pattern exps pats alt block-name)
  (if (null pats)
      ;; No more patterns to match.
      ;; Return an exp that handles the guards and where-decls.
      (cfn-ast-1
        (rewrite-guards-and-where-decls
	  (alt-where-decls alt) (alt-rhs-list alt) block-name))
      ;; Otherwise dispatch on type of first pattern.
      (let ((pat  (pop pats))
	    (exp  (pop exps)))
	(funcall
	  (typecase pat
	    (wildcard-pat (function match-wildcard-pat))
	    (var-pat      (function match-var-pat))
	    (pcon         (function match-pcon))
	    (as-pat       (function match-as-pat))
	    (irr-pat      (function match-irr-pat))
	    (const-pat    (function match-const-pat))
	    (plus-pat     (function match-plus-pat))
	    (list-pat     (function match-list-pat))
	    (else         (error "Unrecognized pattern ~s." pat)))
	  pat
	  exp
	  pats
	  exps
	  alt
	  block-name))))




;;; Wildcard patterns add no pattern matching test.
;;; Just recurse on the next pattern to be matched.

(define (match-wildcard-pat pat exp pats exps alt block-name)
  (declare (ignore pat exp))
  (match-pattern exps pats alt block-name))


;;; A variable pattern likewise does not add any test.  However,
;;; a binding of the variable to the corresponding exp must be added.

(define (match-var-pat pat exp pats exps alt block-name)
  (push (**valdef/pat pat exp)
	(alt-where-decls alt))
  (match-pattern exps pats alt block-name))


;;; Pcons are the hairy case because they may have subpatterns that need
;;; to be matched.
;;; If there are subpats and the exp is not a var-ref, make a let binding.
;;; If the con is a tuple type, there is no need to generate a test
;;; since the test would always succeed anyway.
;;; Do not generate let bindings here for subexpressions; do this lazily
;;; if and when necessary.

(define (match-pcon pat exp pats exps alt block-name)
  (let* ((var?    (is-type? 'var-ref exp))
	 (var     (if var?
		      (var-ref-var exp)
		      (create-temp-var 'conexp)))
	 (con     (pcon-con pat))
	 (arity   (con-arity con))
	 (alg     (con-alg con))
	 (tuple?  (algdata-tuple? alg))
	 (subpats (pcon-pats pat))
	 (subexps '()))
    (dotimes (i arity)
      (push (**sel con (**var/def var) i) subexps))
    (setf exps (nconc (nreverse subexps) exps))
    (setf pats (append subpats pats))
    (let ((tail  (match-pattern exps pats alt block-name)))
      (when (not tuple?)
	(setf tail
	      (**and-exp (**is-constructor (**var/def var) con) tail)))
      (when (not var?)
	(setf tail
	      (**let (list (**valdef/def var (cfn-ast-1 exp))) tail)))
      tail)))


;;; For as-pat, add a variable binding.
;;; If the expression being matched is not already a variable reference,
;;; take this opportunity to make the let binding.  Otherwise push the
;;; let-binding onto the where-decls.

(define (match-as-pat pat exp pats exps alt block-name)
  (let ((var    (var-ref-var (as-pat-var pat)))
	(subpat (as-pat-pattern pat)))
    (if (is-type? 'var-ref exp)
	(begin
	  (push (**valdef/def var (**var/def (var-ref-var exp)))
		(alt-where-decls alt))
	  (match-pattern
	    (cons exp exps)
	    (cons subpat pats)
	    alt
	    block-name))
	(**let (list (**valdef/def var (cfn-ast-1 exp)))
	       (match-pattern
		 (cons (**var/def var) exps)
		 (cons subpat pats)
		 alt
		 block-name)))))


;;; An irrefutable pattern adds no test to the pattern matching,
;;; but adds a pattern binding to the where-decls.

(define (match-irr-pat pat exp pats exps alt block-name)
  (let ((subpat  (irr-pat-pattern pat)))
    (push (**valdef/pat subpat exp) (alt-where-decls alt))
    (match-pattern exps pats alt block-name)))


;;; A const pat has a little piece of code inserted by the typechecker
;;; to do the test.
;;; For matches against string constants, generate an inline test to match 
;;; on each character of the string.

(define (match-const-pat pat exp pats exps alt block-name)
  (let ((const  (const-pat-value pat)))
    (**and-exp 
      (if (is-type? 'string-const const)
	  (let ((string  (string-const-value const)))
	    (if (string=? string "")
		(**is-constructor exp (core-symbol "Nil"))
		(**app (**var/def (core-symbol "primStringEq")) const exp)))
	  (cfn-ast-1 (**app (const-pat-match-fn pat) exp)))
      (match-pattern exps pats alt block-name))
    ))


;;; Plus pats have both a magic test and a piece of code to
;;; make a binding in the where-decls.  Make a variable binding
;;; for the exp if it's not already a variable.

(define (match-plus-pat pat exp pats exps alt block-name)
  (let* ((var?  (is-type? 'var-ref exp))
	 (var   (if var? (var-ref-var exp) (create-temp-var 'plusexp))))
    (push (**valdef/pat (plus-pat-pattern pat)
			(**app (plus-pat-bind-fn pat) (**var/def var)))
	  (alt-where-decls alt))
    (let ((tail  (match-pattern exps pats alt block-name)))
      (setf tail
	    (**and-exp
	      (cfn-ast-1 (**app (plus-pat-match-fn pat) (**var/def var)))
	      tail))
      (if var?
	  tail
	  (**let (list (**valdef/def var exp)) tail)))))


;;; Rewrite list pats as pcons, then process recursively.

(define (match-list-pat pat exp pats exps alt block-name)
  (let ((newpat  (rewrite-list-pat (list-pat-pats pat))))
    (match-pattern
      (cons exp exps)
      (cons newpat pats)
      alt
      block-name)))

(define (rewrite-list-pat subpats)
  (if (null? subpats)
      (**pcon/def (core-symbol "Nil") '())
      (**pcon/def (core-symbol ":")
		  (list (car subpats)
			(rewrite-list-pat (cdr subpats))))))




;;;=====================================================================
;;; Pattern definitions
;;;=====================================================================


(define (do-cfn-pattern-def-top object)
  (typecase (valdef-lhs object)
    (var-pat
      ;; If the pattern definition is a simple variable assignment, it
      ;; may have dictionary parameters that need to be messed with.
      ;; Complicated pattern bindings can't be overloaded in this way.
      (list (add-dict-params object (do-cfn-pattern-def-simple object))))
    (irr-pat
      ;; Irrefutable patterns are redundant here.
      (setf (valdef-lhs object) (irr-pat-pattern (valdef-lhs object)))
      (do-cfn-pattern-def-top object))
    (wildcard-pat
     ;; Wildcards are no-ops.
     '())
    (pcon
     ;; Special-case because it's frequent and general case creates
     ;; such lousy code
     (do-cfn-pattern-def-pcon object))
    (else
      (do-cfn-pattern-def-general object))))


;;; Do a "simple" pattern definition, e.g. one that already has a
;;; var-pat on the lhs.

(define (do-cfn-pattern-def-simple object)
  (let* ((pat  (valdef-lhs object))
	 (sfd  (car (valdef-definitions object)))
	 (exp  (rewrite-guards-and-where-decls
		 (single-fun-def-where-decls sfd)
		 (single-fun-def-rhs-list sfd)
		 '#f)))
  (**valdef/pat pat (cfn-ast-1 exp))))


;;; Destructure a pcon.
;;; Note that the simplified expansion is only valid if none of
;;; the subpatterns introduce tests.  Otherwise we must defer to
;;; the general case.

(define (do-cfn-pattern-def-pcon object)
  (let* ((pat     (valdef-lhs object))
	 (subpats (pcon-pats pat)))
    (if (every (function irrefutable-pat?) subpats)
	(let* ((con     (pcon-con pat))
	       (arity   (con-arity con))
	       (alg     (con-alg con))
	       (tuple?  (algdata-tuple? alg))
	       (temp    (create-temp-var 'pbind))
	       (result  '()))
	  (dotimes (i arity)
	    (setf result
		  (nconc result
			 (do-cfn-pattern-def-top 
			   (**valdef/pat (pop subpats)
					 (**sel con (**var/def temp) i))))))
	  (if (null? result)
	      '()
	      (let* ((sfd   (car (valdef-definitions object)))
		     (exp   (cfn-ast-1
			      (rewrite-guards-and-where-decls
			        (single-fun-def-where-decls sfd)
				(single-fun-def-rhs-list sfd)
				'#f))))
		(when (not tuple?)
		  (let ((temp1  (create-temp-var 'cfn)))
		    (setf exp
			  (**let (list (**valdef/def temp1 exp))
				 (**if (**is-constructor (**var/def temp1) con)
				       (**var/def temp1)
				       (make-failure-exp))))))
		(cons (**valdef/def temp exp) result))))
	(do-cfn-pattern-def-general object))))



;;; Turn a complicated pattern definition into a list of simple ones.
;;; The idea is to use case to match the pattern and build a tuple of
;;; all the values which are being destructured into the pattern
;;; variables.

(define (do-cfn-pattern-def-general object)
  (multiple-value-bind (new-pat vars new-vars)
      (copy-pattern-variables (valdef-lhs object))
    (if (not (null? vars))
	(let* ((sfd      (car (valdef-definitions object)))
	       (exp      (rewrite-guards-and-where-decls
			   (single-fun-def-where-decls sfd)
			   (single-fun-def-rhs-list sfd)
			   '#f))
	       (arity    (length vars)))
	  (if (eqv? arity 1)
	      (list (**valdef/def
		      (var-ref-var (car vars))
		      (do-cfn-case
		        exp
			(list (**alt/simple new-pat (car new-vars))))))
	      (let ((temp     (create-temp-var 'pbind))
		    (bindings '()))
		(dotimes (i arity)
		  (push (**valdef/def (var-ref-var (pop vars))
				      (**tuple-sel arity i (**var/def temp)))
			bindings))
		(cons (**valdef/def
		        temp
			(do-cfn-case
			  exp
			  (list (**alt/simple new-pat (**tuple/l new-vars)))))
		      bindings))))
	'())))



;;; Helper function for above.
;;; All the variables in the pattern must be replaced with temporary
;;; variables.  

(define (copy-pattern-variables pat)
  (typecase pat
    (wildcard-pat
      (values pat '() '()))
    (var-pat
      (let ((new  (create-temp-var (var-ref-name (var-pat-var pat)))))
	(values (**var-pat/def new)
		(list (var-pat-var pat))
		(list (**var/def new)))))
    (pcon
      (multiple-value-bind (new-pats vars new-vars)
	  (copy-pattern-variables-list (pcon-pats pat))
	(values (**pcon/def (pcon-con pat) new-pats)
		vars
		new-vars)))
    (as-pat
      (let ((new  (create-temp-var (var-ref-name (as-pat-var pat)))))
	(multiple-value-bind (new-pat vars new-vars)
	    (copy-pattern-variables (as-pat-pattern pat))
	  (values
	    (make as-pat
		  (var (**var/def new))
		  (pattern new-pat))
	    (cons (as-pat-var pat) vars)
	    (cons (**var/def new) new-vars)))))
    (irr-pat
      (multiple-value-bind (new-pat vars new-vars)
	  (copy-pattern-variables (irr-pat-pattern pat))
	(values
	  (make irr-pat (pattern new-pat))
	  vars
	  new-vars)))
    (const-pat
      (values pat '() '()))
    (plus-pat
      (multiple-value-bind (new-pat vars new-vars)
	  (copy-pattern-variables (plus-pat-pattern pat))
	(values
	  (make plus-pat
		(pattern new-pat)
		(k (plus-pat-k pat))
		(match-fn (plus-pat-match-fn pat))
		(bind-fn (plus-pat-bind-fn pat)))
	  vars
	  new-vars)))
    (list-pat
      (multiple-value-bind (new-pats vars new-vars)
	  (copy-pattern-variables-list (list-pat-pats pat))
	(values (make list-pat (pats new-pats))
		vars
		new-vars)))
    (else
      (error "Unrecognized pattern ~s." pat))))

(define (copy-pattern-variables-list pats)
  (let ((new-pats  '())
	(vars      '())
	(new-vars  '()))
    (dolist (p pats)
      (multiple-value-bind (p v n) (copy-pattern-variables p)
	(push p new-pats)
	(setf vars (nconc vars v))
	(setf new-vars (nconc new-vars n))))
    (values (nreverse new-pats)
	    vars
	    new-vars)))



;;;=====================================================================
;;; Helper functions for processing guards and where-decls
;;;=====================================================================

;;; Process guards and where-decls into a single expression.
;;; If block-name is non-nil, wrap the exp with a return-from.
;;; If block-name is nil, add a failure exp if necessary.
;;; Note that this does NOT do the CFN traversal on the result or
;;; any part of it.

(define (rewrite-guards-and-where-decls where-decls rhs-list block-name)
  (if (null? where-decls)
      (rewrite-guards rhs-list block-name)
      (**let where-decls
	     (rewrite-guards rhs-list block-name))))

(define (rewrite-guards rhs-list block-name)
  (if (null? rhs-list)
      (if block-name
	  (**con/def (core-symbol "False"))
	  (make-failure-exp))
      (let* ((rhs     (car rhs-list))
	     (guard   (guarded-rhs-guard rhs))
	     (exp     (guarded-rhs-rhs rhs)))
	(when block-name
	  (setf exp (**return-from block-name exp)))
	(cond ((is-type? 'omitted-guard (guarded-rhs-guard (car rhs-list)))
	       exp)
	      ((and block-name (null? (cdr rhs-list)))
	       (**and-exp guard exp))
	      (else
	       (**if guard
		     exp
		     (rewrite-guards (cdr rhs-list) block-name)))
	      ))))


(define (make-failure-exp)
  (let ((c  (dynamic *context*)))
    (**abort
      (if (not c)
	  "Pattern match failed."
	  (let* ((stuff  (ast-node-line-number c))
		 (line   (source-pointer-line stuff))
		 (file   (source-pointer-file stuff)))
	    (if (and (is-type? 'valdef c)
		     (is-type? 'var-pat (valdef-lhs c)))
		(format
		  '#f
		  "Pattern match failed in function ~a at line ~s in file ~a."
		  (valdef-lhs c) line file)
		(format
		  '#f
		  "Pattern match failed at line ~s in file ~a."
		  line file)))))))