CPS Transformer | Generated by AI

Home PDF

;; A simple CPS transformer which does proper tail-call and does not
;; duplicate contexts for if-expressions.
;; author: Yin Wang (yw21@cs.indiana.edu)
(load "pmatch.scm") ; Loads a pattern matching library for Scheme, making it easier to handle different expression structures.

(define cps
  (lambda (exp)
    (letrec
        ([trivial? (lambda (x) (memq x '(zero? add1 sub1)))] ; Checks if a given symbol is one of the primitive operators: zero?, add1, sub1. These are treated specially in the transformation.
         [id (lambda (v) v)] ; The identity function, used as the initial continuation for the top-level expression.
         [ctx0 (lambda (v) `(k ,v))]      ; tail context. This creates a continuation that simply applies the current continuation 'k' to a value 'v'. It's used when the current call is in tail position.
         [fv (let ([n -1]) ; Creates a generator for fresh variable names.
               (lambda ()
                 (set! n (+ 1 n))
                 (string->symbol (string-append "v" (number->string n)))))]
         [cps1
          (lambda (exp ctx) ; The core recursive function that performs the CPS transformation. It takes an expression 'exp' and a continuation 'ctx' as arguments. The continuation represents what to do with the result of evaluating 'exp'.
            (pmatch exp ; Uses pattern matching to analyze the structure of the expression.
              [,x (guard (not (pair? x))) (ctx x)] ; Base case: If the expression 'x' is not a pair (i.e., it's a literal or a variable), it means it's already a value. Apply the current continuation 'ctx' to this value.

              [(if ,test ,conseq ,alt) ; Matches an 'if' expression with a test, consequent, and alternative.
               (cps1 test ; Recursively transform the 'test' expression.
                     (lambda (t) ; The continuation for the 'test' expression. It takes the result of the test (which will be a boolean value) as 't'.
                       (cond
                        [(memq ctx (list ctx0 id)) ; If the current context 'ctx' is either the tail context 'ctx0' or the initial identity context 'id', it means the 'if' expression itself is in a tail position.
                         `(if ,t ,(cps1 conseq ctx) ,(cps1 alt ctx))] ; In this case, the 'if' expression remains an 'if' expression in the CPSed code. The consequent and alternative are CPSed with the same context 'ctx'. This avoids duplicating contexts.
                        [else ; If the current context is not a tail context, it means the result of the 'if' expression needs to be passed to some further computation.
                         (let ([u (fv)]) ; Generate a fresh variable name 'u' to hold the result of the 'if' expression.
                           `(let ([k (lambda (,u) ,(ctx u))]) ; Create a new continuation 'k' that takes the result 'u' and applies the original context 'ctx' to it.
                              (if ,t ,(cps1 conseq ctx0) ,(cps1 alt ctx0))))])))] ; The 'if' expression is wrapped in a 'let' that introduces the new continuation 'k'. The consequent and alternative are CPSed with the tail context 'ctx0', as their results will be immediately passed to 'k'.

              [(lambda (,x) ,body) ; Matches a lambda expression with a single argument 'x' and a body.
               (ctx `(lambda (,x k) ,(cps1 body ctx0)))] ; The lambda expression is transformed into a new lambda expression that takes an additional argument 'k' (the continuation). The body of the original lambda is CPSed with the tail context 'ctx0', as its result will be passed to this continuation 'k'.

              [(,op ,a ,b) ; Matches an expression with a binary operator 'op' and two operands 'a' and 'b'.
               (cps1 a ; Recursively transform the first operand 'a'.
                     (lambda (v1) ; The continuation for 'a'. It takes the result 'v1'.
                       (cps1 b ; Recursively transform the second operand 'b'.
                             (lambda (v2) ; The continuation for 'b'. It takes the result 'v2'.
                                   (ctx `(,op ,v1 ,v2))))))] ; Apply the original context 'ctx' to the expression formed by the operator 'op' and the CPSed results of the operands 'v1' and 'v2'.

              [(,rator ,rand) ; Matches a function application with a rator (the function) and a rand (the argument).
               (cps1 rator ; Recursively transform the rator.
                     (lambda (r) ; The continuation for the rator. It takes the result 'r' (the function).
                       (cps1 rand ; Recursively transform the operand.
                             (lambda (d) ; The continuation for the operand. It takes the result 'd' (the argument).
                               (cond
                                [(trivial? r) (ctx `(,r ,d))] ; If the rator 'r' is a trivial operator (like zero?, add1, sub1), apply the current context 'ctx' to the application of the operator to the operand.
                                [(eq? ctx ctx0) `(,r ,d k)]  ; tail call. If the current context is the tail context 'ctx0', it means this function application is in a tail position. The CPSed function 'r' is called with the CPSed argument 'd' and the current continuation 'k'.
                                [else ; If the function application is not in a tail position.
                                 (let ([u (fv)]) ; Generate a fresh variable name 'u' for the result.
                                   `(,r ,d (lambda (,u) ,(ctx u))))])))))]))]) ; The CPSed function 'r' is called with the CPSed argument 'd' and a new continuation that takes the result 'u' and applies the original context 'ctx' to it.

      (cps1 exp id))));; Starts the CPS transformation by calling 'cps1' with the input expression 'exp' and the initial identity continuation 'id'.

;;; tests
;; var
(cps 'x) ; Transforms the variable 'x'. The result will be '(k x)' because the initial context is 'id', and 'id' is applied to 'x'.

(cps '(lambda (x) x)) ; Transforms a simple identity lambda function. The result will be '(lambda (x k) (k x))'.

(cps '(lambda (x) (x 1))) ; Transforms a lambda function that applies its argument to 1. The result will be '(lambda (x k) (x 1 k))'.

;; no lambda (will generate identity functions to return to the toplevel)
(cps '(if (f x) a b)) ; Transforms an if expression where the test is a function call.

(cps '(if x (f a) b)) ; Transforms an if expression where the test is a variable.

;; if stand-alone (tail)
(cps '(if x (f a) b)) ; Here, the 'if' is at the top level, so it's in a tail context.

;; if inside if-test (non-tail)
(cps '(lambda (x) (if (f x) a b))) ; The 'if' is inside a lambda, and its result is used by the lambda (implicitly returned), so it's not in a tail context.

(cps '(lambda (x) (if (if x (f a) b) c d))) ; Nested 'if' expressions. The inner 'if' is in the test of the outer 'if'.

;; both branches are trivial, should do some more optimizations
(cps '(lambda (x) (if (if x (zero? a) b) c d)))

;; if inside if-branch (tail)
(cps '(lambda (x) (if t (if x (f a) b) c))) ; The inner 'if' is in the consequent branch of the outer 'if'. If the outer 'if' is in a tail context, the inner one will also be.

;; if inside if-branch, but again inside another if-test (non-tail)
(cps '(lambda (x) (if (if t (if x (f a) b) c) e w)))

;; if as operand (non-tail)
(cps '(lambda (x) (h (if x (f a) b)))) ; The result of the 'if' expression is used as an argument to 'h'.

;; if as operator (non-tail)
(cps '(lambda (x) ((if x (f g) h) c))) ; The result of the 'if' expression is used as the function to be called.

;; why we need more than two names
(cps '(((f a) (g b)) ((f c) (g d)))) ; This example likely demonstrates the need for the fresh variable name generator ('fv') to avoid naming conflicts when transforming complex nested expressions.

;; factorial
(define fact-cps
  (cps
   '(lambda (n)
      ((lambda (fact)
         ((fact fact) n))
       (lambda (fact)
         (lambda (n)
           (if (zero? n)
               1
               (* n ((fact fact) (sub1 n))))))))));; print out CPSed function

(pretty-print fact-cps);; =>
;; '(lambda (n k)
;;    ((lambda (fact k) (fact fact (lambda (v0) (v0 n k))))
;;     (lambda (fact k)
;;       (k
;;        (lambda (n k)
;;          (if (zero? n)
;;            (k 1)
;;            (fact
;;             fact
;;             (lambda (v1) (v1 (sub1 n) (lambda (v2) (k (* n v2))))))))));
;;     k))

((eval fact-cps) 5 (lambda (v) v));; => 120

Explanation of the CPS Transformer:

This Scheme code implements a Continuation-Passing Style (CPS) transformation for a simple subset of the Scheme language. Here’s a breakdown of the key concepts and how the code works:

1. Continuation-Passing Style (CPS):

Why use CPS?

2. The cps Function:

3. Helper Functions:

4. The cps1 Function (The Core Transformation):

5. Tests:

6. Factorial Example:

In summary, this code implements a CPS transformation that aims to:


Back 2025.04.01 Donate