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

Tree @master (Download .tar.gz)

canon.scm @masterraw · history · blame

;
; canon.scm - canonicalization of Scheme procedure S-expressions
; Total Procedures in Scheme, May 2006, Chris Pressey
; For license information, see the file LICENSE in this directory.
;

(define forbidden-syntax '(
    set!
    letrec
    do
    delay
    let-syntax
    letrec-syntax
    syntax-rules
    define
    define-syntax
  ))

;
; Create the environment that the body of a let* statement would
; be evaluated with.
;
(define-total make-let*-env
  (lambda (names env)
    (if (acyclic? names)
      (cond
        ((null? names)
          env)
        (else
          (let* ((first   (car names))
                 (rest    (cdr names))
                 (new-env (extend-env env first 'defined)))
            (make-let*-env rest new-env)))))))
  
(definerec-total (

;
; Canonicalize the given S-expression representing a Scheme procedre.
; That is, return a new S-exp which is equivalent to the given S-exp,
; but "de-sugared" so that it is simpler and so we can more easily
; work with it during program analysis.
;
; Aborts with an error, as a side-effect, if there is invalid syntax
; in the procedure, that is, syntax which does not appear in the
; subset of Scheme which we can successfully canonicalize.
;
(canonicalize
  (lambda (expr-rep env)
    (if (acyclic? expr-rep)
      (cond
        ((null? expr-rep)  ; for totality-checker's benefit
          expr-rep)
        ((pair? expr-rep)
          (cond
	    ;
	    ; If it is defined in the environment, it might be a
	    ; local variable which we are calling, in which case
	    ; we must override the default meaning of the symbol.
	    ; (This code is a copy of the is-call? code below.)
	    ;
	    ((and (symbol? (car expr-rep)) (get-env env (car expr-rep)))
              (let* ((func-ref-rep (canonicalize (car expr-rep) env))
                     (args-rep     (cdr expr-rep))
                     (pair         (canonicalize-func-args args-rep env '() '()))
                     (new-bindings (car pair))
                     (new-args     (cdr pair))
                     (new-call-rep (cons func-ref-rep new-args)))
                (cond
                  ((null? expr-rep)
                    expr-rep)
                  ((null? new-bindings)
                    expr-rep)
                  (else
                    (list 'let new-bindings new-call-rep)))))	      
            ;
            ; 'if' with no else-branch is given an explicit undefined else branch.
            ;
            ((is-if? expr-rep)
              (let* ((test-expr (cadr expr-rep))     ; get-test-expr
                     (then-expr (caddr expr-rep)))   ; get-then-expr
                (if (> (length expr-rep) 3)
                    (let* ((else-expr (cadddr expr-rep))) ; get-else-expr
                      (list 'if (canonicalize test-expr env)
                                (canonicalize then-expr env)
                                (canonicalize else-expr env)))
                    (list 'if (canonicalize test-expr env)
                              (canonicalize then-expr env)
                              ''undefined))))
            ((is-cond? expr-rep)
              (canonicalize-cond-branches (cdr expr-rep) env)) ; get-cond-branches
	    ;
	    ; XXX TODO: disallow named let
	    ;
            ((is-let? expr-rep)
              (let* ((bindings     (cadr expr-rep))  ; get-let-bindings
                     (body-rep     (cddr expr-rep))  ; get-let-body
		     (names        (bindings->names bindings))
                     (new-bindings (canonicalize-bindings bindings env '()))
                     (new-env      (extend-env-many env (names->env names 'defined (make-empty-env))))
                     (new-body     (canonicalize-begin body-rep new-env)))
                (list 'let new-bindings new-body)))
            ((is-let*? expr-rep)
              (let* ((bindings   (cadr expr-rep))   ; get-let-bindings
                     (body       (cddr expr-rep))  ; get-let-body
                     (names      (bindings->names bindings))
                     (inner-env  (make-let*-env names env))
                     (canon-body (canonicalize-begin body inner-env)))
                (canonicalize-let* bindings canon-body env)))
            ;
            ; Canonicalizing a lambda expression doesn't make any significant
            ; structural changes by itself, but it does modify the environment.
            ;
            ((is-lambda? expr-rep)
              (let* ((args         (cadr expr-rep))           ; get-lambda-args
                     (body-rep     (cddr expr-rep))           ; get-lambda-body
                     (new-bindings (names->env args 'defined (make-empty-env)))
                     (new-env      (extend-env-many env new-bindings))
                     (new-body     (canonicalize-begin body-rep new-env)))
                (list 'lambda args new-body)))
            ((is-begin? expr-rep)
              (canonicalize-begin (cdr expr-rep) env)) ; get-begin-list
            ;
            ; Evaluation of parameters inside a function call are brought outside the
            ; function call, i.e. (foo (bar baz)) -> (let ((x (bar baz))) (func x))
            ; This only brings out nontrivial (non-symbol, int etc) parameters.
            ;
            ((is-call? expr-rep)
              (let* ((func-ref-rep (canonicalize (car expr-rep) env))
                     (args-rep     (cdr expr-rep))
                     (pair         (canonicalize-func-args args-rep env '() '()))
                     (new-bindings (car pair))
                     (new-args     (cdr pair))
                     (new-call-rep (cons func-ref-rep new-args)))
                (cond
                  ((null? expr-rep)
                    expr-rep)
                  ((null? new-bindings)
                    expr-rep)
                  (else
                    (list 'let new-bindings new-call-rep)))))
            ((memv (car expr-rep) forbidden-syntax)
              #f)
            (else
              expr-rep)))
        (else
          expr-rep)))))

;
; 'cond' is transformed into a series of nested 'if's.
;
(canonicalize-cond-branches
  (lambda (branch-reps env)
    (if (acyclic? branch-reps)
      (cond
        ((null? branch-reps)
          ''undefined)
        (else
          (let* ((first-branch     (car branch-reps))
                 (rest-of-branches (cdr branch-reps))
                 (first-test       (car first-branch))
                 (first-result     (cdr first-branch))
                 (canon-result     (canonicalize-begin first-result env)))
            (cond
              ((eq? first-test 'else)
                canon-result)
              (else
                (list 'if
                      (canonicalize first-test env)
                      canon-result
                      (canonicalize-cond-branches rest-of-branches env))))))))))

(canonicalize-bindings
  (lambda (bindings env acc)
    (if (acyclic? bindings)
      (cond
        ((null? bindings)
          (reverse acc))
        (else
          (let* ((binding     (car bindings))
                 (rest        (cdr bindings))
                 (name        (car binding))
                 (value       (cadr binding))
                 (new-value   (canonicalize value env))
                 (new-binding (list name new-value)))
            (canonicalize-bindings rest env (cons new-binding acc))))))))

;
; 'let*' is transformed into a series of nested 'let's.
; Note that body-rep is given to us pre-canonicalized.
;
(canonicalize-let*
  (lambda (bindings body-rep env)
    (if (acyclic? bindings)
      (cond
        ((null? bindings)
          body-rep)
        (else
          (let* ((binding   (car bindings))
                 (rest      (cdr bindings))
                 (name      (car binding))
                 (value     (cadr binding))
                 (canon-val (canonicalize value env))
                 (new-env   (extend-env env name '())))
            (list 'let
                  (list (list name canon-val))
                  (canonicalize-let* rest body-rep env))))))))

;
; The list of expressions contained inside a 'begin' expression
; is transformed into a series of nested, two-statement 'begin's.
;
(canonicalize-begin
  (lambda (exprs env)
    (if (acyclic? exprs)
      (cond
        ((null? exprs)
          ''undefined)
        ((null? (cdr exprs))
          (canonicalize (car exprs) env))
        (else
          (let* ((first (canonicalize (car exprs) env))
                 (rest  (canonicalize-begin (cdr exprs) env)))
            (list 'begin first rest)))))))

(canonicalize-func-args
  (lambda (args env acc-let acc-args)
    (if (acyclic? args)
      (cond
        ((null? args)
          (cons (reverse acc-let) (reverse acc-args)))
        ((list? (car args))
          (let* ((canonicalized-arg (canonicalize (car args) env))
                 (new-name          (get-fresh-name env))
                 (new-env           (extend-env env new-name '()))
                 (new-acc-let       (cons (list new-name canonicalized-arg) acc-let))
                 (new-acc-args      (cons new-name acc-args)))
            (canonicalize-func-args (cdr args) new-env new-acc-let new-acc-args)))
        (else
          (canonicalize-func-args (cdr args) env acc-let (cons (car args) acc-args)))))))

))

;
; Given a list of S-expressions representing Scheme expressions, return
; a corresponding list of canonicalized expressions.
;
(define-total canonicalize-all
  (lambda (expr-reps env acc)
    (if (acyclic? expr-reps)
      (cond
        ((null? expr-reps)
          (reverse acc))
        (else
          (let* ((expr-rep       (car expr-reps))
                 (rest           (cdr expr-reps))
                 (canon-expr-rep (canonicalize expr-rep env))
                 (new-acc        (cons canon-expr-rep acc)))
            (canonicalize-all rest env new-acc)))))))