-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
vm.lisp
209 lines (167 loc) · 7.01 KB
/
vm.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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
(in-package #:org.shirakumo.fraf.speechless)
(defclass request () ())
(defclass input-request (request) ())
(defclass target-request (request)
((target :initarg :target :reader target)))
(defclass text-request ()
((text :initarg :text :reader text)
(markup :initarg :markup :reader markup)))
(defmethod print-object ((request text-request) stream)
(print-unreadable-object (request stream :type T :identity T)
(format stream "~s" (text request))))
(defclass choice-request (input-request)
((choices :initarg :choices :reader choices)
(targets :initarg :targets :reader targets)))
(defmethod print-object ((request choice-request) stream)
(print-unreadable-object (request stream :type T :identity T)
(format stream "~s" (choices request))))
(defclass confirm-request (input-request target-request text-request)
())
(defclass clear-request (target-request)
())
(defclass emote-request (text-request target-request)
((emote :initarg :emote :reader emote)))
(defmethod print-object ((request emote-request) stream)
(print-unreadable-object (request stream :type T :identity T)
(format stream "~s ~s" (emote request) (text request))))
(defclass pause-request (text-request target-request)
((duration :initarg :duration :reader duration)))
(defmethod print-object ((request pause-request) stream)
(print-unreadable-object (request stream :type T :identity T)
(format stream "~fs ~s" (duration request) (text request))))
(defclass source-request (target-request)
((name :initarg :name :reader name)))
(defclass end-request (request)
())
(defclass vm ()
((instructions :initform () :accessor instructions)
(text-buffer :initform (make-string-output-stream) :reader text-buffer)
(choices :initform () :accessor choices)
(markup :initform () :accessor markup)))
(defgeneric execute (instruction vm ip))
(defmethod text ((vm vm))
(let ((string (get-output-stream-string (text-buffer vm))))
(write-string string (text-buffer vm))
string))
(defmethod pop-text ((vm vm))
(values (get-output-stream-string (text-buffer vm))
(shiftf (markup vm) ())))
(defmethod run (assembly (vm (eql T)))
(run assembly (make-instance 'vm)))
(defmethod run ((assembly assembly) (vm vm))
(setf (instructions vm) (instructions assembly))
(reset vm))
(defmethod run ((string string) (vm vm))
(run (compile* string T) vm))
(defmethod reset ((vm vm))
(get-output-stream-string (text-buffer vm))
(setf (markup vm) ())
(setf (choices vm) ())
vm)
(defmethod resume ((vm vm) ip)
(catch 'suspend
(loop with instructions = (instructions vm)
while (< ip (length instructions))
do (setf ip (execute (aref instructions ip) vm ip)))
(make-instance 'end-request)))
(defmethod suspend ((vm vm) return)
(throw 'suspend return))
(defmethod execute ((instruction noop) (vm vm) ip)
(1+ ip))
(defmethod execute ((instruction jump) (vm vm) ip)
(target instruction))
(defmethod execute ((instruction text) (vm vm) ip)
(write-string (text instruction) (text-buffer vm))
(1+ ip))
(defmethod execute ((instruction confirm) (vm vm) ip)
(multiple-value-bind (text markup) (pop-text vm)
(suspend vm (make-instance 'confirm-request
:target (1+ ip)
:markup markup
:text text))))
(defmethod execute ((instruction clear) (vm vm) ip)
(suspend vm (make-instance 'clear-request
:target (1+ ip))))
(defmethod execute ((instruction dispatch) (vm vm) ip)
(nth (funcall (func instruction))
(targets instruction)))
(defmethod execute ((instruction conditional) (vm vm) ip)
(loop for (func . target) in (clauses instruction)
do (when (funcall func)
(return target))
finally (error "WTF: No valid branch found in conditional.")))
(defmethod execute ((instruction choose) (vm vm) ip)
(let ((choices (nreverse (shiftf (choices vm) ()))))
(suspend vm (make-instance 'choice-request
:choices (mapcar #'car choices)
:targets (mapcar #'cdr choices)))))
(defmethod execute ((instruction commit-choice) (vm vm) ip)
(let ((choice (get-output-stream-string (text-buffer vm))))
(when (string/= "" choice)
(push (cons choice (target instruction))
(choices vm))))
(1+ ip))
(defmethod execute ((instruction eval) (vm vm) ip)
(funcall (func instruction))
(1+ ip))
(defmethod execute ((instruction begin-mark) (vm vm) ip)
(let ((markup (list* (file-position (text-buffer vm)) NIL (markup instruction))))
(setf (markup (end instruction)) markup)
(push markup (markup vm)))
(1+ ip))
(defmethod execute ((instruction end-mark) (vm vm) ip)
(setf (second (markup instruction))
(file-position (text-buffer vm)))
(1+ ip))
(defmethod execute ((instruction placeholder) (vm vm) ip)
(princ (funcall (func instruction))
(text-buffer vm))
(1+ ip))
(defmethod execute ((instruction emote) (vm vm) ip)
(multiple-value-bind (text markup) (pop-text vm)
(suspend vm (make-instance 'emote-request
:emote (emote instruction)
:target (1+ ip)
:markup markup
:text text))))
(defmethod execute ((instruction pause) (vm vm) ip)
(multiple-value-bind (text markup) (pop-text vm)
(suspend vm (make-instance 'pause-request
:duration (duration instruction)
:target (1+ ip)
:markup markup
:text text))))
(defmethod execute ((instruction source) (vm vm) ip)
(suspend vm (make-instance 'source-request
:target (1+ ip)
:name (name instruction))))
(defun simulate (thing)
(let ((vm (run (typecase thing
(assembly thing)
(T (compile* thing)))
(make-instance 'vm)))
(ip 0))
(macrolet ((with-types (thing &body clauses)
`(progn
,@ (loop for (type . body) in clauses
collect `(when (typep ,thing ',type)
,@body)))))
(loop for request = (resume vm ip)
do (with-types request
(text-request
(format T "~a~@[[~a]~]" (text request) (markup request)))
(emote-request
(format T "(~a)" (emote request)))
(source-request
(format T "~&- ~a~%" (name request)))
(clear-request
(format T "~%"))
(pause-request
(sleep (duration request)))
(choice-request
(format T "~& Choose ~{~a~^, ~}:~%> " (choices request))
(setf ip (aref (targets request) (read))))
(target-request
(setf ip (target request)))
(end-request
(return)))))))