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