(module generator mzscheme
(require (lib "plt-match.ss")
(lib "list.ss")
(lib "contract.ss")
(lib "stxparam.ss"))
(require-for-syntax (lib "stx.ss" "syntax"))
(define-struct (exn:fail:generator-exhausted exn:fail) ())
(provide (struct exn:fail:generator-exhausted ()))
(define-struct yielded-datum (datum))
(define-struct yielded-exn (exn))
(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?)))
(define-syntax (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?)))
(define (ext:make-generator seed-function)
(let
([saved-point (void)]
[caller (void)]
[caller-continuation-marks (void)])
(letrec ([entry-point
(lambda ()
(set-caller-context!)
(let ([yielded
(call/cc (lambda (k)
(set! caller k)
(cond
[(restoring-from-save-point?)
(restore-from-save-point!)]
[else (do-work)])))])
(match yielded
((struct yielded-datum (datum)) datum)
((struct yielded-exn (exn)) (raise exn)))))]
[restoring-from-save-point?
(lambda () (not (void? saved-point)))]
[restore-from-save-point!
(lambda ()
(saved-point 'resume))]
[set-save-point!
(lambda (f) (set! saved-point f))]
[set-caller-context!
(lambda ()
(set! caller-continuation-marks
(current-continuation-marks)))]
[do-work
(lambda ()
(with-handlers ((exn:fail?
(lambda (exn)
(forever (yield-exn exn)))))
(seed-function yield-datum))
(forever (yield-generator-exhausted)))]
[yield-datum
(lambda (v)
(call/cc
(lambda (k)
(set-save-point! k)
(caller (make-yielded-datum v)))))]
[yield-exn
(lambda (exn)
(call/cc
(lambda (k)
(set-save-point! k)
(caller (make-yielded-exn exn)))))]
[yield-generator-exhausted
(lambda ()
(call/cc
(lambda (k)
(set-save-point! k)
(caller
(make-yielded-exn
(make-exn:fail:generator-exhausted
(string->immutable-string
(format "generator ~a exhausted"
(object-name seed-function)))
caller-continuation-marks))))))])
(make-generator entry-point))))
(provide/contract (generator-next
(case->
[generator? (exn:fail? . -> . any) . -> . any]
[generator? . -> . any])))
(define generator-next
(case-lambda
[(gen exhausted-function)
(with-handlers ([exn:fail:generator-exhausted?
(lambda (exn)
(exhausted-function exn))])
(gen))]
[(gen)
(generator-next gen (lambda (exn) (raise exn)))]))
(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 (error 'leftmost-identifier "don't know how to handle ~a" stx)]))
(provide yield)
(define-syntax-parameter yield
(lambda (stx)
(raise-syntax-error
#f
"used outside of the context of a DEFINE-GENERATOR"
stx)))
(provide define-generator)
(define-syntax (define-generator stx)
(syntax-case stx ()
[(_ (name-or-curried . args) body body-rest ...)
(with-syntax
([function-name
(leftmost-identifier
(syntax/loc stx generator-name-or-curried-form))])
(syntax/loc stx
(define (name-or-curried . args)
(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)]))
(define-syntax (with-yield-rebound stx)
(syntax-case stx ()
[(_ real-yield body ...)
(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)))
(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)))
(define (generator-for-each f gen)
(generator-fold (lambda (x acc) (f x)) (void) gen))
(provide/contract (list/gen (-> (listof any/c) generator?)))
(define-generator (list/gen lst)
(for-each yield lst))
(provide/contract (list->flattened/gen (-> (listof any/c) generator?)))
(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)]))))