File size: 6,050 Bytes
43203b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
(cl:in-package :cl-flow)

(defvar *current-context* nil)
(defvar *continue* nil)
(defvar *parent-context* nil)

(define-constant +min-stack-extension+ 3)

(defstruct flow-context
  (native-dispatcher nil
   :type (or null function)
   :read-only t)
  (dispatcher nil
   :type (or null function))
  (value nil
   :type t)
  (function nil
   :type (or null function))
  (stack (make-array +min-stack-extension+ :element-type 'list :initial-element nil
                                           :fill-pointer 0 :adjustable t)
   :type array
   :read-only t)
  (parent nil
   :type (or null flow-context)
   :read-only t))


(defun dispatch (context task invariant &rest args &key &allow-other-keys)
  (setf (flow-context-function context) task)
  (apply (flow-context-dispatcher context) invariant args))


(defun dispatch-rest (context)
  (declare (type flow-context context)
           #.+optimize-form+)
  (cond
    ((eq *current-context* context) (setf *continue* t))
    ((eq *parent-context* context)
     ;; see catch in #'%dispatch-rest:
     ;; this is to unwind a stack
     ;; to avoid overflowing it
     ;; in case of single-threaded dispatch
     (throw *parent-context* t))
    (t (%dispatch-rest context))))


(defun make-child-flow-context (parent-context)
  (init-context-dispatcher
   (make-flow-context :native-dispatcher (flow-context-native-dispatcher parent-context)
                      :value (flow-context-value parent-context)
                      :parent parent-context)))


(defun %dispatch-rest (flow-context)
  (declare (type flow-context flow-context)
           #.+optimize-form+)
  (let ((*parent-context* *current-context*)
        (*current-context* flow-context)
        (*continue* nil))
    (loop for block = (chop-head flow-context)
          do (setf *continue* nil)
             (when block
               (if (listp block)
                   (progn
                     (push-flow-stack flow-context block)
                     (setf *continue* t))
                   (when (catch *current-context*
                           (funcall block flow-context)
                           nil)
                     (setf *continue* t))))
          while *continue*)))


(defun capture-flow-value (context value)
  (setf (flow-context-value context) value))


(defun push-flow-stack (context flow)
  (vector-push-extend flow (flow-context-stack context) +min-stack-extension+))


(defun chop-head (context)
  (let ((stack (flow-context-stack context)))
    (symbol-macrolet ((current (aref stack (1- (length stack)))))
      (flet ((%chop-head ()
               (let ((top current))
                 (if (listp top)
                     (let ((head (first top))
                           (tail (rest top)))
                       (prog1 head
                         (if tail
                             (setf current tail)
                             (vector-pop stack))))
                     (vector-pop stack)))))
        (loop while (> (length stack) 0)
                thereis (%chop-head))))))


;;;
;;; RESTARTS
;;;
(defun try-restart (restart-name &optional (arg nil provided-p))
  (if provided-p
      (invoke-restart restart-name arg)
      (invoke-restart restart-name)))


(defun rerun-flow-block ()
  (try-restart 'rerun-flow-block))


(defun skip-flow-block ()
  (try-restart 'skip-flow-block))


(defun inject-flow (flow)
  (try-restart 'inject-flow flow))


(defun use-flow-block-value (value)
  (try-restart 'use-flow-block-value value))


(defun %%rerun-invoke ()
  (throw 'begin (values nil t nil)))

(defun %%rerun-invoke-text (stream)
  (format stream "Rerun current flow block"))

(defun %%skip-invoke ()
  (throw 'begin (values nil nil nil)))

(defun %%skip-invoke-text (stream)
  (format stream "Skip flow block returning nil"))

(defun %%use-invoke (value)
  (throw 'begin (values value nil nil)))

(defun %%use-invoke-text (stream)
  (format stream "Skip flow block returning provided value"))

(defun %%inject-invoke (flow)
  (throw 'begin (values nil nil flow)))

(defun %%inject-invoke-text (stream)
  (format stream "Inject flow to run instead of current block"))

(defun %invoke-with-restarts (fu arg)
  (declare (type (function (t) *) fu)
           #.+optimize-form+)
  ;; catch+restart-bind insead of tagbody+restart-case to avoid any consing
  (catch 'begin
    (restart-bind ((rerun-flow-block #'%%rerun-invoke
                                     :report-function #'%%rerun-invoke-text)
                   (skip-flow-block #'%%skip-invoke
                                    :report-function #'%%skip-invoke-text)
                   (use-flow-block-value #'%%use-invoke
                                         :report-function #'%%use-invoke-text)
                   (inject-flow #'%%inject-invoke
                                :report-function #'%%inject-invoke-text))
      (values (funcall fu arg) nil nil))))


(defun invoke-with-restarts (flow-context fu arg)
  (declare (type (function (t) *) fu)
           #.+optimize-form+)
  ;; loop instead tagbody to avoid any consing
  (loop do (multiple-value-bind (result looping-p flow)
               (%invoke-with-restarts fu arg)
             (cond
               (flow (push-flow-stack flow-context flow)
                     (dispatch-rest flow-context)
                     (return))
               ((not looping-p) (return result))))))


(defun invoke-flow-function (context)
  (capture-flow-value context (invoke-with-restarts context
                                                    (flow-context-function context)
                                                    (flow-context-value context)))
  (dispatch-rest context))


(defun init-context-dispatcher (context)
  (let ((dispatcher (flow-context-native-dispatcher context)))
    (labels ((%invoke ()
               (invoke-flow-function context))
             (%dispatcher (invariant &rest args &key &allow-other-keys)
               (apply dispatcher #'%invoke invariant args)))
      (setf (flow-context-dispatcher context) #'%dispatcher)))
  context)