4e987026 |
;;; 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)))))))
|