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