(module generator mzscheme (require (lib "plt-match.ss") (lib "list.ss") (lib "contract.ss") (lib "stxparam.ss") (lib "etc.ss") (lib "control.ss")) (require-for-syntax (lib "stx.ss" "syntax")) ;; We define a generator as ;; ;; (make-generator thunk) ;; ;; where thunk is a zero-arity procedure. ;; ;; The structure (make-generator thunk) is callable ;; as a procedure. (See Section 4.6 of MzScheme language ;; manual for details on this.) (define-values (struct:generator make-generator generator? generator-ref generator-set) (make-struct-type 'generator #f 1 0 #f null #f 0)) (provide/contract (generator? (-> any/c boolean?))) ;; We raise exn:fail:generator-exhausted ;; when we reach the end of a generator. (define-struct (exn:fail:generator-exhausted exn:fail) ()) (provide (struct exn:fail:generator-exhausted ())) (define-syntax (loop-forever stx) (syntax-case stx () [(_ e1 e2 ...) (syntax/loc stx (let loop () e1 e2 ... (loop)))])) (provide/contract (rename ext:make-generator make-generator (((-> any/c any) . -> . any) . -> . generator?))) ;; make-generator: ((X -> any) -> any) -> (generator-of X) ;; Creates a generator, given a seed function that accepts ;; a yield function. Whenever the seed-function wants to ;; pass values back, it should call the yield function with ;; that value. ;; ;; For example: (make-generator ;; (lambda (yield) (yield 1) (yield 2))) ;; produces a generator that yields 1 and 2. (define (ext:make-generator seed-function) (local ( ;; A yielded-value is one of the following: ;; 1. (make-yielded-datum datum) ;; 2. (make-yielded-exn exn) (define-struct yielded-datum (datum)) (define-struct yielded-exn (exn)) (define toplevel (case-lambda [() (toplevel (void))] [(resumption-val) ;; We save the continuation marks before the prompt ;; to give a cleaner stack trace when the generator ;; exhausts. (set! cont-marks (current-continuation-marks)) (match (prompt (resume-here resumption-val)) ((struct yielded-datum (datum)) datum) ((struct yielded-exn (exn)) (raise exn)))])) (define cont-marks (current-continuation-marks)) ;; Initially, resume-here is bound to a function that does ;; the seed-function. As we generate new values, resume-here ;; will be rebound to the remainder of the work. (define (resume-here resume-value) (with-handlers ((exn:fail? (lambda (exn) (loop-forever (yield-exn exn))))) (seed-function yield-datum) (loop-forever (yield-generator-exhausted)))) ;; yield-datum: any/c -> void ;; Suspends the generator computation, and return ;; the yielded value back. (define (yield-datum v) (control new-save-point (set! resume-here new-save-point) (make-yielded-datum v))) ;; yield-exn: exn:fail? -> void ;; Suspends computation, signals a user error. (define (yield-exn exn) (control new-save-point (set! resume-here new-save-point) (make-yielded-exn exn))) ;; yield-exn: -> void ;; Suspends computation, signals generator exhaustion. (define (yield-generator-exhausted) (control new-save-point (set! resume-here new-save-point) (make-yielded-exn (make-exn:fail:generator-exhausted (string->immutable-string (format "generator ~a exhausted" (object-name seed-function))) cont-marks))))) (make-generator toplevel))) (provide/contract (generator-next (case-> [generator? . -> . any] [generator? (exn:fail? . -> . any) . -> . any] [generator? (exn:fail? . -> . any) any/c . -> . any]))) ;; generator-next: (generator-of X) -> X ;; generator-next: (generator-of X) (exn:fail -> Y) -> (union X Y) ;; generator-next: (generator-of X) (exn:fail -> Y) any -> (union X Y) ;; ;; Returns the next element in the generator. ;; ;; With the first form, if the generator is exhausted, ;; raises exn:fail:generator-exhausted. ;; ;; With the second form, if the generator is exhaused, ;; returns the value of applying the fail-function. ;; ;; The third form also allows one to communicate a value back to ;; the generator at the resumption point. (define generator-next (case-lambda [(gen) (generator-next gen (lambda (exn) (raise exn)) (void))] [(gen exhausted-function) (generator-next gen exhausted-function (void))] [(gen exhausted-function resumption-val) (with-handlers ([exn:fail:generator-exhausted? (lambda (exn) (exhausted-function exn))]) (gen resumption-val))])) ;; leftmost-identifier: syntax -> (union #f syntax) ;; Looks for the first leftmost identifier syntax object in stx. ;; If one can't be found, returns #f. (define-for-syntax (leftmost-identifier stx) (cond [(identifier? stx) stx] [(stx-null? stx) #f] [(stx-pair? stx) (or (leftmost-identifier (stx-car stx)) (leftmost-identifier (stx-cdr stx)))] [else #f])) ; (provide define-generator*) ; ;; Hygienic macro for writing generator functions. ; ;; ; ;; For example, ; ;; ; ;; ;; nums: number -> (generator-of number) ; ;; (define-generator* (nums n) yield ; ;; (let loop ((n n)) ; ;; (yield n) ; ;; (loop (add1 n)))) ; ;; ; ;; produces a generator function. when called, returns a ; ;; generator that yields successive numbers, starting from n. ; ;; ; ;; ; ;; We also support mzscheme's curry notation, so: ; ;; ; ;; (define-generator* ((foo x) y) ba! ; ;; (ba! x) ; ;; (ba! y)) ; ;; ; ;; will also do the expected thing. ; (define-syntax (define-generator* stx) ; (syntax-case stx () ; [(_ (generator-name-or-curried-form args ...) yield-kw ; body body-rest ...) ; (unless (identifier? #'yield-kw) ; (raise-syntax-error #f "yield-kw must be an identifier" ; stx #'yield-kw)) ; (with-syntax ; ([function-name ; (leftmost-identifier ; (syntax/loc stx generator-name-or-curried-form))]) ; (syntax/loc stx ; (define (generator-name-or-curried-form args ...) ; ;; we wrap an additional LET here so that OBJECT-NAME ; ;; can derive a good name. ; (let ([function-name ; (lambda (yield-kw) ; body body-rest ...)]) ; (make-generator ; function-name)))))] ; [else (raise-syntax-error ; #f ; "expected (define-generator* (name args ...) yield-kw body ...)" ; stx)])) (provide yield) ;; YIELD syntactic parameter; we syntax-parameterize this in ;; define-generator. (define-syntax-parameter yield (lambda (stx) (raise-syntax-error #f "used outside of the context of a DEFINE-GENERATOR" stx))) (provide define-generator) ;; Macro for writing generator functions with convenient ;; yield keyword. Cooperates with the YIELD syntax parameter ;; above. ;; ;; For example, ;; ;; ;; nums: number -> (generator-of number) ;; (define-generator (nums n) ;; (let loop ((n n)) ;; (yield n) ;; (loop (add1 n)))) ;; ;; produces a generator function. When called, returns a ;; generator that yields successive numbers, starting from n. ;; ;; We also support mzscheme's curry notation, so: ;; ;; (define-generator ((flip-flop x) y) ;; (let loop () ;; (yield x) ;; (yield y) ;; (loop))) ;; ;; will also do the expected thing. (define-syntax (define-generator stx) (syntax-case stx () [(_ (name-or-curried . args) body body-rest ...) (with-syntax ([function-name (leftmost-identifier #'name-or-curried)]) (syntax/loc stx (define (name-or-curried . args) ;; we wrap an additional LET here so that OBJECT-NAME ;; can derive a good name. (let ([function-name (lambda (real-yield) (with-yield-rebound real-yield body body-rest ...))]) (ext:make-generator function-name)))))] [else (raise-syntax-error #f "expected (define-generator (name args ...) body ...)" stx)])) ;; with-yield-rebound ;; in the dynamic context of with-yield-rebound, real-yield ;; replaces yield in the body. (define-syntax (with-yield-rebound stx) (syntax-case stx () [(_ real-yield body ...) ;; Within the context of this body, we establish ;; the YIELD syntax parameter to be redirected to ;; REAL-YIELD. (syntax/loc stx (syntax-parameterize [(yield (lambda (stx) (syntax-case stx () [_ (identifier? stx) (syntax/loc stx real-yield)] [(yield value) (syntax/loc stx (real-yield value))])))] body ...))])) (provide/contract (generator-fold ((any/c any/c . -> . any) any/c generator? . -> . any))) ;; generator-fold: (X Y -> Y) Y (generator-of X) ;; Applies a fold of f across all the elements in the generator gen, ;; using the initial accumulator acc. (define (generator-fold f initial-acc gen) (let loop ((acc initial-acc)) (let-values ([(finished? new-acc) (with-handlers ([exn:fail:generator-exhausted? (lambda (_) (values #t acc))]) (values #f (f (gen) acc)))]) (cond [finished? acc] [else (loop new-acc)])))) (provide/contract (generator-for-each ((any/c . -> . any) generator? . -> . any))) ;; generator-for-each: (X -> any) (generator-of X) -> any ;; Applies f along every elements in generator gen. (define (generator-for-each f gen) (generator-fold (lambda (x acc) (f x)) (void) gen)) (provide/contract (list/gen (-> (listof any/c) generator?))) ;; list/gen: (listof A) -> (-> A) ;; Given a list, returns a function iterates over ;; the elements of lst. ;; When the list is exhausted, raises exn:fail:generator-exhausted. (define-generator (list/gen lst) (for-each yield lst)) (provide/contract (list->flattened/gen (-> (listof any/c) generator?))) ;; list->flattened/gen: (listof X) -> (generator-of datum) ;; Given a nested list of datums, ;; returns a generator that only returns datums. (define-generator (list->flattened/gen lst) (let loop ((datum lst)) (cond [(empty? datum) 'done] [(pair? datum) (loop (first datum)) (loop (rest datum))] [else (yield datum)]))))