git @ Cat's Eye Technologies TPiS / ccc84a0
Initial import of TPiS version 1.0 revision 2006.0528. catseye 9 years ago
12 changed file(s) with 1665 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 Copyright (c)2006 Chris Pressey. All rights reserved.
1
2 Redistribution and use in source and binary forms, with or without
3 modification, are permitted provided that the following conditions
4 are met:
5
6 1. Redistributions of source code must retain the above copyright
7 notices, this list of conditions and the following disclaimer.
8 2. Redistributions in binary form must reproduce the above copyright
9 notices, this list of conditions, and the following disclaimer in
10 the documentation and/or other materials provided with the
11 distribution.
12 3. Neither the names of the copyright holders nor the names of their
13 contributors may be used to endorse or promote products derived
14 from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING, BUT NOT
18 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20 COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 POSSIBILITY OF SUCH DAMAGE.
0 ;
1 ; boot.scm - bootstrap driver
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; Bootstrapping process for asserting that the totality checker
8 ; is itself total.
9 ;
10 (load "std.scm")
11
12 (load "boot1.scm")
13
14 (load "util.scm")
15 (load "env.scm")
16 (load "canon.scm")
17 (load "crit.scm")
18 (load "total.scm")
19
20 (display "Re-loading with real definition of define-total") (newline)
21 (load "boot2.scm")
22
23 (load "util.scm")
24 (load "env.scm")
25 (load "canon.scm")
26 (load "crit.scm")
27 (load "total.scm")
28
29 (load "boot3.scm")
30
31 ;; Define-total.
32
33 (define-total fac
34 (lambda (n)
35 (if (>= n 0)
36 (cond
37 ((zero? n)
38 1)
39 (else
40 (* n (fac (- n 1))))))))
41 ;
42 ; This should be an error.
43 ;
44
45 ;(define-total ever1
46 ; (lambda (x)
47 ; (cond
48 ; ((eq? x 10000)
49 ; x)
50 ; (else
51 ; (ever1 (+ x 1))))))
52
53 (display (fac 5)) (newline)
0 ;
1 ; boot1.scm - first stage bootstrap for define[rec]-total
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; These macros are used in the first stage of bootstrapping
8 ; to provide "reasonable" semantics for define-total before
9 ; total procedure checking is actually defined. i.e. it
10 ; allows the procedure to be defined, without actually checking
11 ; it, on the assumption that a future bootstrapping pass will
12 ; do that. ("This is Fake D.I.Y.")
13 ;
14 ; Note that these do not affect known-total-env in any way.
15 ;
16 (define-syntax define-total
17 (syntax-rules ()
18 ((define-total proc-name lambda-expr)
19 (begin
20 (define proc-name lambda-expr)
21 (display "WARNING: assuming ") (display (quote proc-name)) (display " is total for now, ")
22 (display "please re-load file with real define-total later") (newline)))))
23
24 (define-syntax definerec-total
25 (syntax-rules ()
26 ((definerec-total ((name val) ...))
27 (definerec-total "split" (name ...) (val ...) () ()))
28 ((definerec-total "split" (name1 name2 ...) (val1 val2 ...) nacc vacc)
29 (definerec-total "split" (name2 ...) (val2 ...) (name1 . nacc) (val1 . vacc)))
30 ((definerec-total "split" () () nacc vacc)
31 (definerec-total "define" nacc vacc))
32 ((definerec-total "define" (name1 name2 ...) (val1 val2 ...))
33 (begin
34 (define-total name1 val1)
35 (definerec-total "define" (name2 ...) (val2 ...))))
36 ((definerec-total "define" () ())
37 (display ""))))
0 ;
1 ; boot2.scm - second stage bootstrap for define[rec]-total
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; These macros are used in the second stage of bootstrapping, to
8 ; replace the existing definitions of assumed total procedures with
9 ; genuinely checked total procedures.
10 ;
11 ; Note that these DO affect known-total-env, by adding to it.
12 ;
13 (define-syntax define-total
14 (syntax-rules ()
15 ((define-total proc-name lambda-expr)
16 (begin
17 (display "Redefining ") (display (quote proc-name)) (display " as total procedure") (newline)
18 (let* ((proc-rep (canonicalize (quote lambda-expr) known-total-env)))
19 (cond
20 ((not (proc-total? (quote proc-name) proc-rep known-total-env))
21 (error "procedure could not be determined to be total")))
22 (set! known-total-env
23 (extend-env known-total-env (quote proc-name) 'total-proc))
24 (set! proc-name lambda-expr))))))
25
26 (define-syntax definerec-total
27 (syntax-rules ()
28 ((definerec-total ((name val) ...))
29 (definerec-total "split" (name ...) (val ...) () ()))
30 ((definerec-total "split" (name1 name2 ...) (val1 val2 ...) nacc vacc)
31 (definerec-total "split" (name2 ...) (val2 ...) (name1 . nacc) (val1 . vacc)))
32 ((definerec-total "split" () () names exprs)
33 (begin
34 (display "Redefining ") (display (quote names)) (display " as total procedures") (newline)
35 (cond
36 ((not (procs-total? (quote names) (quote exprs) known-total-env))
37 (error "procedures could not be determined to be total")))
38 (set! known-total-env
39 (names->env (quote names) 'total-proc known-total-env))
40 (definerec-total "define" names exprs)))
41 ((definerec-total "define" (name1 name2 ...) (val1 val2 ...))
42 (begin
43 (set! name1 val1)
44 (definerec-total "define" (name2 ...) (val2 ...))))
45 ((definerec-total "define" () ())
46 (display ""))))
0 ;
1 ; boot3.scm - third stage bootstrap for define[rec]-total
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; Given a new procedure name and a procedure (lambda) S-expression, define
8 ; a new procedure with this name and this lambda, if and only if it can be
9 ; established that the procedure is total.
10 ;
11 ; This has side-effects; an environment of known-total procedures is kept,
12 ; and if the given procedure turns out to be total, it is added to this.
13 ;
14 ; A new environment is set up which initially associates each argument name
15 ; with a nil value, indicating that we know nothing about it yet.
16 ;
17 ; These are the final, "real" definitions of define-total and definerec-total,
18 ; used in the last stage of the bootstrapping process.
19 ;
20 (define-syntax define-total
21 (syntax-rules ()
22 ((define-total proc-name lambda-expr)
23 (begin
24 (define proc-name 'TBA)
25 (let* ((proc-rep (canonicalize (quote lambda-expr) known-total-env)))
26 (cond
27 ((not (proc-total? (quote proc-name) proc-rep known-total-env))
28 (error "procedure could not be determined to be total")))
29 (set! known-total-env
30 (extend-env known-total-env (quote proc-name) 'total-proc))
31 (set! proc-name lambda-expr))))))
32
33 (define-syntax definerec-total
34 (syntax-rules ()
35 ((definerec-total ((name val) ...))
36 (definerec-total "split" (name ...) (val ...) () ()))
37 ((definerec-total "split" (name1 name2 ...) (val1 val2 ...) nacc vacc)
38 (definerec-total "split" (name2 ...) (val2 ...) (name1 . nacc) (val1 . vacc)))
39 ((definerec-total "split" () () names exprs)
40 (begin
41 (cond
42 ((not (procs-total? (quote names) (quote exprs) known-total-env))
43 (error "procedures could not be determined to be total")))
44 (set! known-total-env
45 (names->env (quote names) 'total-proc known-total-env))
46 (definerec-total "define" names exprs)))
47 ((definerec-total "define" (name1 name2 ...) (val1 val2 ...))
48 (begin
49 (define name1 val1)
50 (definerec-total "define" (name2 ...) (val2 ...))))
51 ((definerec-total "define" () ())
52 (display ""))))
0 ;
1 ; canon.scm - canonicalization of Scheme procedure S-expressions
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 (define forbidden-syntax '(
7 set!
8 letrec
9 do
10 delay
11 let-syntax
12 letrec-syntax
13 syntax-rules
14 define
15 define-syntax
16 ))
17
18 ;
19 ; Create the environment that the body of a let* statement would
20 ; be evaluated with.
21 ;
22 (define-total make-let*-env
23 (lambda (names env)
24 (if (acyclic? names)
25 (cond
26 ((null? names)
27 env)
28 (else
29 (let* ((first (car names))
30 (rest (cdr names))
31 (new-env (extend-env env first 'defined)))
32 (make-let*-env rest new-env)))))))
33
34 (definerec-total (
35
36 ;
37 ; Canonicalize the given S-expression representing a Scheme procedre.
38 ; That is, return a new S-exp which is equivalent to the given S-exp,
39 ; but "de-sugared" so that it is simpler and so we can more easily
40 ; work with it during program analysis.
41 ;
42 ; Aborts with an error, as a side-effect, if there is invalid syntax
43 ; in the procedure, that is, syntax which does not appear in the
44 ; subset of Scheme which we can successfully canonicalize.
45 ;
46 (canonicalize
47 (lambda (expr-rep env)
48 (if (acyclic? expr-rep)
49 (cond
50 ((null? expr-rep) ; for totality-checker's benefit
51 expr-rep)
52 ((pair? expr-rep)
53 (cond
54 ;
55 ; If it is defined in the environment, it might be a
56 ; local variable which we are calling, in which case
57 ; we must override the default meaning of the symbol.
58 ; (This code is a copy of the is-call? code below.)
59 ;
60 ((and (symbol? (car expr-rep)) (get-env env (car expr-rep)))
61 (let* ((func-ref-rep (canonicalize (car expr-rep) env))
62 (args-rep (cdr expr-rep))
63 (pair (canonicalize-func-args args-rep env '() '()))
64 (new-bindings (car pair))
65 (new-args (cdr pair))
66 (new-call-rep (cons func-ref-rep new-args)))
67 (cond
68 ((null? expr-rep)
69 expr-rep)
70 ((null? new-bindings)
71 expr-rep)
72 (else
73 (list 'let new-bindings new-call-rep)))))
74 ;
75 ; 'if' with no else-branch is given an explicit undefined else branch.
76 ;
77 ((is-if? expr-rep)
78 (let* ((test-expr (cadr expr-rep)) ; get-test-expr
79 (then-expr (caddr expr-rep))) ; get-then-expr
80 (if (> (length expr-rep) 3)
81 (let* ((else-expr (cadddr expr-rep))) ; get-else-expr
82 (list 'if (canonicalize test-expr env)
83 (canonicalize then-expr env)
84 (canonicalize else-expr env)))
85 (list 'if (canonicalize test-expr env)
86 (canonicalize then-expr env)
87 ''undefined))))
88 ((is-cond? expr-rep)
89 (canonicalize-cond-branches (cdr expr-rep) env)) ; get-cond-branches
90 ;
91 ; XXX TODO: disallow named let
92 ;
93 ((is-let? expr-rep)
94 (let* ((bindings (cadr expr-rep)) ; get-let-bindings
95 (body-rep (cddr expr-rep)) ; get-let-body
96 (names (bindings->names bindings))
97 (new-bindings (canonicalize-bindings bindings env '()))
98 (new-env (extend-env-many env (names->env names 'defined (make-empty-env))))
99 (new-body (canonicalize-begin body-rep new-env)))
100 (list 'let new-bindings new-body)))
101 ((is-let*? expr-rep)
102 (let* ((bindings (cadr expr-rep)) ; get-let-bindings
103 (body (cddr expr-rep)) ; get-let-body
104 (names (bindings->names bindings))
105 (inner-env (make-let*-env names env))
106 (canon-body (canonicalize-begin body inner-env)))
107 (canonicalize-let* bindings canon-body env)))
108 ;
109 ; Canonicalizing a lambda expression doesn't make any significant
110 ; structural changes by itself, but it does modify the environment.
111 ;
112 ((is-lambda? expr-rep)
113 (let* ((args (cadr expr-rep)) ; get-lambda-args
114 (body-rep (cddr expr-rep)) ; get-lambda-body
115 (new-bindings (names->env args 'defined (make-empty-env)))
116 (new-env (extend-env-many env new-bindings))
117 (new-body (canonicalize-begin body-rep new-env)))
118 (list 'lambda args new-body)))
119 ((is-begin? expr-rep)
120 (canonicalize-begin (cdr expr-rep) env)) ; get-begin-list
121 ;
122 ; Evaluation of parameters inside a function call are brought outside the
123 ; function call, i.e. (foo (bar baz)) -> (let ((x (bar baz))) (func x))
124 ; This only brings out nontrivial (non-symbol, int etc) parameters.
125 ;
126 ((is-call? expr-rep)
127 (let* ((func-ref-rep (canonicalize (car expr-rep) env))
128 (args-rep (cdr expr-rep))
129 (pair (canonicalize-func-args args-rep env '() '()))
130 (new-bindings (car pair))
131 (new-args (cdr pair))
132 (new-call-rep (cons func-ref-rep new-args)))
133 (cond
134 ((null? expr-rep)
135 expr-rep)
136 ((null? new-bindings)
137 expr-rep)
138 (else
139 (list 'let new-bindings new-call-rep)))))
140 ((memv (car expr-rep) forbidden-syntax)
141 #f)
142 (else
143 expr-rep)))
144 (else
145 expr-rep)))))
146
147 ;
148 ; 'cond' is transformed into a series of nested 'if's.
149 ;
150 (canonicalize-cond-branches
151 (lambda (branch-reps env)
152 (if (acyclic? branch-reps)
153 (cond
154 ((null? branch-reps)
155 ''undefined)
156 (else
157 (let* ((first-branch (car branch-reps))
158 (rest-of-branches (cdr branch-reps))
159 (first-test (car first-branch))
160 (first-result (cdr first-branch))
161 (canon-result (canonicalize-begin first-result env)))
162 (cond
163 ((eq? first-test 'else)
164 canon-result)
165 (else
166 (list 'if
167 (canonicalize first-test env)
168 canon-result
169 (canonicalize-cond-branches rest-of-branches env))))))))))
170
171 (canonicalize-bindings
172 (lambda (bindings env acc)
173 (if (acyclic? bindings)
174 (cond
175 ((null? bindings)
176 (reverse acc))
177 (else
178 (let* ((binding (car bindings))
179 (rest (cdr bindings))
180 (name (car binding))
181 (value (cadr binding))
182 (new-value (canonicalize value env))
183 (new-binding (list name new-value)))
184 (canonicalize-bindings rest env (cons new-binding acc))))))))
185
186 ;
187 ; 'let*' is transformed into a series of nested 'let's.
188 ; Note that body-rep is given to us pre-canonicalized.
189 ;
190 (canonicalize-let*
191 (lambda (bindings body-rep env)
192 (if (acyclic? bindings)
193 (cond
194 ((null? bindings)
195 body-rep)
196 (else
197 (let* ((binding (car bindings))
198 (rest (cdr bindings))
199 (name (car binding))
200 (value (cadr binding))
201 (canon-val (canonicalize value env))
202 (new-env (extend-env env name '())))
203 (list 'let
204 (list (list name canon-val))
205 (canonicalize-let* rest body-rep env))))))))
206
207 ;
208 ; The list of expressions contained inside a 'begin' expression
209 ; is transformed into a series of nested, two-statement 'begin's.
210 ;
211 (canonicalize-begin
212 (lambda (exprs env)
213 (if (acyclic? exprs)
214 (cond
215 ((null? exprs)
216 ''undefined)
217 ((null? (cdr exprs))
218 (canonicalize (car exprs) env))
219 (else
220 (let* ((first (canonicalize (car exprs) env))
221 (rest (canonicalize-begin (cdr exprs) env)))
222 (list 'begin first rest)))))))
223
224 (canonicalize-func-args
225 (lambda (args env acc-let acc-args)
226 (if (acyclic? args)
227 (cond
228 ((null? args)
229 (cons (reverse acc-let) (reverse acc-args)))
230 ((list? (car args))
231 (let* ((canonicalized-arg (canonicalize (car args) env))
232 (new-name (get-fresh-name env))
233 (new-env (extend-env env new-name '()))
234 (new-acc-let (cons (list new-name canonicalized-arg) acc-let))
235 (new-acc-args (cons new-name acc-args)))
236 (canonicalize-func-args (cdr args) new-env new-acc-let new-acc-args)))
237 (else
238 (canonicalize-func-args (cdr args) env acc-let (cons (car args) acc-args)))))))
239
240 ))
241
242 ;
243 ; Given a list of S-expressions representing Scheme expressions, return
244 ; a corresponding list of canonicalized expressions.
245 ;
246 (define-total canonicalize-all
247 (lambda (expr-reps env acc)
248 (if (acyclic? expr-reps)
249 (cond
250 ((null? expr-reps)
251 (reverse acc))
252 (else
253 (let* ((expr-rep (car expr-reps))
254 (rest (cdr expr-reps))
255 (canon-expr-rep (canonicalize expr-rep env))
256 (new-acc (cons canon-expr-rep acc)))
257 (canonicalize-all rest env new-acc)))))))
0 ;
1 ; crit.scm - critical argument locator
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; Given an abstract environment and the test expression of an 'if',
8 ; return the environment which should apply to the 'then' expression
9 ; branch of the 'if'. For example, if the test is '(zero? n)', our
10 ; new environment is a copy of the given environment, but with the
11 ; name 'n' now bound to 'crit-nat' because n is available as a critical
12 ; natural number argument inside the 'then' branch of this if expression.
13 ;
14 (define-total make-then-branch-env
15 (lambda (env test-expr-rep)
16 (cond
17 ((is-call? test-expr-rep)
18 (let ((proc-name (car test-expr-rep))
19 (proc-args (cdr test-expr-rep)))
20 (cond
21 ((eq? proc-name 'acyclic?)
22 (extend-env env (car proc-args) 'list))
23 ((and (eq? proc-name '>=) (eq? (cadr proc-args) 0))
24 (extend-env env (car proc-args) 'nat))
25 ((and (eq? proc-name 'zero?) (eq? (get-env env (car proc-args)) 'nat))
26 (extend-env env (car proc-args) 'crit-nat))
27 ((and (eq? proc-name 'null?) (eq? (get-env env (car proc-args)) 'list))
28 (extend-env env (car proc-args) 'crit-list))
29 (else
30 env))))
31 (else
32 env))))
33
34 ;
35 ; 'Update' the env to reflect the information accrued in args,
36 ; after name changes to variables (let).
37 ;
38 ; OTOH, if renaming has taken place, we need to retrieve the
39 ; bindings and re-associate the types with the previous names,
40 ; aka "back propogate" the types.
41 ;
42 ; XXX still TODO?
43 ;
44 (define-total back-propogate-crit
45 (lambda (lower-env upper-env)
46 lower-env))
47
48 (define-total merge-crit-envs
49 (lambda (env1 env2 acc-env)
50 (if (acyclic? env1)
51 (cond
52 ((null? env1)
53 acc-env)
54 (else
55 (let* ((pair (car env1))
56 (rest (cdr env1))
57 (var (car pair))
58 (type1 (cdr pair))
59 (type2 (get-env env2 var)))
60 ;
61 ; Note that this glosses over the possibility that the
62 ; var is 'crit-nat' in one environment and 'crit-list' in
63 ; the other, which, absurd as it is, can happen.
64 ;
65 (cond
66 ((or (eq? type1 'crit-nat) (eq? type2 'crit-nat))
67 (merge-crit-envs rest env2 (extend-env acc-env var 'crit-nat)))
68 ((or (eq? type1 'crit-list) (eq? type2 'crit-list))
69 (merge-crit-envs rest env2 (extend-env acc-env var 'crit-list)))
70 (else
71 (merge-crit-envs rest env2 acc-env)))))))))
72
73 ;
74 ; Given the representation and name of a procedure, find its
75 ; critical arguments. The critical arguments are the arguments
76 ; that take on a predictable final value exactly when the
77 ; procedure returns.
78 ;
79 ; We find critical arguments by first finding critical variables,
80 ; then relating these variables to the arguments they came from.
81 ;
82 ; We find critical variables by creating new environments on
83 ; each branch of a conditional statement, and noticing if a
84 ; variable takes on a final value in any branch that terminates
85 ; (rather than recursing.)
86 ;
87 (define-total find-crit-expr
88 (lambda (expr-rep proc-names env)
89 (if (acyclic? expr-rep)
90 (cond
91 ;
92 ; This branch is used to convince the totality checker that
93 ; the expr-rep data structure really is well-founded, even
94 ; though it will never in practice be taken.
95 ;
96 ((null? expr-rep)
97 env)
98 ((is-let? expr-rep)
99 (let* ((body-rep (caddr expr-rep)) ; get-let-body
100 (in-let-env (find-crit-expr body-rep proc-names env)))
101 (back-propogate-crit in-let-env env)))
102 ;
103 ; A variable is critical if it has a critical type in either environment
104 ; of an if expression.
105 ;
106 ; Note that this is the only place where we extend the
107 ; environment.
108 ;
109 ((is-if? expr-rep)
110 (let* ((test-expr-rep (cadr expr-rep)) ; get-test-expr
111 (then-expr-rep (caddr expr-rep)) ; get-then-expr
112 (else-expr-rep (cadddr expr-rep)) ; get-else-expr
113 (then-env (make-then-branch-env env test-expr-rep))
114 (in-then-env (find-crit-expr then-expr-rep proc-names then-env))
115 (in-else-env (find-crit-expr else-expr-rep proc-names env))
116 (merged-env (merge-crit-envs in-then-env in-else-env '())))
117 merged-env))
118 ;
119 ; When we see a 'begin', we know that only the second expression
120 ; can establish critical variables, since the first cannot return.
121 ;
122 ((is-begin? expr-rep)
123 (let* ((sub-expr (caddr expr-rep))) ; get=begin-second
124 (find-crit-expr sub-expr proc-names env)))
125 ((is-call? expr-rep)
126 (let* ((called-proc-name (car expr-rep)))
127 (cond
128 ((memv called-proc-name proc-names)
129 ;
130 ; The procedure recursively calls itself here.
131 ; Therefore none of the variables in this branch
132 ; can be critical variables, so return an empty
133 ; environment.
134 ;
135 '())
136 ((not (is-identifier? called-proc-name))
137 '())
138 (else
139 ;
140 ; The procedure terminates non-recursively here,
141 ; therefore some of the variables in this branch
142 ; might be critical variables, so return (i.e.
143 ; endorse) the environment we were given.
144 ;
145 env))))
146 (else
147 ;
148 ; The procedure terminates here too; see above comment.
149 ;
150 env)))))
151
152 ;
153 ; Find the critical arguments of a procedure.
154 ;
155 (define-total find-crit
156 (lambda (expr-rep proc-names)
157 (cond
158 ((is-lambda? expr-rep)
159 (let* ((body (caddr expr-rep))) ; get-canonicalized-lambda-body
160 (find-crit-expr body proc-names '())))
161 (else
162 '()))))
0 ;
1 ; env.scm - environment support for abstract interpretation
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; Each environment associates a name with a type.
8 ;
9 ; For canonicalization, we only need to know if a name has been
10 ; defined at all. In this case, the only type is 'defined'.
11 ; For the termination analysis proper, we need a more refined system:
12 ;
13 ; unknown a variable which we know nothing about.
14 ;
15 ; list a variable that we know to be an acyclic list.
16 ; (true branch of an 'acyclic-list?' test.)
17 ;
18 ; reduced-list cdr of an acylic-list.
19 ;
20 ; null null list.
21 ;
22 ; nat a variable which has tested for positive integerness.
23 ;
24 ; reduced-nat a positive-int that has been decremented.
25 ;
26 ; zero a variable that we know must be zero (true branch
27 ; of a 'zero?' test.)
28 ;
29 ; total-proc a procedure that we know always terminates.
30 ;
31
32
33 (define-total make-empty-env
34 (lambda ()
35 '()))
36
37 (define-total extend-env
38 (lambda (env name value)
39 (cons (cons name value) env)))
40
41 (define-total names->env
42 (lambda (names value env)
43 (if (acyclic? names)
44 (cond
45 ((null? names)
46 env)
47 (else
48 (names->env (cdr names) value (extend-env env (car names) value)))))))
49
50 (define-total extend-env-many
51 (lambda (env list-of-bindings)
52 (append list-of-bindings env)))
53
54 (define-total get-env
55 (lambda (env name)
56 (let* ((result (assq name env)))
57 (cond
58 (result
59 (cdr result))
60 (else
61 #f)))))
62
63 (define-total get-fresher-name
64 (lambda (env acc)
65 (if (acyclic? env)
66 (cond
67 ((null? env)
68 acc)
69 ((get-env env acc)
70 (get-fresher-name (cdr env) (string->symbol
71 (string-append (symbol->string acc) (symbol->string (caar env))))))
72 (else
73 acc)))))
74
75 (define-total get-fresh-name
76 (lambda (env)
77 (get-fresher-name env 'z)))
78
79 ; Entries in env2 override those in env1.
80 (define-total merge-envs
81 (lambda (env1 env2)
82 (if (acyclic? env1)
83 (cond
84 ((null? env1)
85 env2)
86 ((get-env env2 (caar env1))
87 (merge-envs (cdr env1) env2))
88 (else
89 (merge-envs (cdr env1) (cons (car env1) env2)))))))
90
91 (define-total display-env-loop
92 (lambda (env)
93 (if (acyclic? env)
94 (cond
95 ((null? env)
96 'ok)
97 (else
98 (let* ((pair (car env))
99 (key (car pair))
100 (val (cdr pair)))
101 (if (not (get-env orig-known-total-env key))
102 (begin (display key) (display " -> ") (display val) (display "; "))
103 'whatever)
104 (display-env-loop (cdr env))))))))
105
106 (define-total display-env
107 (lambda (env)
108 (display "{")
109 (display-env-loop env)
110 (display "}")))
111
112 ;
113 ; There is only one type complex enough to require helper functions -
114 ; the type of the procedure currently being analyzed, called a "this".
115 ; A this tracks the types of all of its arguments.
116 ;
117
118 (define-total is-this?
119 (lambda (type)
120 (and (pair? type) (eq? (car type) 'this))))
121
122 (define-total this-args
123 (lambda (type)
124 (cdr type)))
125
126 (define-total all-unknown?
127 (lambda (types)
128 (if (acyclic? types)
129 (cond
130 ((null? types)
131 #t)
132 (else
133 (cond
134 ((eq? (car types) 'unknown)
135 (all-unknown? (cdr types)))
136 (else
137 #f)))))))
138
139 ;
140 ; A type is "recursable" if it is a this and at least one of the types
141 ; of its arguments is not "unknown".
142 ;
143 (define-total is-recursable?
144 (lambda (type)
145 (and (is-this? type) (not (all-unknown? (this-args type))))))
0 ;
1 ; std.scm - standard environment of known-total procedures.
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; These are mostly Scheme builtins.
8 ;
9 ; We make some assumptions about this list. The basic assumption
10 ; is that all of these builtins are implemented correctly and are
11 ; thus guaranteed to return. Another assumption is that those
12 ; builtins that operate on lists (for example, 'length', 'memv',
13 ; 'list->vector', etc) always return, even when given a cyclic list
14 ; (even though R5RS does not specify this behaviour.) Should this
15 ; assumption fail for some given implementation of some of these
16 ; builtins, those builtins should be removed from this list, and
17 ; if those builtins are used in the totality checker itself
18 ; (notably 'reverse' is used in several places,) "hand-rolled"
19 ; versions that are guaranteed to terminate should be substituted.
20 ;
21 ; Note that this file should NOT be reloaded during bootstrapping.
22 ;
23 ; It might be nice if these were local to define-total, but
24 ; it's more useful for testing purposes if we can access them
25 ; from outside there too.
26 ;
27 (define orig-known-total-env
28 (map (lambda (name) (cons name 'total-proc)) '(
29 eqv? eq? equal?
30 number? complex? real? rational? integer?
31 exact? inexact?
32 = < > <= >=
33 zero? positive? negative? odd? even? max min
34 + * - /
35 abs quotient remainder modulo
36 gcd lcm
37 numerator denominator
38 floor ceiling truncate round
39 rationalize
40 exp log sin cos tan asin acos atan
41 sqrt expt
42 make-rectangular make-polar real-part imag-part magnitude angle
43 exact->inexact inexact->exact
44 number->string string->number
45 not boolean? and or
46 pair? cons car cdr
47 ;set-car! set-cdr! ; no side effects
48 caar cadr cdar cddr
49 caaar caadr cadar caddr cdaar cdadr cddar cdddr
50 caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr
51 cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
52 null? list?
53 list length append reverse ; NOTE: can depend on implementation
54 list-tail list-ref ; NOTE: can depend on implementation
55 memq memv member ; NOTE: can depend on implementation
56 assq assv assoc ; NOTE: can depend on implementation
57 symbol? symbol->string string->symbol
58 char?
59 char=? char<? char>? char<=? char>=?
60 char-ci=? char-ci<? char-ci>? char-ci<=? char-ci>=?
61 char-alphabetic? char-numeric? char-whitespace?
62 char-upper-case? char-lower-case?
63 char->integer integer->char
64 char-upcase char-downcase
65 string? make-string string string-length string-ref
66 ;string-set! ; no side effects
67 string=? string-ci=?
68 string<? string>? string<=? string>=?
69 string-ci<? string-ci>? string-ci<=? string-ci>=?
70 substring string-append
71 string->list list->string
72 string-copy
73 ;string-fill! ; no side effects
74 vector? make-vector vector vector-length vector-ref
75 ;vector-set! ; no side effects
76 vector->list list->vector
77 ;vector-fill! ; no side effects
78 procedure?
79 ;apply ; The following builtins call for
80 ;map for-each ; more complex flow analysis than
81 ;force ; our analyzer does at present.
82 ;call-with-current-continuation values call-with-values dynamic-wind
83 ;eval scheme-report-environment null-environment interaction-environment
84 ;call-with-input-file call-with-output-file
85 input-port? output-port?
86 current-input-port current-output-port
87 ;with-input-from-file with-output-to-file
88 ;open-input-file open-output-file
89 ;close-input-port close-output-port
90 read read-char peek-char
91 eof-object? char-ready?
92 write display newline write-char
93 ;load transcript-on transcript-off
94 acyclic? ; part of the analyzer
95 )))
96
97 ;
98 ; This variable changes over time, as define-total and definerec-total
99 ; 'learn' about new user-defined procedures which are also total.
100 ;
101 (define known-total-env orig-known-total-env)
0 ;
1 ; test.scm - simple test suite
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 (load "std.scm")
7 (load "boot1.scm")
8 (load "util.scm")
9 (load "env.scm")
10 (load "canon.scm")
11 (load "crit.scm")
12 (load "total.scm")
13
14 ;
15 ; Definitions (as S-expressions) of procedures that we will test.
16 ;
17
18 (define simple-proc
19 '(lambda (a b)
20 (if (> a b) (* 2 a) (* 3 b)))
21 )
22
23 (define unsafe-fac-proc
24 '(lambda (n)
25 (cond
26 ((zero? n)
27 1)
28 (else
29 (* n (fac (- n 1))))))
30 )
31
32 (define fac-proc
33 '(lambda (n)
34 (if (>= n 0)
35 (cond
36 ((zero? n)
37 1)
38 (else
39 (* n (fac (- n 1)))))
40 0))
41 )
42
43 (define unsafe-anbncn-proc
44 '(lambda (l k)
45 (cond
46 ((null? l)
47 (eq? k 0))
48 ((eq? (car l) 'a)
49 (anbncn (cdr l) (+ k 1)))
50 ((eq? (car l) 'b)
51 (anbncn (cdr l) (+ k 1)))
52 ((eq? (car l) 'c)
53 (anbncn (cdr l) (- k 2)))
54 (else
55 (anbncn (cdr l) k))))
56 )
57
58 (define anbncn-proc
59 '(lambda (l k)
60 (if (acyclic? l)
61 (cond
62 ((null? l)
63 (eq? k 0))
64 ((eq? (car l) 'a)
65 (anbncn (cdr l) (+ k 1)))
66 ((eq? (car l) 'b)
67 (anbncn (cdr l) (+ k 1)))
68 ((eq? (car l) 'c)
69 (anbncn (cdr l) (- k 2)))
70 (else
71 (anbncn (cdr l) k)))))
72 )
73
74 (define ack-proc
75 '(lambda (x y)
76 (if (and (>= x 0) (>= y 0))
77 (cond
78 ((zero? x)
79 (+ y 1))
80 ((zero? y)
81 (ack (- x 1) 1))
82 (else
83 (ack (- x 1) (ack x (- y 1)))))))
84 )
85
86 (define ever1-proc
87 '(lambda (x)
88 (cond
89 ((eq? x 10000)
90 x)
91 (else
92 (ever1 (+ x 1)))))
93 )
94
95 ;
96 ; The tests proper.
97 ;
98
99 ;; Tests for acyclicity.
100
101 (test acyclic-1
102 (acyclic? '(1 2 3 4 5))
103 #t
104 )
105
106 (test acyclic-2
107 (acyclic? '(1 2 (a b c) 3 4 5))
108 #t
109 )
110
111 (test acyclic-3
112 (let* ((tail '(5 . TBA))
113 (lst (append '(1 2 3 4) tail)))
114 (set-cdr! tail lst)
115 (acyclic? lst))
116 #f
117 )
118
119 (test acyclic-4
120 (let* ((tail '(5 . TBA))
121 (inner-lst (append '(1 2 3 4) tail))
122 (outer-lst (list 'a 'b inner-lst 'c 'd)))
123 (set-cdr! tail inner-lst)
124 (acyclic? outer-lst))
125 #f
126 )
127
128 ;; Tests for canonicalization.
129
130 (test canonicalize-simple
131 (canonicalize simple-proc '())
132 '(lambda (a b)
133 (if (> a b)
134 (* 2 a)
135 (* 3 b)))
136 )
137
138 (test canonicalize-fac
139 (canonicalize fac-proc '())
140 '(lambda (n)
141 (if (>= n 0)
142 (if (zero? n)
143 1
144 (let ((z
145 (let ((z (- n 1))) (fac z))
146 ))
147 (* n z)))
148 0))
149 )
150
151 (test canonicalize-ack
152 (canonicalize ack-proc '())
153 '(lambda (x y)
154 (if (let ((z (>= x 0)) (zz (>= y 0))) (and z zz))
155 (if (zero? x)
156 (+ y 1)
157 (if (zero? y)
158 (let ((z (- x 1)))
159 (ack z 1))
160 (let ((z (- x 1))
161 (zz (let ((zz (- y 1))) (ack x zz))))
162 (ack z zz))))
163 'undefined))
164 )
165
166 (test canonicalize-let*
167 (canonicalize
168 '(lambda (x y)
169 (let* ((p (+ x y))
170 (q (* p p)))
171 (display q)))
172 '())
173 '(lambda (x y)
174 (let ((p (+ x y)))
175 (let ((q (* p p)))
176 (display q))))
177 )
178
179 ;; Tests for finding critical arguments.
180
181 (test crit-args-simple
182 (find-crit (canonicalize simple-proc '()) '(simple))
183 '()
184 )
185
186 (test crit-args-fac
187 (find-crit (canonicalize fac-proc '()) '(fac))
188 '((n . crit-nat))
189 )
190
191 (test crit-args-unsafe-fac
192 (find-crit (canonicalize unsafe-fac-proc '()) '(fac))
193 '()
194 )
195
196 (test crit-args-anbncn
197 (find-crit (canonicalize anbncn-proc '()) '(anbncn))
198 '((l . crit-list))
199 )
200
201 (test crit-args-ack
202 (find-crit (canonicalize ack-proc '()) '(ack))
203 '()
204 )
205
206 (test crit-args-ever1
207 (find-crit (canonicalize ever1-proc '()) '(ever1))
208 '()
209 )
210
211 ;; Tests for determining totality.
212
213 (test proc-total-simple
214 (proc-total? 'simple (canonicalize simple-proc '()) known-total-env)
215 #t
216 )
217
218 (test proc-total-fac
219 (proc-total? 'fac (canonicalize fac-proc '()) known-total-env)
220 #t
221 )
222
223 (test proc-total-unsafe-fac
224 (proc-total? 'fac (canonicalize unsafe-fac-proc '()) known-total-env)
225 #f
226 )
227
228 (test proc-total-anbncn
229 (proc-total? 'anbncn (canonicalize anbncn-proc '()) known-total-env)
230 #t
231 )
232
233 (test proc-total-ack
234 (proc-total? 'ack (canonicalize ack-proc '()) known-total-env)
235 #f
236 )
237
238 (test proc-total-ever1
239 (proc-total? 'ever1 (canonicalize ever1-proc '()) known-total-env)
240 #f
241 )
242
243 ;; Tests for determining totality of mutually-recursive procedures.
244
245 (test mutrec-total-even
246 (procs-total? '(even odd) (list
247 (canonicalize '(lambda (x)
248 (if (>= x 0)
249 (cond
250 ((zero? x)
251 1)
252 (else
253 (odd (- x 1)))))) '())
254 (canonicalize '(lambda (x)
255 (if (>= x 0)
256 (cond
257 ((zero? x)
258 0)
259 (else
260 (odd (- x 1)))))) '()))
261 known-total-env)
262 #t
263 )
264
265 (test mutrec-total-bad
266 (procs-total? '(foo bar) (list
267 (canonicalize '(lambda (x y)
268 (if (acyclic? x)
269 (cond
270 ((null? x)
271 (bar '(1 2 3 4 5) y))
272 (else
273 (foo (cdr x) (+ y 1)))))) '())
274 (canonicalize '(lambda (x y)
275 (if (acyclic? x)
276 (cond
277 ((null? x)
278 (foo '(1 2 3 4 5) y))
279 (else
280 (bar (cdr x) (+ y 1)))))) '()))
281 known-total-env)
282 #f
283 )
0 ;
1 ; total.scm - kernel of totality checker
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; Create initial environment from the list of arguments of the lambda
8 ; expression and from the environment of previously gathered critical
9 ; arguments. Each argument which is found in the critical arguments
10 ; gets a type apropos to its critical nature; arguments that are not
11 ; found in the critical argument environment get the type 'unknown'.
12 ;
13 (define-total make-initial-args
14 (lambda (formals crit-args env)
15 (if (acyclic? formals)
16 (cond
17 ((null? formals)
18 env)
19 (else
20 (let* ((formal (car formals))
21 (rest (cdr formals))
22 (type (get-env crit-args formal)))
23 (cond
24 (type
25 (make-initial-args rest crit-args (extend-env env formal type)))
26 (else
27 (make-initial-args rest crit-args (extend-env env formal 'unknown))))))))))
28
29 ;
30 ; Create a new 'this' type.
31 ;
32 (define-total make-this
33 (lambda (formals crit-args acc)
34 (if (acyclic? formals)
35 (cond
36 ((null? formals)
37 (cons 'this (reverse acc)))
38 (else
39 (let* ((formal (car formals))
40 (rest (cdr formals))
41 (this-arg (or (get-env crit-args formal) 'unknown)))
42 (make-this rest crit-args (cons this-arg acc))))))))
43
44 ;
45 ; Create a new environment that is suited for a base for the
46 ; environments of a set of mutually recursive procedures.
47 ; This environment has one 'this' in it for each procedure.
48 ; These procedures can call each other, and so must be informed
49 ; of each others' critical arguments.
50 ;
51 (define-total make-mutual-env
52 (lambda (proc-names expr-reps env)
53 (if (acyclic? proc-names)
54 (cond
55 ((null? proc-names)
56 env)
57 (else
58 (let* ((proc-name (car proc-names))
59 (expr-rep (car expr-reps))
60 (formals (cadr expr-rep)) ; get-lambda-args
61 (crit-args (find-crit expr-rep proc-names))
62 (this (make-this formals crit-args '()))
63 (new-env (extend-env env proc-name this)))
64 (make-mutual-env (cdr proc-names) (cdr expr-reps) new-env)))))))
65
66 ;
67 ; A list of built-in functions that, when applied to an acyclic list
68 ; (list or reduced-list type) always result in a reduced-list.
69 ;
70 (define reducing-procs '(
71 car
72 cdr
73 caar
74 cadr
75 cdar
76 cddr
77 caaar
78 caadr
79 cadar
80 caddr
81 cdaar
82 cdadr
83 cddar
84 cdddr
85 caaaar
86 caaadr
87 caadar
88 caaddr
89 cadaar
90 cadadr
91 caddar
92 cadddr
93 cdaaar
94 cdaadr
95 cdadar
96 cdaddr
97 cddaar
98 cddadr
99 cdddar
100 cddddr
101 ))
102
103 ;
104 ; Determine the type of an expression, with respect to totality analysis.
105 ;
106 ; If the expression is (- foo 1) where foo is a critical natural number,
107 ; the type is a reduced natural number.
108 ;
109 ; If the expression is (cdr foo) where foo is a critical list, the
110 ; type is a reduced list.
111 ;
112 (define-total get-expr-type
113 (lambda (expr-rep env)
114 (cond
115 ((is-call? expr-rep)
116 (let* ((proc-name (car expr-rep))
117 (proc-args (cdr expr-rep))
118 (proc-type (get-env env proc-name)))
119 (cond
120 ((eq? proc-name '-)
121 (cond
122 ((and (eq? (get-env env (car proc-args)) 'crit-nat)
123 (eq? (cadr proc-args) 1))
124 'reduced-nat)
125 (else
126 'unknown)))
127 ((memv proc-name reducing-procs)
128 (cond
129 ((or (eq? (get-env env (car proc-args)) 'crit-list)
130 (eq? (get-env env (car proc-args)) 'reduced-list))
131 'reduced-list)
132 (else
133 'unknown)))
134 (else
135 'unknown))))
136 (else
137 'unknown))))
138
139 ;
140 ; When encountering a self-recursive call, check that each passed
141 ; argument coincides with an identified critical argument.
142 ;
143 (define-total check-recurse-args
144 (lambda (actuals formal-types env)
145 (if (acyclic? actuals)
146 (cond
147 ((null? actuals)
148 #t)
149 (else
150 (let* ((actual (car actuals))
151 (actual-type (get-env env actual))
152 (formal-type (car formal-types)))
153 (cond
154 ((and (eq? formal-type 'crit-nat) (not (eq? actual-type 'reduced-nat)))
155 #f)
156 ((and (eq? formal-type 'crit-list) (not (eq? actual-type 'reduced-list)))
157 #f)
158 (else
159 (check-recurse-args (cdr actuals) (cdr formal-types) env)))))))))
160
161 ;
162 ; Create a new abstract environment from the existing one, in the context
163 ; of the abstract execution of the body of the let.
164 ;
165 (define-total make-let-env
166 (lambda (names vals orig-env new-env)
167 (if (acyclic? names)
168 (cond
169 ((null? names)
170 (merge-envs orig-env new-env))
171 (else
172 (let* ((first-name (car names))
173 (rest-names (cdr names))
174 (first-val (car vals))
175 (rest-vals (cdr vals))
176 (first-type (get-expr-type first-val orig-env))
177 (new-env (extend-env new-env first-name first-type)))
178 (cond
179 (first-type
180 (make-let-env rest-names rest-vals orig-env new-env))
181 (else
182 #f))))))))
183
184 (definerec-total (
185
186 ;
187 ; Determine if all bindings in a let-expression are total.
188 ;
189 (bindings-total?
190 (lambda (bindings env)
191 (if (acyclic? bindings)
192 (cond
193 ((null? bindings)
194 #t)
195 (else
196 (let* ((binding (car bindings))
197 (rest (cdr bindings))
198 (name (car binding))
199 (val (cadr binding)))
200 (cond
201 ((expr-total? val env)
202 (bindings-total? rest env))
203 (else
204 #f))))))))
205
206 ;
207 ; Given a canonicalized representation of an expression and a type
208 ; environment, return #t if we can establish that this expression is total
209 ; (always terminates for any input,) or #f if we cannot establish that.
210 ;
211 (expr-total?
212 (lambda (expr-rep env)
213 (if (acyclic? expr-rep)
214 (cond
215 ;
216 ; Branch never taken but convinces analysis that we're total.
217 ;
218 ((null? expr-rep)
219 #t)
220 ;
221 ; a) If we see a 'let', we abstractly evaluate each of its
222 ; initial values in the current environment; then we
223 ; create a new environment where each name is bound to its
224 ; newly associated (abstract) value, and abstractly evaluate
225 ; the body in the new environment.
226 ;
227 ((is-let? expr-rep)
228 (let* ((bindings (cadr expr-rep))
229 (names (bindings->names bindings))
230 (vals (bindings->values bindings))
231 (body-rep (caddr expr-rep))
232 (new-env (make-let-env names vals env '())))
233 (cond
234 (new-env
235 (and (bindings-total? bindings env)
236 (expr-total? body-rep new-env)))
237 (else
238 (display expr-rep)
239 #f))))
240 ;
241 ; b) If we see an 'if', we abstractly evaluate the test
242 ; and both branches; the procedure is only total if
243 ; all of these are total.
244 ;
245 ((is-if? expr-rep)
246 (let* ((test-expr-rep (cadr expr-rep)) ; get-test-expr
247 (then-expr-rep (caddr expr-rep)) ; get-then-expr
248 (else-expr-rep (cadddr expr-rep)) ; get-else-expr
249 (test-total (expr-total? test-expr-rep env))
250 (then-total (expr-total? then-expr-rep env))
251 (else-total (expr-total? else-expr-rep env)))
252 (and test-total then-total else-total)))
253 ;
254 ; c) A sequence is only total if all of its constituent
255 ; expressions are total.
256 ;
257 ((is-begin? expr-rep)
258 (let* ((first-expr (cadr expr-rep)) ; get-begin-first
259 (second-expr (caddr expr-rep))) ; get-begin-second
260 (and (expr-total? first-expr env)
261 (expr-total? second-expr env))))
262 ;
263 ; d) If we see a call to a procedure, we insist that the
264 ; called procedure must be total as well. There are three
265 ; sub-cases to consider here:
266 ;
267 ((is-call? expr-rep)
268 (let* ((proc-name (car expr-rep))
269 (proc-type (get-env env proc-name))
270 (proc-args (cdr expr-rep)))
271 (cond
272 ;
273 ; d.1) It's a call to a function that we already know is total.
274 ;
275 ((eq? proc-type 'total-proc)
276 #t)
277 ;
278 ; d.2) It's a recursive call - either self-recursive or
279 ; to one of any number of mutually recursive procedures.
280 ; In either case, we must check that the arguments being
281 ; passed to the critical arguments of the procedure are
282 ; those that monotonically decrease.
283 ;
284 ((is-recursable? proc-type)
285 (check-recurse-args proc-args (this-args proc-type) env))
286 ;
287 ; d.3) It's a call to a function that we know nothing about.
288 ; Perhaps it is an external function which was not
289 ; given in the list of known-total functions, or perhaps
290 ; it was passed to this function as a parameter. Either
291 ; way, because of this call, we must assume that our
292 ; function might not be total.
293 ;
294 (else
295 #f))))
296 ;
297 ; e) Otherwise, the expression is an atom, number, lambda, or
298 ; other primitive, and as such, is certainly total.
299 ;
300 (else
301 #t)))))
302
303 ))
304
305 ;
306 ; Given the name of a procedure, a canonicalized, syntax-checked S-expression
307 ; representing that procedure, and a type environment, return #t if we can
308 ; establish that this procedure is total (always terminates for any input,)
309 ; or #f if we cannot establish that.
310 ;
311 ; We create a new environment from the given environment, which we use while
312 ; checking the representation of the procedure. In this new environment,
313 ; each of the arguments of the procedure is associated with its type, and
314 ; the name of the procedure is associated with a 'this' type, which knows
315 ; the types that each of the arguments should be.
316 ;
317 (define-total proc-total?
318 (lambda (proc-name expr-rep env)
319 (cond
320 ((is-lambda? expr-rep)
321 (let* ((crit-args (find-crit expr-rep (list proc-name)))
322 (body-rep (caddr expr-rep)) ; get-canonicalized-lambda-body
323 (formals (cadr expr-rep)) ; get-lambda-args
324 (inner-env (make-initial-args formals crit-args '()))
325 (this (make-this formals crit-args '()))
326 (outer-env (extend-env env proc-name this))
327 (new-env (merge-envs outer-env inner-env)))
328 (expr-total? body-rep new-env)))
329 (else
330 #f))))
331
332 ;
333 ; Given a set of (possibly mutually recursive) procedures, determine if
334 ; they are all total. First form the mutual environment, then call
335 ; each-proc-total? on the list of procedures, with that environment.
336 ;
337 (define procs-total?
338 (lambda (proc-names expr-reps env)
339 (let* ((canon-expr-reps (canonicalize-all expr-reps env '()))
340 (mutual-env (make-mutual-env proc-names canon-expr-reps (make-empty-env))))
341 (each-proc-total? proc-names canon-expr-reps (merge-envs env mutual-env)))))
342
343 ;
344 ; Do the actual work required by procs-total?.
345 ;
346 (define-total each-proc-total?
347 (lambda (proc-names expr-reps env)
348 (if (acyclic? expr-reps)
349 (cond
350 ((null? expr-reps)
351 #t)
352 (else
353 (let* ((proc-name (car proc-names))
354 (expr-rep (car expr-reps))
355 (body-rep (caddr expr-rep)) ; get-canonicalized-lambda-body
356 (formals (cadr expr-rep)) ; get-lambda-args
357 (crit-args (find-crit expr-rep proc-names))
358 (inner-env (make-initial-args formals crit-args '()))
359 (new-env (merge-envs env inner-env)))
360 (and
361 (expr-total? body-rep new-env)
362 (each-proc-total? (cdr proc-names) (cdr expr-reps) env))))))))
0 ;
1 ; util.scm - miscellaneous utility procedures
2 ; Total Procedures in Scheme, May 2006, Chris Pressey
3 ; For license information, see the file LICENSE in this directory.
4 ;
5
6 ;
7 ; Determine if a Scheme datum is acyclic.
8 ;
9 (define acyclic?
10 (lambda (pair)
11 (acyclic-test pair '())))
12
13 (define acyclic-test
14 (lambda (pair acc)
15 (cond
16 ((not (pair? pair))
17 #t)
18 ((memq pair acc)
19 #f)
20 (else
21 (let ((fst (car pair))
22 (snd (cdr pair))
23 (new-acc (cons pair acc)))
24 (and (acyclic-test fst new-acc) (acyclic-test snd new-acc)))))))
25
26 ;
27 ; XXX explain and/or borrow eopl:error
28 ;
29 (define-total error
30 (lambda (msg)
31 (display msg) (newline)
32 (read '())))
33
34 ;
35 ; Test case for test suite.
36 ;
37 (define-syntax test
38 (syntax-rules ()
39 ((test test-name expr expected)
40 (begin
41 (display "Running test: ") (display (quote test-name)) (display "... ")
42 (let ((result expr))
43 (cond
44 ((equal? result expected)
45 (display "passed.") (newline))
46 (else
47 (display "FAILED!") (newline)
48 (display "Expected: ") (display expected) (newline)
49 (display "Actual: ") (display result) (newline))))))))
50
51 ;
52 ; XXX there may be a problem doing NON-TAIL (self-)recursion, check it out.
53 ;
54 (define-total bindings->names
55 (lambda (bindings)
56 (if (acyclic? bindings)
57 (cond
58 ((null? bindings)
59 '())
60 (else
61 (let* ((binding (car bindings))
62 (rest (cdr bindings))
63 (name (car binding)))
64 (cons name (bindings->names rest))))))))
65
66 (define-total bindings->values
67 (lambda (bindings)
68 (if (acyclic? bindings)
69 (cond
70 ((null? bindings)
71 '())
72 (else
73 (let* ((binding (car bindings))
74 (rest (cdr bindings))
75 (value (cadr binding)))
76 (cons value (bindings->values rest))))))))
77
78 (define-total is-lambda?
79 (lambda (expr-rep)
80 (and
81 (pair? expr-rep)
82 (eq? 'lambda (car expr-rep)))))
83
84 (define-total is-let?
85 (lambda (expr-rep)
86 (and
87 (pair? expr-rep)
88 (eq? 'let (car expr-rep)))))
89
90 (define-total is-let*?
91 (lambda (expr-rep)
92 (and
93 (pair? expr-rep)
94 (eq? 'let* (car expr-rep)))))
95
96 (define-total is-if?
97 (lambda (expr-rep)
98 (and
99 (pair? expr-rep)
100 (eq? 'if (car expr-rep)))))
101
102 (define-total is-cond?
103 (lambda (expr-rep)
104 (and
105 (pair? expr-rep)
106 (eq? 'cond (car expr-rep)))))
107
108 (define-total is-begin?
109 (lambda (expr-rep)
110 (and
111 (pair? expr-rep)
112 (eq? 'begin (car expr-rep)))))
113
114 (define-total is-call?
115 (lambda (expr-rep)
116 (and
117 (pair? expr-rep)
118 (not (eq? (car expr-rep) 'quote))))) ;... and all others
119
120 (define-total is-identifier?
121 (lambda (expr-rep)
122 (symbol? expr-rep)))
123
124 (define-total is-quote?
125 (lambda (expr-rep)
126 (and
127 (pair? expr-rep)
128 (eq? 'quote (car expr-rep)))))