git @ Cat's Eye Technologies TPiS / master crit.scm
master

Tree @master (Download .tar.gz)

crit.scm @masterraw · history · blame

;
; crit.scm - critical argument locator
; Total Procedures in Scheme, May 2006, Chris Pressey
; For license information, see the file LICENSE in this directory.
;

;
; Given an abstract environment and the test expression of an 'if',
; return the environment which should apply to the 'then' expression
; branch of the 'if'.  For example, if the test is '(zero? n)', our
; new environment is a copy of the given environment, but with the
; name 'n' now bound to 'crit-nat' because n is available as a critical
; natural number argument inside the 'then' branch of this if expression.
;
(define-total make-then-branch-env
  (lambda (env test-expr-rep)
    (cond
      ((is-call? test-expr-rep)
        (let ((proc-name (car test-expr-rep))
              (proc-args (cdr test-expr-rep)))
          (cond
            ((eq? proc-name 'acyclic?)
              (extend-env env (car proc-args) 'list))
            ((and (eq? proc-name '>=) (eq? (cadr proc-args) 0))
              (extend-env env (car proc-args) 'nat))
            ((and (eq? proc-name 'zero?) (eq? (get-env env (car proc-args)) 'nat))
              (extend-env env (car proc-args) 'crit-nat))
            ((and (eq? proc-name 'null?) (eq? (get-env env (car proc-args)) 'list))
              (extend-env env (car proc-args) 'crit-list))
            (else
                env))))
        (else
          env))))

;
; 'Update' the env to reflect the information accrued in args,
; after name changes to variables (let).
;
; OTOH, if renaming has taken place, we need to retrieve the
; bindings and re-associate the types with the previous names,
; aka "back propogate" the types.
;
; XXX still TODO?
;
(define-total back-propogate-crit
  (lambda (lower-env upper-env)
    lower-env))

(define-total merge-crit-envs
  (lambda (env1 env2 acc-env)
    (if (acyclic? env1)
      (cond
        ((null? env1)
          acc-env)
        (else
          (let* ((pair  (car env1))
                 (rest  (cdr env1))
                 (var   (car pair))
                 (type1 (cdr pair))
                 (type2 (get-env env2 var)))
            ;
            ; Note that this glosses over the possibility that the
            ; var is 'crit-nat' in one environment and 'crit-list' in
            ; the other, which, absurd as it is, can happen.
            ;
            (cond
              ((or (eq? type1 'crit-nat) (eq? type2 'crit-nat))
                (merge-crit-envs rest env2 (extend-env acc-env var 'crit-nat)))
              ((or (eq? type1 'crit-list) (eq? type2 'crit-list))
                (merge-crit-envs rest env2 (extend-env acc-env var 'crit-list)))
              (else
                (merge-crit-envs rest env2 acc-env)))))))))

;
; Given the representation and name of a procedure, find its
; critical arguments.  The critical arguments are the arguments
; that take on a predictable final value exactly when the
; procedure returns.
;
; We find critical arguments by first finding critical variables,
; then relating these variables to the arguments they came from.
;
; We find critical variables by creating new environments on
; each branch of a conditional statement, and noticing if a
; variable takes on a final value in any branch that terminates
; (rather than recursing.)
;
(define-total find-crit-expr
  (lambda (expr-rep proc-names env)
    (if (acyclic? expr-rep)
      (cond
        ;
        ; This branch is used to convince the totality checker that
        ; the expr-rep data structure really is well-founded, even
        ; though it will never in practice be taken.
        ;
        ((null? expr-rep)
          env)
        ((is-let? expr-rep)
          (let* ((body-rep   (caddr expr-rep))           ; get-let-body
                 (in-let-env (find-crit-expr body-rep proc-names env)))
            (back-propogate-crit in-let-env env)))
        ;
        ; A variable is critical if it has a critical type in either environment
        ; of an if expression.
        ;
        ; Note that this is the only place where we extend the
        ; environment.
        ;
        ((is-if? expr-rep)
          (let* ((test-expr-rep (cadr expr-rep))   ; get-test-expr
                 (then-expr-rep (caddr expr-rep))  ; get-then-expr
                 (else-expr-rep (cadddr expr-rep)) ; get-else-expr
                 (then-env      (make-then-branch-env env test-expr-rep))
                 (in-then-env   (find-crit-expr then-expr-rep proc-names then-env))
                 (in-else-env   (find-crit-expr else-expr-rep proc-names env))
                 (merged-env    (merge-crit-envs in-then-env in-else-env '())))
            merged-env))
        ;
        ; When we see a 'begin', we know that only the second expression
        ; can establish critical variables, since the first cannot return.
        ;
        ((is-begin? expr-rep)
          (let* ((sub-expr (caddr expr-rep)))  ; get=begin-second
            (find-crit-expr sub-expr proc-names env)))
        ((is-call? expr-rep)
          (let* ((called-proc-name (car expr-rep)))
            (cond
              ((memv called-proc-name proc-names)
                ;
                ; The procedure recursively calls itself here.
                ; Therefore none of the variables in this branch
                ; can be critical variables, so return an empty
                ; environment.
                ;
                '())
              ((not (is-identifier? called-proc-name))
                '())
              (else
                ;
                ; The procedure terminates non-recursively here,
                ; therefore some of the variables in this branch
                ; might be critical variables, so return (i.e.
                ; endorse) the environment we were given.
                ;
                env))))
        (else
          ;
          ; The procedure terminates here too; see above comment.
          ;
          env)))))

;
; Find the critical arguments of a procedure.
;
(define-total find-crit
  (lambda (expr-rep proc-names)
    (cond
      ((is-lambda? expr-rep)
        (let* ((body (caddr expr-rep))) ; get-canonicalized-lambda-body
          (find-crit-expr body proc-names '())))
      (else
        '()))))