-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathseq.generators.lisp
77 lines (71 loc) · 2.01 KB
/
seq.generators.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
(defpackage #:com.inuoe.seq.generators
(:use
#:cl)
(:import-from
#:alexandria
#:if-let
#:ensure-list
#:parse-body
#:with-gensyms)
(:import-from
#:com.inuoe.seq
#:col-seq
#:seq-first
#:seq-rest
#:lazy-seq)
(:export
#:with-generator
#:generator
#:defgenerator
#:yield
#:yield-break))
(in-package #:com.inuoe.seq.generators)
(defmacro with-generator (&body body)
"Run `body' in an context where `yield' will yield a new value in
the generator, and `yield-break' will end the generator.
Example:
(to-list
(with-generator
(yield 1)
(yield 2)
(when (zerop (random 2))
(yield-break))
(yield 3)))
=>
(1 2)
OR
(1 2 3)
depending on the output of RANDOM."
(with-gensyms (cc)
`(labels ((,cc (,cc)
(multiple-value-bind (value valid next) (funcall ,cc)
(when valid
(cons value (lazy-seq (,cc next)))))))
(lazy-seq
(,cc (lambda ()
(cl-cont:with-call/cc
(flet ((yield (result)
(cl-cont:let/cc ,cc
(values result t ,cc)))
(yield-break ()
(cl-cont:let/cc ,cc
(declare (ignore ,cc))
(values nil nil (lambda () (values nil nil nil))))))
,@body
(yield-break)))))))))
(defmacro generator (&whole whole args &body body)
"Lambda who's body is a generator."
(multiple-value-bind (body decls doc)
(parse-body body :documentation t :whole whole)
`(lambda ,args
,@(ensure-list doc)
,@decls
(with-generator ,@body))))
(defmacro defgenerator (&whole whole name args &body body)
"Like defun, for a function who's body is a generator."
(multiple-value-bind (body decls doc)
(parse-body body :documentation t :whole whole)
`(defun ,name ,args
,@(ensure-list doc)
,@decls
(with-generator ,@body))))