Show More
@@ -0,0 +1,8 b'' | |||
|
1 | # begin | |
|
2 | x = -1 | |
|
3 | gt 'loc', x | |
|
4 | - | |
|
5 | # loc | |
|
6 | if args[0]: 'args[0] = -1' | |
|
7 | 'end' | |
|
8 | - |
@@ -0,0 +1,19 b'' | |||
|
1 | # begin | |
|
2 | func('getObjByKey', 'item') | |
|
3 | - | |
|
4 | ||
|
5 | # getObjByKey | |
|
6 | $needle = $args[0] | |
|
7 | i = 1 | |
|
8 | :lab | |
|
9 | if i <= countobj: | |
|
10 | if $getobj(i) = $needle: | |
|
11 | result = i | |
|
12 | exit | |
|
13 | else | |
|
14 | i += 1 | |
|
15 | jump 'lab' | |
|
16 | end | |
|
17 | end | |
|
18 | result = -1 | |
|
19 | - |
@@ -1,510 +1,523 b'' | |||
|
1 | 1 | |
|
2 | 2 | (in-package txt2web.api) |
|
3 | 3 | |
|
4 | 4 | ;;; API deals with DOM manipulation and some bookkeeping for the |
|
5 | 5 | ;;; intrinsics, namely variables |
|
6 | 6 | ;;; API is an implementation detail and has no QSP documentation. It |
|
7 | 7 | ;;; doesn't call intrinsics |
|
8 | 8 | |
|
9 | 9 | ;;; Utils |
|
10 | 10 | |
|
11 | 11 | (defun make-act-html (title img) |
|
12 | 12 | (+ "<a class='qsp-act' href='" (href-call call-act title) "' onmouseover='" (inline-call select-act title) "'>" |
|
13 | 13 | (if img (+ "<img src='" img "'>") "") |
|
14 | 14 | title |
|
15 | 15 | "</a>")) |
|
16 | 16 | |
|
17 | 17 | (defun make-menu-item-html (num title img loc) |
|
18 | 18 | (+ "<a href='" (href-call finish-menu loc) "'>" |
|
19 | 19 | (if img (+ "<img src='" img "'>") "") |
|
20 | 20 | title |
|
21 | 21 | "</a>")) |
|
22 | 22 | |
|
23 | 23 | (defun make-obj (title img selected) |
|
24 | 24 | (+ "<li onclick='" (inline-call select-obj title img) "'>" |
|
25 | 25 | "<a class='qsp-obj" (if selected " qsp-obj-selected" "") "'>" |
|
26 | 26 | (if img (+ "<img src='" img "'>") "") |
|
27 | 27 | title |
|
28 | 28 | "</a>")) |
|
29 | 29 | |
|
30 | 30 | (defun make-menu-delimiter () |
|
31 | 31 | "<hr>") |
|
32 | 32 | |
|
33 | 33 | (defun copy-obj (obj) |
|
34 | 34 | (chain *j-s-o-n (parse (chain *j-s-o-n (stringify obj))))) |
|
35 | 35 | |
|
36 | 36 | (defun report-error (text) |
|
37 | 37 | (alert text)) |
|
38 | 38 | |
|
39 | 39 | (defun start-sleeping () |
|
40 | 40 | (chain (by-id "qsp") class-list (add "disable"))) |
|
41 | 41 | |
|
42 | 42 | (defun finish-sleeping () |
|
43 | 43 | (chain (by-id "qsp") class-list (remove "disable"))) |
|
44 | 44 | |
|
45 | 45 | (defun sleep (msec) |
|
46 | 46 | (with-sleep (resume) |
|
47 | 47 | (set-timeout resume msec))) |
|
48 | 48 | |
|
49 | 49 | (defun init-dom () |
|
50 | 50 | ;; Save/load buttons |
|
51 | 51 | (let ((btn (by-id "qsp-btn-save"))) |
|
52 | 52 | (setf (@ btn onclick) savegame) |
|
53 | 53 | (setf (@ btn href) "#")) |
|
54 | 54 | (let ((btn (by-id "qsp-btn-open"))) |
|
55 | 55 | (setf (@ btn onclick) opengame) |
|
56 | 56 | (setf (@ btn href) "#")) |
|
57 | 57 | ;; Close image on click |
|
58 | 58 | (setf (@ (by-id "qsp-image-container") onclick) |
|
59 | 59 | show-image) |
|
60 | 60 | ;; Enter in input field |
|
61 | 61 | (setf (@ (get-frame :input) onkeyup) |
|
62 | 62 | on-input-key) |
|
63 | 63 | ;; Close the dropdown on any click |
|
64 | 64 | (setf (@ window onclick) |
|
65 | 65 | (lambda (event) |
|
66 | 66 | (setf (@ window mouse) |
|
67 | 67 | (list (@ event page-x) |
|
68 | 68 | (@ event page-y))) |
|
69 | 69 | (finish-menu nil)))) |
|
70 | 70 | |
|
71 | (defun init-globals (game-name) | |
|
72 | (chain *object (assign *globals (getprop *default-globals game-name)))) | |
|
73 | ||
|
71 | 74 | (defun call-serv-loc (var-name &rest args) |
|
72 | 75 | (let ((loc-name (get-global var-name 0))) |
|
73 | 76 | (when loc-name |
|
74 | 77 | (let ((loc (getprop *locs loc-name))) |
|
75 | 78 | (when loc |
|
76 | 79 | (call-loc loc-name args)))))) |
|
77 | 80 | |
|
78 | 81 | (defun filename-game (filename) |
|
79 | 82 | (let ((game-name (chain filename (match "(.*/)?([^.]+)(\\.[a-zA-Z]+)?") 2)))) |
|
80 | 83 | (getprop *games game-name)) |
|
81 | 84 | |
|
82 | 85 | (defun run-game (name) |
|
83 | 86 | (let ((game (filename-game name))) |
|
84 | 87 | (setf *main-game name) |
|
85 | 88 | ;; Replace locations with the new game's |
|
86 | 89 | (setf *locs game) |
|
87 | 90 | (funcall (getprop game |
|
88 | 91 | (chain *object (keys game) 0)) |
|
89 | 92 | (list)))) |
|
90 | 93 | |
|
91 | 94 | ;;; Misc |
|
92 | 95 | |
|
93 | 96 | (defun newline (key) |
|
94 | 97 | (append-id (key-to-id key) "<br>" t)) |
|
95 | 98 | |
|
96 | 99 | (defun clear-id (id) |
|
97 | 100 | (setf (inner-html (by-id id)) "")) |
|
98 | 101 | |
|
99 | 102 | (defun escape-html (text) |
|
100 | 103 | (chain text |
|
101 | 104 | (replace (regex "/&/g") "&") |
|
102 | 105 | (replace (regex "/</g") "<") |
|
103 | 106 | (replace (regex "/>/g") ">") |
|
104 | 107 | (replace (regex "/\"/g") """) |
|
105 | 108 | (replace (regex "/'/g") "'"))) |
|
106 | 109 | |
|
107 | 110 | (defun prepare-contents (s &optional force-html) |
|
108 | 111 | (setf s (chain s (to-string))) |
|
109 | 112 | (if (or force-html (get-global "USEHTML" 0)) |
|
110 | 113 | s |
|
111 | 114 | (escape-html s))) |
|
112 | 115 | |
|
113 | 116 | (defun get-id (id &optional force-html) |
|
114 | 117 | (inner-html (by-id id))) |
|
115 | 118 | |
|
116 | 119 | (defun set-id (id contents &optional force-html) |
|
117 | 120 | (setf (inner-html (by-id id)) (prepare-contents contents force-html))) |
|
118 | 121 | |
|
119 | 122 | (defun append-id (id contents &optional force-html) |
|
120 | 123 | (when contents |
|
121 | 124 | (incf (inner-html (by-id id)) (prepare-contents contents force-html)))) |
|
122 | 125 | |
|
123 | 126 | (defun on-input-key (ev) |
|
124 | 127 | (when (= 13 (@ ev key-code)) |
|
125 | 128 | (chain ev (prevent-default)) |
|
126 | 129 | (call-serv-loc "$USERCOM"))) |
|
127 | 130 | |
|
128 | 131 | ;;; Function calls |
|
129 | 132 | |
|
130 | 133 | (defun init-args (args) |
|
131 |
(dotimes (i |
|
|
134 | (dotimes (i 10) | |
|
135 | (set-global "ARGS" i 0) | |
|
136 | (set-global "$ARGS" i "") | |
|
137 | (when (< i (length args)) | |
|
132 | 138 | (let ((arg (elt args i))) |
|
133 | 139 | (if (numberp arg) |
|
134 |
(set- |
|
|
135 |
(set- |
|
|
140 | (set-global "ARGS" i arg) | |
|
141 | (set-global "$ARGS" i arg)))))) | |
|
136 | 142 | |
|
137 | 143 | (defun get-result () |
|
138 | 144 | (or (get-global "$RESULT" 0) |
|
139 | 145 | (get-global "RESULT" 0))) |
|
140 | 146 | |
|
141 | 147 | (defun call-loc (name args) |
|
142 | 148 | (setf name (chain name (to-upper-case))) |
|
143 | 149 | (with-frame |
|
144 | 150 | (with-call-args args |
|
145 | 151 | (funcall (getprop *locs name)))) |
|
146 | 152 | (void)) |
|
147 | 153 | |
|
148 | 154 | (defun call-act (title) |
|
149 | 155 | (with-frame |
|
150 | 156 | (funcall (getprop *acts title :act))) |
|
151 | 157 | (void)) |
|
152 | 158 | |
|
153 | 159 | ;;; Text windows |
|
154 | 160 | |
|
155 | 161 | (defun key-to-id (key) |
|
156 | 162 | (case key |
|
157 | 163 | (:all "qsp") |
|
158 | 164 | (:main "qsp-main") |
|
159 | 165 | (:stat "qsp-stat") |
|
160 | 166 | (:objs "qsp-objs") |
|
161 | 167 | (:acts "qsp-acts") |
|
162 | 168 | (:input "qsp-input") |
|
163 | 169 | (:image "qsp-image") |
|
164 | 170 | (:dropdown "qsp-dropdown") |
|
165 | 171 | (t (report-error "Internal error!")))) |
|
166 | 172 | |
|
167 | 173 | (defun get-frame (key) |
|
168 | 174 | (by-id (key-to-id key))) |
|
169 | 175 | |
|
170 | 176 | (defun add-text (key text) |
|
171 | 177 | (append-id (key-to-id key) text)) |
|
172 | 178 | |
|
173 | 179 | (defun get-text (key) |
|
174 | 180 | (get-id (key-to-id key))) |
|
175 | 181 | |
|
176 | 182 | (defun clear-text (key) |
|
177 | 183 | (clear-id (key-to-id key))) |
|
178 | 184 | |
|
179 | 185 | (defun enable-frame (key enable) |
|
180 | 186 | (let ((obj (get-frame key))) |
|
181 | 187 | (setf (@ obj style display) (if enable "block" "none")) |
|
182 | 188 | (void))) |
|
183 | 189 | |
|
184 | 190 | ;;; Actions |
|
185 | 191 | |
|
186 | 192 | (defun add-act (title img act) |
|
187 | 193 | (setf (getprop *acts title) |
|
188 | 194 | (create :title title :img img :act act :selected nil)) |
|
189 | 195 | (update-acts)) |
|
190 | 196 | |
|
191 | 197 | (defun del-act (title) |
|
192 | 198 | (delete (getprop *acts title)) |
|
193 | 199 | (update-acts)) |
|
194 | 200 | |
|
195 | 201 | (defun clear-act () |
|
196 | 202 | (setf *acts (create)) |
|
197 | 203 | (update-acts)) |
|
198 | 204 | |
|
199 | 205 | (defun update-acts () |
|
200 | 206 | (clear-id "qsp-acts") |
|
201 | 207 | (let ((elt (by-id "qsp-acts"))) |
|
202 | 208 | (for-in (title *acts) |
|
203 | 209 | (let ((obj (getprop *acts title))) |
|
204 | 210 | (incf (inner-html elt) (make-act-html title (getprop obj :img))))))) |
|
205 | 211 | |
|
206 | 212 | (defun select-act (title) |
|
207 | 213 | (loop :for (k v) :of *acts |
|
208 | 214 | :do (setf (getprop v :selected) nil)) |
|
209 | 215 | (setf (getprop *acts title :selected) t) |
|
210 | 216 | (call-serv-loc "$ONACTSEL")) |
|
211 | 217 | |
|
212 | 218 | ;;; Variables |
|
213 | 219 | |
|
214 | 220 | (defun new-var (slot &rest indexes) |
|
215 | 221 | (let ((v (list))) |
|
216 | 222 | (dolist (index indexes) |
|
217 | 223 | (setf (elt v index) (if (eq #\$ (elt slot 0)) "" 0))) |
|
218 | 224 | (setf (@ v :indexes) (create)) |
|
219 | 225 | v)) |
|
220 | 226 | |
|
221 | 227 | (defun set-str-element (slot index value) |
|
222 | 228 | (if (has index (getprop slot :indexes)) |
|
223 | 229 | (setf (elt (getprop slot) |
|
224 | 230 | (getprop slot :indexes index)) |
|
225 | 231 | value) |
|
226 | 232 | (progn |
|
227 | 233 | (chain slot (push value)) |
|
228 | 234 | (setf (elt slot index) |
|
229 | 235 | (length slot)))) |
|
230 | 236 | (void)) |
|
231 | 237 | |
|
232 | 238 | (defun set-any-element (slot index value) |
|
233 | 239 | (cond ((null index) |
|
234 | 240 | (chain (elt slot) (push value))) |
|
235 | 241 | ((numberp index) |
|
236 | 242 | (setf (elt slot index) value)) |
|
237 | 243 | ((stringp index) |
|
238 | 244 | (set-str-element slot index value)) |
|
239 | 245 | (t (report-error "INTERNAL ERROR"))) |
|
240 | 246 | (void)) |
|
241 | 247 | |
|
242 | 248 | (defun set-serv-var (name index value) |
|
243 | 249 | (let ((slot (getprop *globals name))) |
|
244 | 250 | (set-any-element slot index value)) |
|
245 | 251 | (funcall (getprop serv-vars name :body) value index) |
|
246 | 252 | (void)) |
|
247 | 253 | |
|
248 | 254 | (defun get-element (slot index) |
|
249 | 255 | (if (numberp index) |
|
250 | 256 | (elt slot index) |
|
251 | 257 | (elt slot (getprop slot :indexes index)))) |
|
252 | 258 | |
|
259 | (defun set-global (name index value) | |
|
260 | (set-any-element (getprop *globals name) index value)) | |
|
261 | ||
|
253 | 262 | (defun get-global (name index) |
|
254 | (elt (getprop *globals name) index)) | |
|
263 | (get-element (getprop *globals name) index)) | |
|
255 | 264 | |
|
256 |
(defun kill-var ( |
|
|
265 | (defun kill-var (&optional name index) | |
|
266 | (cond (name | |
|
257 | 267 | (setf name (chain name (to-upper-case))) |
|
258 | 268 | (if (and index (not (= 0 index))) |
|
259 | 269 | (chain (getprop *globals name) (kill index)) |
|
260 | (delete (getprop *globals name))) | |
|
270 | (delete (getprop *globals name)))) | |
|
271 | (t | |
|
272 | (setf *globals (create)) | |
|
273 | (init-globals *main-game))) | |
|
261 | 274 | (void)) |
|
262 | 275 | |
|
263 | 276 | (defun array-size (name) |
|
264 | 277 | (@ (var-ref name) :values length)) |
|
265 | 278 | |
|
266 | 279 | ;;; Locals |
|
267 | 280 | |
|
268 | 281 | (defun push-local-frame () |
|
269 | 282 | (chain *locals (push (create))) |
|
270 | 283 | (void)) |
|
271 | 284 | |
|
272 | 285 | (defun pop-local-frame () |
|
273 | 286 | (chain *locals (pop)) |
|
274 | 287 | (void)) |
|
275 | 288 | |
|
276 | 289 | (defun current-local-frame () |
|
277 | 290 | (elt *locals (1- (length *locals)))) |
|
278 | 291 | |
|
279 | 292 | ;;; Objects |
|
280 | 293 | |
|
281 | 294 | (defun select-obj (title img) |
|
282 | 295 | (loop :for (k v) :of *objs |
|
283 | 296 | :do (setf (getprop v :selected) nil)) |
|
284 | 297 | (setf (getprop *objs title :selected) t) |
|
285 | 298 | (call-serv-loc "$ONOBJSEL" title img)) |
|
286 | 299 | |
|
287 | 300 | (defun update-objs () |
|
288 | 301 | (let ((elt (by-id "qsp-objs"))) |
|
289 | 302 | (setf (inner-html elt) "<ul>") |
|
290 | 303 | (loop :for (name obj) :of *objs |
|
291 | 304 | :do (incf (inner-html elt) |
|
292 | 305 | (make-obj name (@ obj :img) (@ obj :selected)))) |
|
293 | 306 | (incf (inner-html elt) "</ul>"))) |
|
294 | 307 | |
|
295 | 308 | ;;; Menu |
|
296 | 309 | |
|
297 | 310 | (defun open-menu (menu-data) |
|
298 | 311 | (let ((elt (get-frame :dropdown)) |
|
299 | 312 | (i 0)) |
|
300 | 313 | (loop :for item :in menu-data |
|
301 | 314 | :do (incf i) |
|
302 | 315 | :do (incf (inner-html elt) |
|
303 | 316 | (if (eq item :delimiter) |
|
304 | 317 | (make-menu-delimiter i) |
|
305 | 318 | (make-menu-item-html i |
|
306 | 319 | (@ item :text) |
|
307 | 320 | (@ item :icon) |
|
308 | 321 | (@ item :loc))))) |
|
309 | 322 | (let ((mouse (@ window mouse))) |
|
310 | 323 | (setf (@ elt style left) (+ (elt mouse 0) "px")) |
|
311 | 324 | (setf (@ elt style top) (+ (elt mouse 1) "px")) |
|
312 | 325 | ;; Make sure it's inside the viewport |
|
313 | 326 | (when (> (@ document body inner-width) |
|
314 | 327 | (+ (elt mouse 0) (@ elt inner-width))) |
|
315 | 328 | (incf (@ elt style left) (@ elt inner-width))) |
|
316 | 329 | (when (> (@ document body inner-height) |
|
317 | 330 | (+ (elt mouse 0) (@ elt inner-height))) |
|
318 | 331 | (incf (@ elt style top) (@ elt inner-height)))) |
|
319 | 332 | (setf (@ elt style display) "block"))) |
|
320 | 333 | |
|
321 | 334 | (defun finish-menu (loc) |
|
322 | 335 | (when *menu-resume |
|
323 | 336 | (let ((elt (get-frame :dropdown))) |
|
324 | 337 | (setf (inner-html elt) "") |
|
325 | 338 | (setf (@ elt style display) "none") |
|
326 | 339 | (funcall *menu-resume) |
|
327 | 340 | (setf *menu-resume nil)) |
|
328 | 341 | (when loc |
|
329 | 342 | (call-loc loc))) |
|
330 | 343 | (void)) |
|
331 | 344 | |
|
332 | 345 | (defun menu (menu-data) |
|
333 | 346 | (with-sleep (resume) |
|
334 | 347 | (open-menu menu-data) |
|
335 | 348 | (setf *menu-resume resume)) |
|
336 | 349 | (void)) |
|
337 | 350 | |
|
338 | 351 | ;;; Content |
|
339 | 352 | |
|
340 | 353 | (defun clean-audio () |
|
341 | 354 | (loop :for k :in (chain *object (keys *playing)) |
|
342 | 355 | :for v := (getprop *playing k) |
|
343 | 356 | :do (when (@ v ended) |
|
344 | 357 | (delete (@ *playing k))))) |
|
345 | 358 | |
|
346 | 359 | (defun show-image (path) |
|
347 | 360 | (let ((img (get-frame :image))) |
|
348 | 361 | (cond (path |
|
349 | 362 | (setf (@ img src) path) |
|
350 | 363 | (setf (@ img style display) "flex")) |
|
351 | 364 | (t |
|
352 | 365 | (setf (@ img src) "") |
|
353 | 366 | (setf (@ img style display) "hidden"))))) |
|
354 | 367 | |
|
355 | 368 | (defun rgb-string (rgb) |
|
356 | 369 | (let ((red (ps::>> rgb 16)) |
|
357 | 370 | (green (logand (ps::>> rgb 8) 255)) |
|
358 | 371 | (blue (logand rgb 255))) |
|
359 | 372 | (flet ((rgb-to-hex (comp) |
|
360 | 373 | (let ((hex (chain (*number comp) (to-string 16)))) |
|
361 | 374 | (if (< (length hex) 2) |
|
362 | 375 | (+ "0" hex) |
|
363 | 376 | hex)))) |
|
364 | 377 | (+ "#" (rgb-to-hex red) (rgb-to-hex green) (rgb-to-hex blue))))) |
|
365 | 378 | |
|
366 | 379 | (defun store-obj (key obj) |
|
367 | 380 | (store-str key (btoa (encode-u-r-i-component (chain *j-s-o-n (stringify obj))))) |
|
368 | 381 | (void)) |
|
369 | 382 | (defun store-str (key str) |
|
370 | 383 | (chain local-storage (set-item (+ "qsp_" key) str)) |
|
371 | 384 | (void)) |
|
372 | 385 | |
|
373 | 386 | (defun load-obj (key) |
|
374 | 387 | (chain *j-s-o-n (parse (encode-u-r-i-component (atob (load-str key)))))) |
|
375 | 388 | (defun load-str (key) |
|
376 | 389 | (chain local-storage (get-item (+ "qsp_" key)))) |
|
377 | 390 | |
|
378 | 391 | ;;; Saves |
|
379 | 392 | |
|
380 | 393 | (defun slot-savegame (slot comment) |
|
381 | 394 | (let ((saves (load-obj "saves"))) |
|
382 | 395 | (setf (@ saves slot) comment) |
|
383 | 396 | (store-obj saves)) |
|
384 | 397 | (store-str slot (state-to-base64)) |
|
385 | 398 | (void)) |
|
386 | 399 | |
|
387 | 400 | (defun slot-loadgame (slot) |
|
388 | 401 | (base64-to-state (load-str slot)) |
|
389 | 402 | (void)) |
|
390 | 403 | |
|
391 | 404 | (defun slot-deletegame (slot) |
|
392 | 405 | (let ((saves (load-obj "saves"))) |
|
393 | 406 | (setf (@ saves slot) undefined) |
|
394 | 407 | (store-obj saves)) |
|
395 | 408 | (store-str slot undefined) |
|
396 | 409 | (void)) |
|
397 | 410 | |
|
398 | 411 | (defun slot-listgames () |
|
399 | 412 | (load-obj "saves")) |
|
400 | 413 | |
|
401 | 414 | (defun opengame () |
|
402 | 415 | (let ((element (chain document (create-element :input)))) |
|
403 | 416 | (chain element (set-attribute :type :file)) |
|
404 | 417 | (chain element (set-attribute :id :qsp-opengame)) |
|
405 | 418 | (chain element (set-attribute :tabindex -1)) |
|
406 | 419 | (chain element (set-attribute "aria-hidden" t)) |
|
407 | 420 | (setf (@ element style display) :block) |
|
408 | 421 | (setf (@ element style visibility) :hidden) |
|
409 | 422 | (setf (@ element style position) :fixed) |
|
410 | 423 | (setf (@ element onchange) |
|
411 | 424 | (lambda (event) |
|
412 | 425 | (let* ((file (@ event target files 0)) |
|
413 | 426 | (reader (new (*file-reader)))) |
|
414 | 427 | (setf (@ reader onload) |
|
415 | 428 | (lambda (ev) |
|
416 | 429 | (block nil |
|
417 | 430 | (let ((target (@ ev current-target))) |
|
418 | 431 | (unless (@ target result) |
|
419 | 432 | (return)) |
|
420 | 433 | (base64-to-state (@ target result)) |
|
421 | 434 | (unstash-state))))) |
|
422 | 435 | (chain reader (read-as-text file))))) |
|
423 | 436 | (chain document body (append-child element)) |
|
424 | 437 | (chain element (click)) |
|
425 | 438 | (chain document body (remove-child element)))) |
|
426 | 439 | |
|
427 | 440 | (defun savegame () |
|
428 | 441 | (let ((element (chain document (create-element :a)))) |
|
429 | 442 | (chain element (set-attribute :href (+ "data:text/plain;charset=utf-8," (state-to-base64)))) |
|
430 | 443 | (chain element (set-attribute :download "savegame.sav")) |
|
431 | 444 | (setf (@ element style display) :none) |
|
432 | 445 | (chain document body (append-child element)) |
|
433 | 446 | (chain element (click)) |
|
434 | 447 | (chain document body (remove-child element)))) |
|
435 | 448 | |
|
436 | 449 | (defun stash-state (args) |
|
437 | 450 | (call-serv-loc "$ONGSAVE") |
|
438 | 451 | (setf *state-stash |
|
439 | 452 | (chain *j-s-o-n (stringify |
|
440 | 453 | (create :vars *globals |
|
441 | 454 | :objs *objs |
|
442 | 455 | :loc-args args |
|
443 | 456 | :msecs (- (chain *date (now)) *started-at) |
|
444 | 457 | :timer-interval *timer-interval |
|
445 | 458 | :main-html (inner-html |
|
446 | 459 | (get-frame :main)) |
|
447 | 460 | :stat-html (inner-html |
|
448 | 461 | (get-frame :stat)) |
|
449 | 462 | :next-location *current-location)))) |
|
450 | 463 | (void)) |
|
451 | 464 | |
|
452 | 465 | (defun unstash-state () |
|
453 | 466 | (let ((data (chain *j-s-o-n (parse *state-stash)))) |
|
454 | 467 | (clear-act) |
|
455 | 468 | (setf *globals (@ data :vars)) |
|
456 | 469 | (loop :for k :in (chain *object (keys *globals)) |
|
457 | 470 | :do (chain *object (set-prototype-of (getprop *globals k) |
|
458 | 471 | (@ *var prototype)))) |
|
459 | 472 | (setf *started-at (- (chain *date (now)) (@ data :msecs))) |
|
460 | 473 | (setf *objs (@ data :objs)) |
|
461 | 474 | (setf *current-location (@ data :next-location)) |
|
462 | 475 | (setf (inner-html (get-frame :main)) |
|
463 | 476 | (@ data :main-html)) |
|
464 | 477 | (setf (inner-html (get-frame :stat)) |
|
465 | 478 | (@ data :stat-html)) |
|
466 | 479 | (update-objs) |
|
467 | 480 | (set-timer (@ data :timer-interval)) |
|
468 | 481 | (call-serv-loc "$ONGLOAD") |
|
469 | 482 | (call-loc *current-location (@ data :loc-args)) |
|
470 | 483 | (void))) |
|
471 | 484 | |
|
472 | 485 | (defun state-to-base64 () |
|
473 | 486 | (btoa (encode-u-r-i-component *state-stash))) |
|
474 | 487 | |
|
475 | 488 | (defun base64-to-state (data) |
|
476 | 489 | (setf *state-stash (decode-u-r-i-component (atob data)))) |
|
477 | 490 | |
|
478 | 491 | ;;; Timers |
|
479 | 492 | |
|
480 | 493 | (defun set-timer (interval) |
|
481 | 494 | (setf *timer-interval interval) |
|
482 | 495 | (clear-interval *timer-obj) |
|
483 | 496 | (setf *timer-obj |
|
484 | 497 | (set-interval |
|
485 | 498 | (lambda () |
|
486 | 499 | (call-serv-loc "$COUNTER")) |
|
487 | 500 | interval))) |
|
488 | 501 | |
|
489 | 502 | ;;; Special variables |
|
490 | 503 | |
|
491 | 504 | (defvar serv-vars (create)) |
|
492 | 505 | |
|
493 | 506 | (define-serv-var $backimage (path) |
|
494 | 507 | (setf (@ (get-frame :main) style background-image) path)) |
|
495 | 508 | |
|
496 | 509 | (define-serv-var bcolor (color) |
|
497 | 510 | (setf (@ (get-frame :all) style background-color) (rgb-string color))) |
|
498 | 511 | |
|
499 | 512 | (define-serv-var fcolor (color) |
|
500 | 513 | (setf (@ (get-frame :all) style color) (rgb-string color))) |
|
501 | 514 | |
|
502 | 515 | (define-serv-var lcolor (color) |
|
503 | 516 | (setf (@ (get-frame :style) inner-text) |
|
504 | 517 | (+ "a { color: " (rgb-string color) ";}"))) |
|
505 | 518 | |
|
506 | 519 | (define-serv-var fsize (size) |
|
507 | 520 | (setf (@ (get-frame :all) style font-size) size)) |
|
508 | 521 | |
|
509 | 522 | (define-serv-var $fname (font-name) |
|
510 | 523 | (setf (@ (get-frame :all) style font-family) (+ font-name ",serif"))) |
@@ -1,164 +1,181 b'' | |||
|
1 | 1 | |
|
2 | 2 | (in-package txt2web.lib) |
|
3 | 3 | |
|
4 | 4 | ;;;; Macros implementing some intrinsics where it makes sense |
|
5 | 5 | ;;;; E.g. an equivalent JS function exists, or it's a direct API call |
|
6 | 6 | |
|
7 | 7 | ;;; 1loc |
|
8 | 8 | |
|
9 | 9 | ;;; 2var |
|
10 | 10 | |
|
11 |
(defpsmacro killvar (varname |
|
|
11 | (defpsmacro killvar (&optional varname index) | |
|
12 | 12 | `(api-call kill-var ,varname ,index)) |
|
13 | 13 | |
|
14 | 14 | (defpsmacro killall () |
|
15 | `(api-call kill-all)) | |
|
15 | `(progn | |
|
16 | (killvar) | |
|
17 | (killobj))) | |
|
16 | 18 | |
|
17 | 19 | ;;; 3expr |
|
18 | 20 | |
|
19 | 21 | (defpsmacro no (arg) |
|
20 | 22 | `(- -1 ,arg)) |
|
21 | 23 | |
|
22 | 24 | ;;; 4code |
|
23 | 25 | |
|
24 | 26 | (defpsmacro qspver () |
|
25 | 27 | "0.0.1") |
|
26 | 28 | |
|
27 | 29 | (defpsmacro curloc () |
|
28 | 30 | `*current-location) |
|
29 | 31 | |
|
30 | 32 | (defpsmacro rnd () |
|
31 | 33 | `(funcall rand 1 1000)) |
|
32 | 34 | |
|
33 | 35 | (defpsmacro qspmax (&rest args) |
|
34 | 36 | (if (= 1 (length args)) |
|
35 | 37 | `(*math.max.apply nil ,@args) |
|
36 | 38 | `(*math.max ,@args))) |
|
37 | 39 | |
|
38 | 40 | (defpsmacro qspmin (&rest args) |
|
39 | 41 | (if (= 1 (length args)) |
|
40 | 42 | `(*math.min.apply nil ,@args) |
|
41 | 43 | `(*math.min ,@args))) |
|
42 | 44 | |
|
43 | 45 | ;;; 5arrays |
|
44 | 46 | |
|
45 | 47 | (defpsmacro arrsize (name) |
|
46 | 48 | `(api-call array-size ,name)) |
|
47 | 49 | |
|
48 | 50 | ;;; 6str |
|
49 | 51 | |
|
50 | 52 | (defpsmacro len (s) |
|
51 | 53 | `(length ,s)) |
|
52 | 54 | |
|
53 | 55 | (defpsmacro mid (s from &optional count) |
|
54 | 56 | `(chain ,s (substring ,from ,count))) |
|
55 | 57 | |
|
56 | 58 | (defpsmacro ucase (s) |
|
57 | 59 | `(chain ,s (to-upper-case))) |
|
58 | 60 | |
|
59 | 61 | (defpsmacro lcase (s) |
|
60 | 62 | `(chain ,s (to-lower-case))) |
|
61 | 63 | |
|
62 | 64 | (defpsmacro trim (s) |
|
63 | 65 | `(chain ,s (trim))) |
|
64 | 66 | |
|
65 | 67 | (defpsmacro qspreplace (s from to) |
|
66 | 68 | `(chain ,s (replace ,from ,to))) |
|
67 | 69 | |
|
68 | 70 | (defpsmacro val (s) |
|
69 | 71 | `(parse-int ,s 10)) |
|
70 | 72 | |
|
71 | 73 | (defpsmacro qspstr (n) |
|
72 | 74 | `(chain ,n (to-string))) |
|
73 | 75 | |
|
74 | 76 | ;;; 7if |
|
75 | 77 | |
|
76 | 78 | ;;; 8sub |
|
77 | 79 | |
|
78 | 80 | ;;; 9loops |
|
79 | 81 | |
|
80 | 82 | ;; JUMP is in ps-macros.lisp (because it's a part of a huge kludge) |
|
81 | 83 | |
|
82 | 84 | (defpsmacro exit () |
|
83 | 85 | `(return-from nil (values))) |
|
84 | 86 | |
|
85 | 87 | ;;; 10dynamic |
|
86 | 88 | |
|
89 | (defpsmacro dynamic (block &rest args) | |
|
90 | `(progn | |
|
91 | (when (stringp ,block) | |
|
92 | (api:report-error "DYNAMIC can't evaluate arbitrary strings.\\nUse {braces} to create blocks for DYNAMIC.")) | |
|
93 | (api:with-call-args ,args | |
|
94 | (funcall ,block)) | |
|
95 | (void))) | |
|
96 | ||
|
97 | (defpsmacro dyneval (block &rest args) | |
|
98 | `(progn | |
|
99 | (when (stringp block) | |
|
100 | (api:report-error "DYNEVAL can't evaluate arbitrary strings.\\nUse {braces} to create blocks for DYNEVAL.")) | |
|
101 | (api:with-call-args args | |
|
102 | (funcall block)))) | |
|
103 | ||
|
87 | 104 | ;;; 11main |
|
88 | 105 | |
|
89 | 106 | (defpsmacro desc (s) |
|
90 | 107 | (declare (ignore s)) |
|
91 | 108 | "") |
|
92 | 109 | |
|
93 | 110 | ;;; 12stat |
|
94 | 111 | |
|
95 | 112 | (defpsmacro showstat (enable) |
|
96 | 113 | `(api-call enable-frame :stat ,enable)) |
|
97 | 114 | |
|
98 | 115 | ;;; 13diag |
|
99 | 116 | |
|
100 | 117 | (defpsmacro msg (text) |
|
101 | 118 | `(alert ,text)) |
|
102 | 119 | |
|
103 | 120 | ;;; 14act |
|
104 | 121 | |
|
105 | 122 | (defpsmacro showacts (enable) |
|
106 | 123 | `(api-call enable-frame :acts ,enable)) |
|
107 | 124 | |
|
108 | 125 | (defpsmacro delact (&optional name) |
|
109 | 126 | (if name |
|
110 | 127 | `(api-call del-act ,name) |
|
111 | 128 | `(api-call del-act))) |
|
112 | 129 | |
|
113 | 130 | (defpsmacro cla () |
|
114 | 131 | `(api-call clear-act)) |
|
115 | 132 | |
|
116 | 133 | ;;; 15objs |
|
117 | 134 | |
|
118 | 135 | (defpsmacro showobjs (enable) |
|
119 | 136 | `(api-call enable-frame :objs ,enable)) |
|
120 | 137 | |
|
121 | 138 | (defpsmacro countobj () |
|
122 | 139 | `(length *objs)) |
|
123 | 140 | |
|
124 | 141 | (defpsmacro getobj (index) |
|
125 | 142 | `(or (elt *objs ,index) "")) |
|
126 | 143 | |
|
127 | 144 | ;;; 16menu |
|
128 | 145 | |
|
129 | 146 | ;;; 17sound |
|
130 | 147 | |
|
131 | 148 | (defpsmacro isplay (filename) |
|
132 | 149 | `(funcall (@ playing includes) ,filename)) |
|
133 | 150 | |
|
134 | 151 | ;;; 18img |
|
135 | 152 | |
|
136 | 153 | (defpsmacro view (&optional path) |
|
137 | 154 | `(api-call show-image ,path)) |
|
138 | 155 | |
|
139 | 156 | ;;; 19input |
|
140 | 157 | |
|
141 | 158 | (defpsmacro showinput (enable) |
|
142 | 159 | `(api-call enable-frame :input ,enable)) |
|
143 | 160 | |
|
144 | 161 | ;;; 20time |
|
145 | 162 | |
|
146 | 163 | (defpsmacro wait (msec) |
|
147 | 164 | `(await (api-call sleep ,msec))) |
|
148 | 165 | |
|
149 | 166 | (defpsmacro settimer (interval) |
|
150 | 167 | `(api-call set-timer ,interval)) |
|
151 | 168 | |
|
152 | 169 | ;;; 21local |
|
153 | 170 | |
|
154 | 171 | ;;; 22for |
|
155 | 172 | |
|
156 | 173 | ;;; misc |
|
157 | 174 | |
|
158 | 175 | (defpsmacro opengame (&optional filename) |
|
159 | 176 | (declare (ignore filename)) |
|
160 | 177 | `(api-call opengame)) |
|
161 | 178 | |
|
162 | 179 | (defpsmacro savegame (&optional filename) |
|
163 | 180 | (declare (ignore filename)) |
|
164 | 181 | `(api-call savegame)) |
@@ -1,330 +1,317 b'' | |||
|
1 | 1 | |
|
2 | 2 | (in-package txt2web.lib) |
|
3 | 3 | |
|
4 | 4 | ;;;; Functions and procedures defined by the QSP language. |
|
5 | 5 | ;;;; They can call api and deal with locations and other data directly. |
|
6 | 6 | ;;;; Except vars. Use VAR and SET or GET-VAR and SET-VAR api calls. |
|
7 | 7 | |
|
8 | 8 | ;;; 1loc |
|
9 | 9 | |
|
10 | 10 | (defun goto (target args) |
|
11 | 11 | (api:clear-text :main) |
|
12 | 12 | (funcall xgoto target args) |
|
13 | 13 | (void)) |
|
14 | 14 | |
|
15 | 15 | (defun xgoto (target args) |
|
16 | 16 | (setf args (or args (list))) |
|
17 | 17 | (api:clear-act) |
|
18 | 18 | (setf *current-location (chain target (to-upper-case))) |
|
19 | 19 | (api:stash-state args) |
|
20 | 20 | (api:call-loc *current-location args) |
|
21 | 21 | (api:call-serv-loc "$ONNEWLOC") |
|
22 | 22 | (void)) |
|
23 | 23 | |
|
24 | 24 | ;;; 2var |
|
25 | 25 | |
|
26 | 26 | ;;; 3expr |
|
27 | 27 | |
|
28 | 28 | (defun obj (name) |
|
29 | 29 | (has name *objs)) |
|
30 | 30 | |
|
31 | 31 | (defun loc (name) |
|
32 | 32 | (has name *locs)) |
|
33 | 33 | |
|
34 | 34 | ;;; 4code |
|
35 | 35 | |
|
36 | 36 | (defun rand (a &optional (b 1)) |
|
37 | 37 | (let ((min (min a b)) |
|
38 | 38 | (max (max a b))) |
|
39 | 39 | (+ min (chain *math (random (- max min)))))) |
|
40 | 40 | |
|
41 | 41 | ;;; 5arrays |
|
42 | 42 | |
|
43 | 43 | (defun copyarr (to from start count) |
|
44 | 44 | (multiple-value-bind (to-name to-slot) |
|
45 | 45 | (api:var-real-name to) |
|
46 | 46 | (multiple-value-bind (from-name from-slot) |
|
47 | 47 | (api:var-real-name from) |
|
48 | 48 | (loop :for i :from start :to (min (api:array-size from-name) |
|
49 | 49 | (+ start count)) |
|
50 | 50 | :do (api:set-var to-name (+ start i) to-slot |
|
51 | 51 | (api:get-var from-name (+ start i) from-slot)))))) |
|
52 | 52 | |
|
53 | 53 | (defun arrpos (name value &optional (start 0)) |
|
54 | 54 | (multiple-value-bind (real-name slot) |
|
55 | 55 | (api:var-real-name name) |
|
56 | 56 | (loop :for i :from start :to (api:array-size name) |
|
57 | 57 | :do (when (eq (api:get-var real-name i slot) value) |
|
58 | 58 | (return-from arrpos i)))) |
|
59 | 59 | -1) |
|
60 | 60 | |
|
61 | 61 | (defun arrcomp (name pattern &optional (start 0)) |
|
62 | 62 | (multiple-value-bind (real-name slot) |
|
63 | 63 | (api:var-real-name name) |
|
64 | 64 | (loop :for i :from start :to (api:array-size name) |
|
65 | 65 | :do (when (funcall (getprop (api:get-var real-name i slot) |
|
66 | 66 | 'match) |
|
67 | 67 | pattern) |
|
68 | 68 | (return-from arrcomp i)))) |
|
69 | 69 | -1) |
|
70 | 70 | |
|
71 | 71 | ;;; 6str |
|
72 | 72 | |
|
73 | 73 | (defun instr (s subs &optional (start 1)) |
|
74 | 74 | (+ start (chain s (substring (- start 1)) (search subs)))) |
|
75 | 75 | |
|
76 | 76 | (defun isnum (s) |
|
77 | 77 | (if (is-na-n s) |
|
78 | 78 | 0 |
|
79 | 79 | -1)) |
|
80 | 80 | |
|
81 | 81 | (defun strcomp (s pattern) |
|
82 | 82 | (if (chain s (match pattern)) |
|
83 | 83 | -1 |
|
84 | 84 | 0)) |
|
85 | 85 | |
|
86 | 86 | (defun strfind (s pattern group) |
|
87 | 87 | (let* ((re (new (*reg-exp pattern))) |
|
88 | 88 | (match (chain re (exec s)))) |
|
89 | 89 | (chain match (group group)))) |
|
90 | 90 | |
|
91 | 91 | (defun strpos (s pattern &optional (group 0)) |
|
92 | 92 | (let* ((re (new (*reg-exp pattern))) |
|
93 | 93 | (match (chain re (exec s))) |
|
94 | 94 | (found (chain match (group group)))) |
|
95 | 95 | (if found |
|
96 | 96 | (chain s (search found)) |
|
97 | 97 | 0))) |
|
98 | 98 | |
|
99 | 99 | ;;; 7if |
|
100 | 100 | |
|
101 | 101 | ;; Has to be a function because it always evaluates all three of its |
|
102 | 102 | ;; arguments |
|
103 | 103 | (defun iif (cond-expr then-expr else-expr) |
|
104 | 104 | (if cond-expr then-expr else-expr)) |
|
105 | 105 | |
|
106 | 106 | ;;; 8sub |
|
107 | 107 | |
|
108 | 108 | (defun gosub (target &rest args) |
|
109 | 109 | (api:call-loc target args) |
|
110 | 110 | (void)) |
|
111 | 111 | |
|
112 | 112 | (defun func (target &rest args) |
|
113 | 113 | (api:call-loc target args)) |
|
114 | 114 | |
|
115 | 115 | ;;; 9loops |
|
116 | 116 | |
|
117 | 117 | ;;; 10dynamic |
|
118 | 118 | |
|
119 | (defun dynamic (block &rest args) | |
|
120 | (when (stringp block) | |
|
121 | (api:report-error "DYNAMIC can't evaluate arbitrary strings.\\nUse {braces} to create blocks for DYNAMIC.")) | |
|
122 | (api:with-call-args args | |
|
123 | (funcall block args)) | |
|
124 | (void)) | |
|
125 | ||
|
126 | (defun dyneval (block &rest args) | |
|
127 | (when (stringp block) | |
|
128 | (api:report-error "DYNEVAL can't evaluate arbitrary strings.\\nUse {braces} to create blocks for DYNEVAL.")) | |
|
129 | (api:with-call-args args | |
|
130 | (funcall block args))) | |
|
131 | ||
|
132 | 119 | ;;; 11main |
|
133 | 120 | |
|
134 | 121 | (defun main-p (s) |
|
135 | 122 | (api:add-text :main s) |
|
136 | 123 | (void)) |
|
137 | 124 | |
|
138 | 125 | (defun main-pl (s) |
|
139 | 126 | (api:add-text :main s) |
|
140 | 127 | (api:newline :main) |
|
141 | 128 | (void)) |
|
142 | 129 | |
|
143 | 130 | (defun main-nl (s) |
|
144 | 131 | (api:newline :main) |
|
145 | 132 | (api:add-text :main s) |
|
146 | 133 | (void)) |
|
147 | 134 | |
|
148 | 135 | (defun maintxt (s) |
|
149 | 136 | (api:get-text :main) |
|
150 | 137 | (void)) |
|
151 | 138 | |
|
152 | 139 | (defun desc (s) |
|
153 | 140 | "") |
|
154 | 141 | |
|
155 | 142 | (defun main-clear () |
|
156 | 143 | (api:clear-text :main) |
|
157 | 144 | (void)) |
|
158 | 145 | |
|
159 | 146 | ;;; 12stat |
|
160 | 147 | |
|
161 | 148 | (defun stat-p (s) |
|
162 | 149 | (api:add-text :stat s) |
|
163 | 150 | (void)) |
|
164 | 151 | |
|
165 | 152 | (defun stat-pl (s) |
|
166 | 153 | (api:add-text :stat s) |
|
167 | 154 | (api:newline :stat) |
|
168 | 155 | (void)) |
|
169 | 156 | |
|
170 | 157 | (defun stat-nl (s) |
|
171 | 158 | (api:newline :stat) |
|
172 | 159 | (api:add-text :stat s) |
|
173 | 160 | (void)) |
|
174 | 161 | |
|
175 | 162 | (defun stattxt (s) |
|
176 | 163 | (api:get-text :stat) |
|
177 | 164 | (void)) |
|
178 | 165 | |
|
179 | 166 | (defun stat-clear () |
|
180 | 167 | (api:clear-text :stat) |
|
181 | 168 | (void)) |
|
182 | 169 | |
|
183 | 170 | (defun cls () |
|
184 | 171 | (stat-clear) |
|
185 | 172 | (main-clear) |
|
186 | 173 | (cla) |
|
187 | 174 | (cmdclear) |
|
188 | 175 | (void)) |
|
189 | 176 | |
|
190 | 177 | ;;; 13diag |
|
191 | 178 | |
|
192 | 179 | ;;; 14act |
|
193 | 180 | |
|
194 | 181 | (defun selact () |
|
195 | 182 | (loop :for (k v) :of *acts |
|
196 | 183 | :do (when (@ v :selected) |
|
197 | 184 | (return-from selact (@ v :name))))) |
|
198 | 185 | |
|
199 | 186 | (defun curacts () |
|
200 | 187 | (let ((acts (api-call copy-obj *acts))) |
|
201 | 188 | (lambda () |
|
202 | 189 | (setf *acts acts) |
|
203 | 190 | (void)))) |
|
204 | 191 | |
|
205 | 192 | ;;; 15objs |
|
206 | 193 | |
|
207 | 194 | (defun addobj (name img) |
|
208 | 195 | (setf img (or img "")) |
|
209 | 196 | (setf (getprop *objs name) |
|
210 | 197 | (create :name name :img img :selected nil)) |
|
211 | 198 | (api:update-objs) |
|
212 | 199 | (api-call call-serv-loc "$ONOBJADD" name img) |
|
213 | 200 | (void)) |
|
214 | 201 | |
|
215 | 202 | (defun delobj (name) |
|
216 | 203 | (delete (getprop *objs name)) |
|
217 | 204 | (api:update-objs) |
|
218 | 205 | (api-call call-serv-loc "$ONOBJDEL" name) |
|
219 | 206 | (void)) |
|
220 | 207 | |
|
221 | 208 | (defun killobj (&optional (num nil)) |
|
222 |
(if (eq |
|
|
209 | (if (eq undefined num) | |
|
223 | 210 | (setf *objs (create)) |
|
224 | 211 | (delobj (elt (chain *object (keys *objs)) num))) |
|
225 | 212 | (api:update-objs) |
|
226 | 213 | (void)) |
|
227 | 214 | |
|
228 | 215 | (defun selobj () |
|
229 | 216 | (loop :for (k v) :of *objs |
|
230 | 217 | :do (when (@ v :selected) |
|
231 | 218 | (return-from selobj (@ v :name))))) |
|
232 | 219 | |
|
233 | 220 | (defun unsel () |
|
234 | 221 | (loop :for (k v) :of *objs |
|
235 | 222 | :do (setf (@ v :selected) nil))) |
|
236 | 223 | |
|
237 | 224 | ;;; 16menu |
|
238 | 225 | |
|
239 | 226 | (defun menu (menu-name) |
|
240 | 227 | (let ((menu-data (list))) |
|
241 | 228 | (loop :for item-obj :in (@ (api:get-array (api:var-real-name menu-name)) values) |
|
242 | 229 | :for item := (@ item-obj :str) |
|
243 | 230 | :do (cond ((string= item "") |
|
244 | 231 | (break)) |
|
245 | 232 | ((string= item "-:-") |
|
246 | 233 | (chain menu-data (push :delimiter))) |
|
247 | 234 | (t |
|
248 | 235 | (let* ((tokens (chain item (split ":")))) |
|
249 | 236 | (when (= (length tokens) 2) |
|
250 | 237 | (chain tokens (push ""))) |
|
251 | 238 | (let* ((text (chain tokens (splice 0 (- (length tokens) 2)) (join ":"))) |
|
252 | 239 | (loc (getprop tokens (- (length tokens) 2))) |
|
253 | 240 | (icon (getprop tokens (- (length tokens) 1)))) |
|
254 | 241 | (chain menu-data |
|
255 | 242 | (push (create :text text |
|
256 | 243 | :loc loc |
|
257 | 244 | :icon icon)))))))) |
|
258 | 245 | (api:menu menu-data) |
|
259 | 246 | (void))) |
|
260 | 247 | |
|
261 | 248 | ;;; 17sound |
|
262 | 249 | |
|
263 | 250 | (defun play (filename &optional (volume 100)) |
|
264 | 251 | (let ((audio (new (*audio filename)))) |
|
265 | 252 | (setf (getprop *playing filename) audio) |
|
266 | 253 | (setf (@ audio volume) (* volume 0.01)) |
|
267 | 254 | (chain audio (play)))) |
|
268 | 255 | |
|
269 | 256 | (defun close (filename) |
|
270 | 257 | (funcall (getprop *playing filename) stop) |
|
271 | 258 | (delete (getprop *playing filename)) |
|
272 | 259 | (void)) |
|
273 | 260 | |
|
274 | 261 | (defun closeall () |
|
275 | 262 | (loop :for k :in (chain *object (keys *playing)) |
|
276 | 263 | :for v := (getprop *playing k) |
|
277 | 264 | :do (funcall v stop)) |
|
278 | 265 | (setf *playing (create))) |
|
279 | 266 | |
|
280 | 267 | ;;; 18img |
|
281 | 268 | |
|
282 | 269 | (defun refint () |
|
283 | 270 | ;; "Force interface update" Uh... what exactly do we do here? |
|
284 | 271 | ;(api:report-error "REFINT is not supported") |
|
285 | 272 | ) |
|
286 | 273 | |
|
287 | 274 | ;;; 19input |
|
288 | 275 | |
|
289 | 276 | (defun usertxt () |
|
290 | 277 | (let ((input (by-id "qsp-input"))) |
|
291 | 278 | (@ input value))) |
|
292 | 279 | |
|
293 | 280 | (defun cmdclear () |
|
294 | 281 | (let ((input (by-id "qsp-input"))) |
|
295 | 282 | (setf (@ input value) ""))) |
|
296 | 283 | |
|
297 | 284 | (defun input (text) |
|
298 | 285 | (chain window (prompt text))) |
|
299 | 286 | |
|
300 | 287 | ;;; 20time |
|
301 | 288 | |
|
302 | 289 | (defun msecscount () |
|
303 | 290 | (- (chain *date (now)) *started-at)) |
|
304 | 291 | |
|
305 | 292 | ;;; 21local |
|
306 | 293 | |
|
307 | 294 | ;;; 22for |
|
308 | 295 | |
|
309 | 296 | ;;; misc |
|
310 | 297 | |
|
311 | 298 | (defun rgb (red green blue) |
|
312 | 299 | (+ (<< red 16) |
|
313 | 300 | (<< green 8) |
|
314 | 301 | blue)) |
|
315 | 302 | |
|
316 | 303 | (defun openqst (name) |
|
317 | 304 | (api-call run-game name)) |
|
318 | 305 | |
|
319 | 306 | (defun addqst (name) |
|
320 | 307 | (let ((game (api-call filename-game name))) |
|
321 | 308 | ;; Add the game's locations |
|
322 | 309 | (chain *object (assign *locs |
|
323 | 310 | (getprop *games name))))) |
|
324 | 311 | |
|
325 | 312 | (defun killqst () |
|
326 | 313 | ;; Delete all locations not from the current main game |
|
327 | 314 | (loop :for (k v) :in *games |
|
328 | 315 | :do (unless (string= k *main-game) |
|
329 | 316 | (delete (getprop *locs k))))) |
|
330 | 317 |
@@ -1,56 +1,58 b'' | |||
|
1 | 1 | |
|
2 | 2 | (in-package txt2web.main) |
|
3 | 3 | |
|
4 | 4 | ;;; Game session state (saved in savegames) |
|
5 | 5 | ;; Variables |
|
6 | 6 | (var *globals (create)) |
|
7 | (var *default-globals (create)) | |
|
7 | 8 | ;; Inventory (objects) |
|
8 | 9 | (var *objs (create)) |
|
9 | 10 | (var *current-location nil) |
|
10 | 11 | ;; Game time |
|
11 | 12 | (var *started-at (chain *date (now))) |
|
12 | 13 | ;; Timers |
|
13 | 14 | (var *timer-interval 500) |
|
14 | 15 | (var *timer-obj nil) |
|
15 | 16 | ;; Games |
|
16 | 17 | (var *loaded-games (list)) |
|
17 | 18 | |
|
18 | 19 | ;;; Transient state |
|
19 | 20 | ;; ACTions |
|
20 | 21 | (var *acts (create)) |
|
21 | 22 | ;; Savegame data |
|
22 | 23 | (var *state-stash (create)) |
|
23 | 24 | ;; List of audio files being played |
|
24 | 25 | (var *playing (create)) |
|
25 | 26 | ;; Local variables stack (starts with an empty frame) |
|
26 | 27 | (var *locals (list)) |
|
27 | 28 | ;; Promise to continue running the game after menu |
|
28 | 29 | (var *menu-resume nil) |
|
29 | 30 | |
|
30 | 31 | ;;; Game data |
|
31 | 32 | ;; Games (filename -> [locations]) |
|
32 | 33 | (var *games (list)) |
|
33 | 34 | ;; The main (non library) game. Updated by openqst |
|
34 | 35 | (var *main-game nil) |
|
35 | 36 | ;; Active locations |
|
36 | 37 | (var *locs (create)) |
|
37 | 38 | |
|
38 | 39 | (setf (@ window onload) |
|
39 | 40 | #'start) |
|
40 | 41 | |
|
41 | 42 | (defun start () |
|
42 | 43 | (#.(intern "INIT-DOM" "TXT2WEB.API")) |
|
43 | 44 | ;; For MSECCOUNT |
|
44 | 45 | (setf *started-at (chain *date (now))) |
|
45 | 46 | ;; For $COUNTER and SETTIMER |
|
46 | 47 | (#.(intern "SET-TIMER" "TXT2WEB.API") |
|
47 | 48 | *timer-interval) |
|
48 | 49 | ;; Start the first game |
|
49 | (#.(intern "RUN-GAME" "TXT2WEB.API") | |
|
50 | (chain *object (keys *games) 0)) | |
|
50 | (let ((first-game (chain *object (keys *games) 0))) | |
|
51 | (#.(intern "INIT-GLOBALS" "TXT2WEB.API") first-game) | |
|
52 | (#.(intern "RUN-GAME" "TXT2WEB.API") first-game)) | |
|
51 | 53 | (values)) |
|
52 | 54 | |
|
53 | 55 | ;;; Some very common utilities (for both api and lib) |
|
54 | 56 | |
|
55 | 57 | (defun by-id (id) |
|
56 | 58 | (chain document (get-element-by-id id))) |
@@ -1,108 +1,109 b'' | |||
|
1 | 1 | |
|
2 | 2 | (in-package cl-user) |
|
3 | 3 | |
|
4 | 4 | (defpackage :txt2web.js) |
|
5 | 5 | |
|
6 | 6 | (defpackage :txt2web.main |
|
7 | 7 | (:use :cl :ps :txt2web.js) |
|
8 | 8 | (:export #:api-call #:by-id |
|
9 | 9 | #:has |
|
10 | 10 | |
|
11 | #:*globals #:*objs #:*current-location | |
|
11 | #:*globals #:*default-globals #:*objs #:*current-location | |
|
12 | 12 | #:*started-at #:*timer-interval #:*timer-obj #:*loaded-games |
|
13 | 13 | |
|
14 | 14 | #:*acts #:*state-stash #:*playing #:*locals |
|
15 | 15 | |
|
16 | 16 | #:*games #:*main-game #:*locs #:*menu-resume |
|
17 | 17 | )) |
|
18 | 18 | |
|
19 | 19 | (defpackage :code-walker |
|
20 | 20 | (:use :cl) |
|
21 | 21 | (:export #:deftransform |
|
22 | 22 | #:deftransform-stop |
|
23 | 23 | #:walk |
|
24 | 24 | #:whole |
|
25 | 25 | #:walk-continue)) |
|
26 | 26 | |
|
27 | 27 | ;;; API functions |
|
28 | 28 | (defpackage :txt2web.api |
|
29 | 29 | (:use :cl :ps :txt2web.main :txt2web.js) |
|
30 | 30 | (:export #:with-frame #:with-call-args |
|
31 | 31 | #:stash-state |
|
32 | 32 | |
|
33 | 33 | #:report-error #:sleep #:init-dom #:call-serv-loc #:*serv-vars* |
|
34 | 34 | #:newline #:clear-id #:prepare-contents #:get-id #:set-id #:append-id |
|
35 |
|
|
|
35 | #:get-result #:call-loc #:call-act | |
|
36 | 36 | #:get-frame #:add-text #:get-text #:clear-text #:enable-frame |
|
37 | 37 | #:add-act #:del-act #:clear-act #:update-acts |
|
38 | 38 | #:set-str-element #:set-any-element #:set-serv-var |
|
39 | 39 | #:*var #:new-value #:index-num #:get #:set #:kill |
|
40 | 40 | #:var-real-name #:ensure-var #:var-ref #:get-var #:set-var |
|
41 | 41 | #:get-array #:set-array #:kill-var #:array-size |
|
42 | 42 | #:push-local-frame #:pop-local-frame #:current-local-frame #:new-local |
|
43 | 43 | #:update-objs |
|
44 | 44 | #:menu |
|
45 | 45 | #:clean-audio |
|
46 | 46 | #:show-image |
|
47 | 47 | #:opengame #:savegame |
|
48 | #:init-globals | |
|
48 | 49 | )) |
|
49 | 50 | |
|
50 | 51 | ;;; QSP library functions and macros |
|
51 | 52 | (defpackage :txt2web.lib |
|
52 | 53 | (:use :cl :ps :txt2web.main :txt2web.js) |
|
53 | 54 | (:local-nicknames (#:api :txt2web.api) |
|
54 | 55 | (#:walker :code-walker)) |
|
55 | 56 | (:export #:str #:exec #:qspblock #:qsploop #:game #:location |
|
56 | 57 | #:qspcond #:qspvar #:set #:local #:jump |
|
57 | 58 | |
|
58 | 59 | #:killvar #:killall |
|
59 | 60 | #:obj #:loc #:no |
|
60 | 61 | #:qspver #:curloc |
|
61 | 62 | #:rnd #:qspmax #:qspmin |
|
62 | 63 | #:arrsize #:len |
|
63 | 64 | #:mid #:ucase #:lcase #:trim #:replace #:val #:qspstr |
|
64 | 65 | #:exit #:desc |
|
65 | 66 | #:showstat #:msg |
|
66 | 67 | #:showacts #:delact #:cla |
|
67 | 68 | #:showobjs #:countobj #:getobj |
|
68 | 69 | #:isplay |
|
69 | 70 | #:view |
|
70 | 71 | #:showinput |
|
71 | 72 | #:wait #:settimer |
|
72 | 73 | #:local |
|
73 | 74 | #:opengame #:savegame |
|
74 | 75 | |
|
75 | 76 | #:goto #:xgoto |
|
76 | 77 | #:rand |
|
77 | 78 | #:copyarr #:arrpos #:arrcomp |
|
78 | 79 | #:instr #:isnum #:strcomp #:strfind #:strpos |
|
79 | 80 | #:iif |
|
80 | 81 | #:gosub #:func |
|
81 | 82 | #:dynamic #:dyneval |
|
82 | 83 | #:main-p #:main-pl #:main-nl #:maintxt #:desc #:main-clear |
|
83 | 84 | #:stat-p #:stat-pl #:stat-nl #:stattxt #:stat-clear #:cls |
|
84 | 85 | #:curacts |
|
85 | 86 | #:addobj #:delobj #:killobj |
|
86 | 87 | #:menu |
|
87 | 88 | #:play #:close #:closeall |
|
88 | 89 | #:refint |
|
89 | 90 | #:usertxt #:cmdclear #:input |
|
90 | 91 | #:msecscount |
|
91 | 92 | #:rgb |
|
92 | 93 | #:openqst #:addqst #:killqst |
|
93 | 94 | )) |
|
94 | 95 | |
|
95 | 96 | (setf (ps:ps-package-prefix "TXT2WEB.MAIN") "qsp_") |
|
96 | 97 | (setf (ps:ps-package-prefix "TXT2WEB.API") "qsp_api_") |
|
97 | 98 | (setf (ps:ps-package-prefix "TXT2WEB.LIB") "qsp_lib_") |
|
98 | 99 | |
|
99 | 100 | ;;; The compiler |
|
100 | 101 | (defpackage :txt2web |
|
101 | 102 | (:use :cl) |
|
102 | 103 | (:local-nicknames (#:p #:esrap) |
|
103 | 104 | (#:lib :txt2web.lib) |
|
104 | 105 | (#:api :txt2web.api) |
|
105 | 106 | (#:main :txt2web.main) |
|
106 | 107 | (#:walker :code-walker)) |
|
107 | 108 | (:export #:parse-file #:entry-point)) |
|
108 | 109 |
@@ -1,664 +1,669 b'' | |||
|
1 | 1 | |
|
2 | 2 | (in-package txt2web) |
|
3 | 3 | |
|
4 | 4 | ;;;; Parses TXT source to an intermediate representation |
|
5 | 5 | |
|
6 | 6 | (eval-when (:compile-toplevel :load-toplevel :execute) |
|
7 | 7 | (defparameter *max-args* 10)) |
|
8 | 8 | |
|
9 | 9 | ;;; Utility |
|
10 | 10 | |
|
11 | 11 | (defun remove-nth (list nth) |
|
12 | 12 | (append (subseq list 0 nth) |
|
13 | 13 | (subseq list (1+ nth)))) |
|
14 | 14 | |
|
15 | 15 | (defun not-quote (char) |
|
16 | 16 | (not (eql #\' char))) |
|
17 | 17 | |
|
18 | 18 | (defun not-doublequote (char) |
|
19 | 19 | (not (eql #\" char))) |
|
20 | 20 | |
|
21 | 21 | (defun not-brace (char) |
|
22 | 22 | (not (eql #\} char))) |
|
23 | 23 | |
|
24 | 24 | (defun not-integer (string) |
|
25 | 25 | (when (find-if-not #'digit-char-p string) |
|
26 | 26 | t)) |
|
27 | 27 | |
|
28 | 28 | (defun not-newline (char) |
|
29 | 29 | (not (eql #\newline char))) |
|
30 | 30 | |
|
31 | 31 | (defun id-any-char (char) |
|
32 | 32 | (and |
|
33 | 33 | (not (digit-char-p char)) |
|
34 | 34 | (not (eql #\newline char)) |
|
35 | 35 | (not (find char " !:&=<>+-*/,'\"()[]{}")))) |
|
36 | 36 | |
|
37 | 37 | (defun intern-first (list) |
|
38 | 38 | (list* (intern (string-upcase (first list)) "TXT2WEB.LIB") |
|
39 | 39 | (rest list))) |
|
40 | 40 | |
|
41 | 41 | (eval-when (:compile-toplevel :load-toplevel :execute) |
|
42 | 42 | (defun remove-nil (list) |
|
43 | 43 | (remove nil list))) |
|
44 | 44 | |
|
45 | 45 | (defun binop-rest (list) |
|
46 | 46 | (destructuring-bind (ws1 operator ws2 operand2) |
|
47 | 47 | list |
|
48 | 48 | (declare (ignore ws1 ws2)) |
|
49 | 49 | (list (intern (string-upcase operator) "TXT2WEB.LIB") operand2))) |
|
50 | 50 | |
|
51 | 51 | (defun do-binop% (left-op other-ops) |
|
52 | 52 | (if (null other-ops) |
|
53 | 53 | left-op |
|
54 | 54 | (destructuring-bind ((operator right-op) &rest rest-ops) |
|
55 | 55 | other-ops |
|
56 | 56 | (if (and (listp left-op) |
|
57 | 57 | (eq (first left-op) |
|
58 | 58 | operator)) |
|
59 | 59 | (do-binop% (append left-op (list right-op)) rest-ops) |
|
60 | 60 | (do-binop% (list operator left-op right-op) rest-ops))))) |
|
61 | 61 | |
|
62 | 62 | (walker:deftransform parser-qspmod mod (&rest args) |
|
63 | 63 | (list* 'qspmod (mapcar #'walker:walk-continue args))) |
|
64 | 64 | |
|
65 | 65 | (defun do-binop (list) |
|
66 | 66 | (walker:walk 'parser-qspmod |
|
67 | 67 | (destructuring-bind (left-op rest-ops) |
|
68 | 68 | list |
|
69 | 69 | (do-binop% left-op |
|
70 | 70 | (mapcar #'binop-rest rest-ops))))) |
|
71 | 71 | |
|
72 | 72 | (defun maybe-text (list) |
|
73 | 73 | "Leaves lists in place and applies esrap:text to everything else" |
|
74 | 74 | (let ((parts nil) |
|
75 | 75 | (part (list 'text))) |
|
76 | 76 | (loop :for token :in list |
|
77 | 77 | :do (cond ((listp token) |
|
78 | 78 | (push (nreverse part) parts) |
|
79 | 79 | (setf part (list 'text)) |
|
80 | 80 | (push token parts)) |
|
81 | 81 | (t (push token part)))) |
|
82 | 82 | (push (nreverse part) parts) |
|
83 | 83 | (remove "" |
|
84 | 84 | (loop :for part :in (nreverse parts) |
|
85 | 85 | :collect (case (first part) |
|
86 | 86 | ('text (p:text (rest part))) |
|
87 | 87 | (t part))) |
|
88 | 88 | :test #'equal))) |
|
89 | 89 | |
|
90 | 90 | (p:defrule line-continuation (and #\_ #\newline) |
|
91 | 91 | (:constant nil)) |
|
92 | 92 | |
|
93 | 93 | (p:defrule text-spaces (+ (or #\space #\tab line-continuation)) |
|
94 | 94 | (:text t)) |
|
95 | 95 | |
|
96 | 96 | (p:defrule spaces (+ (or #\space #\tab line-continuation)) |
|
97 | 97 | (:constant nil) |
|
98 | 98 | (:error-report nil)) |
|
99 | 99 | |
|
100 | 100 | (p:defrule spaces? (* (or #\space #\tab line-continuation)) |
|
101 | 101 | (:constant nil) |
|
102 | 102 | (:error-report nil)) |
|
103 | 103 | |
|
104 | 104 | (p:defrule colon #\: |
|
105 | 105 | (:constant nil)) |
|
106 | 106 | |
|
107 | 107 | (p:defrule equal #\= |
|
108 | 108 | (:constant nil)) |
|
109 | 109 | |
|
110 | 110 | (p:defrule alphanumeric (alphanumericp character)) |
|
111 | 111 | |
|
112 | 112 | (p:defrule not-newline (not-newline character)) |
|
113 | 113 | |
|
114 | 114 | (p:defrule squote-esc "''" |
|
115 | 115 | (:lambda (list) |
|
116 | 116 | (p:text (elt list 0)))) |
|
117 | 117 | |
|
118 | 118 | (p:defrule dquote-esc "\"\"" |
|
119 | 119 | (:lambda (list) |
|
120 | 120 | (p:text (elt list 0)))) |
|
121 | 121 | |
|
122 | 122 | (p:defrule sstring-char (or squote-esc (not-quote character)) |
|
123 | 123 | (:text t)) |
|
124 | 124 | |
|
125 | 125 | (p:defrule dstring-char (or dquote-esc (not-doublequote character)) |
|
126 | 126 | (:text t)) |
|
127 | 127 | |
|
128 | 128 | ;;; Identifiers |
|
129 | 129 | |
|
130 | 130 | (defparameter *keywords* '(act addlib addobj addqst and arrcomp arrpos arrsize cla clear *clear close clr *clr cls cmdclear cmdclr copyarr countobj curact curacts curloc debug delact dellib delobj desc disablescroll dynamic dyneval else elseif end exit loop freelib func getobj gosub goto gs gt if iif img *img inclib input instr isnum isplay jump killall killobj killqst killvar lcase len let loc local maintxt max menu mid min mod msecscount msg nl *nl no nosave obj opengame openqst or p *p pl *pl play qspver rand refint replace rgb rnd savegame selact selobj set settimer showacts showinput showobjs showstat stattxt step str strcomp strfind strpos to trim ucase unsel unselect user_text usrtxt val view wait while xgoto xgt)) |
|
131 | 131 | |
|
132 | 132 | (defun trim-$ (str) |
|
133 | 133 | (if (char= #\$ (elt str 0)) |
|
134 | 134 | (subseq str 1) |
|
135 | 135 | str)) |
|
136 | 136 | |
|
137 | 137 | (defun qsp-keyword-p (id) |
|
138 | 138 | (member (intern (trim-$ (string-upcase id))) *keywords*)) |
|
139 | 139 | |
|
140 | 140 | (defun not-qsp-keyword-p (id) |
|
141 | 141 | (not (member (intern (trim-$ (string-upcase id))) *keywords*))) |
|
142 | 142 | |
|
143 | 143 | (p:defrule qsp-keyword (qsp-keyword-p identifier-raw)) |
|
144 | 144 | |
|
145 | 145 | (p:defrule id-first (id-any-char character)) |
|
146 | 146 | (p:defrule id-next (or (id-any-char character) |
|
147 | 147 | (digit-char-p character))) |
|
148 | 148 | (p:defrule identifier-raw (and id-first (* id-next)) |
|
149 | 149 | (:lambda (list) |
|
150 | 150 | (intern (string-upcase (p:text list)) "TXT2WEB.LIB"))) |
|
151 | 151 | |
|
152 | 152 | (p:defrule identifier (not-qsp-keyword-p identifier-raw)) |
|
153 | 153 | |
|
154 | 154 | ;;; Strings |
|
155 | 155 | |
|
156 | 156 | (p:defrule qsp-string (or normal-string brace-string)) |
|
157 | 157 | |
|
158 | 158 | (p:defrule brace-string (and #\{ before-statement block-body #\}) |
|
159 | 159 | (:lambda (list) |
|
160 | 160 | (list* 'lib:qspblock (third list)))) |
|
161 | 161 | |
|
162 | 162 | (p:defrule normal-string (or sstring dstring) |
|
163 | 163 | (:lambda (str) |
|
164 | 164 | (list* 'lib:str (or str (list ""))))) |
|
165 | 165 | |
|
166 | 166 | (p:defrule sstring (and #\' (* (or sstring-interpol |
|
167 | 167 | sstring-exec |
|
168 | 168 | sstring-char)) |
|
169 | 169 | #\') |
|
170 | 170 | (:lambda (list) |
|
171 | 171 | (maybe-text (second list)))) |
|
172 | 172 | |
|
173 | 173 | (p:defrule dstring (and #\" (* (or dstring-interpol |
|
174 | 174 | dstring-exec |
|
175 | 175 | dstring-char)) |
|
176 | 176 | #\") |
|
177 | 177 | (:lambda (list) |
|
178 | 178 | (maybe-text (second list)))) |
|
179 | 179 | |
|
180 | 180 | (defun parse-interpol (list) |
|
181 | 181 | (p:parse 'expression (p:text (mapcar 'second (second list))))) |
|
182 | 182 | |
|
183 | 183 | (defun parse-exec (list) |
|
184 | (list* 'lib:exec (p:parse 'exec-body (p:text (second list))))) | |
|
184 | (list* 'lib:exec (p:parse 'exec-body (p:text (mapcar #'second (second list)))))) | |
|
185 | 185 | |
|
186 | 186 | (p:defrule sstring-interpol (and "<<" (+ (and (p:! ">>") |
|
187 | 187 | sstring-char)) |
|
188 | 188 | ">>") |
|
189 | 189 | (:function parse-interpol)) |
|
190 | 190 | |
|
191 | 191 | (p:defrule dstring-interpol (and "<<" (+ (and (p:! ">>") |
|
192 | 192 | dstring-char)) |
|
193 | 193 | ">>") |
|
194 | 194 | (:function parse-interpol)) |
|
195 | 195 | |
|
196 | 196 | (p:defrule sstring-exec (or (and (p:~ "\"exec:") |
|
197 | 197 | (+ (and (p:& (not-doublequote character)) sstring-char)) |
|
198 | 198 | #\") |
|
199 | 199 | (and (p:~ "''exec:") |
|
200 | 200 | (+ (not-quote character)) |
|
201 | 201 | "''")) |
|
202 | 202 | (:function parse-exec)) |
|
203 | 203 | |
|
204 | 204 | (p:defrule dstring-exec (or (and (p:~ "'exec:") |
|
205 | 205 | (+ (and (p:& (not-quote character)) dstring-char)) |
|
206 | 206 | #\') |
|
207 | 207 | (and (p:~ "\"\"exec") |
|
208 | 208 | (+ (not-doublequote character)) |
|
209 | 209 | "\"\"")) |
|
210 | 210 | (:function parse-exec)) |
|
211 | 211 | |
|
212 | 212 | ;;; Location |
|
213 | 213 | |
|
214 | 214 | (p:defrule txt2web-grammar (and (* (or spaces #\newline)) |
|
215 | 215 | (* location)) |
|
216 | 216 | (:lambda (list) |
|
217 | 217 | `(lib:game ,@(second list)))) |
|
218 | 218 | |
|
219 | 219 | (p:defrule location (and location-header block-body location-end) |
|
220 | 220 | (:destructure (header body end) |
|
221 | 221 | (declare (ignore end)) |
|
222 | 222 | `(lib:location (,header) ,@body))) |
|
223 | 223 | |
|
224 | 224 | (p:defrule location-header (and #\# |
|
225 | 225 | (+ not-newline) |
|
226 | 226 | (and #\newline spaces? before-statement)) |
|
227 | 227 | (:destructure (spaces1 name spaces2) |
|
228 | 228 | (declare (ignore spaces1 spaces2)) |
|
229 | 229 | (string-upcase (string-trim " " (p:text name))))) |
|
230 | 230 | |
|
231 | 231 | (p:defrule location-end (and #\- (* not-newline) #\newline before-statement) |
|
232 | 232 | (:constant nil)) |
|
233 | 233 | |
|
234 | 234 | ;;; Block body |
|
235 | 235 | |
|
236 | 236 | (p:defrule newline-block-body (and #\newline spaces? block-body) |
|
237 | 237 | (:function third)) |
|
238 | 238 | |
|
239 | 239 | (p:defrule block-body (* statement) |
|
240 | 240 | (:function remove-nil)) |
|
241 | 241 | |
|
242 | 242 | ;; Just for <a href="exec:...'> |
|
243 | 243 | ;; Explicitly called from that rule's production |
|
244 | 244 | (p:defrule exec-body (and before-statement line-body) |
|
245 | 245 | (:function second)) |
|
246 | 246 | |
|
247 | 247 | (p:defrule line-body (and inline-statement (* next-inline-statement)) |
|
248 | 248 | (:lambda (list) |
|
249 | 249 | (list* (first list) (second list)))) |
|
250 | 250 | |
|
251 | 251 | (p:defrule before-statement (* (or #\newline spaces)) |
|
252 | 252 | (:constant nil)) |
|
253 | 253 | |
|
254 | 254 | (p:defrule statement-end (or statement-end-real statement-end-block-close)) |
|
255 | 255 | |
|
256 | 256 | (p:defrule statement-end-real (and (or #\newline |
|
257 | 257 | (and #\& spaces? (p:& statement%))) |
|
258 | 258 | before-statement) |
|
259 | 259 | (:constant nil)) |
|
260 | 260 | |
|
261 | 261 | (p:defrule statement-end-block-close (or (p:& #\})) |
|
262 | 262 | (:constant nil)) |
|
263 | 263 | |
|
264 | 264 | (p:defrule inline-statement (and statement% spaces?) |
|
265 | 265 | (:function first)) |
|
266 | 266 | |
|
267 | 267 | (p:defrule next-inline-statement (and #\& spaces? inline-statement) |
|
268 | 268 | (:function third)) |
|
269 | 269 | |
|
270 | 270 | (p:defrule not-a-non-statement (and (p:! (p:~ "elseif")) |
|
271 | 271 | (p:! (p:~ "else")) |
|
272 | 272 | (p:! (p:~ "end")))) |
|
273 | 273 | |
|
274 | 274 | (p:defrule statement (and inline-statement statement-end) |
|
275 | 275 | (:function first)) |
|
276 | 276 | |
|
277 | 277 | (p:defrule statement% (and not-a-non-statement |
|
278 | 278 | (or label comment string-output |
|
279 | 279 | block non-returning-intrinsic local |
|
280 | 280 | assignment expression-output)) |
|
281 | 281 | (:function second)) |
|
282 | 282 | |
|
283 | 283 | (p:defrule expr-stopper (or comment block non-returning-intrinsic)) |
|
284 | 284 | |
|
285 | 285 | (p:defrule string-output qsp-string |
|
286 | 286 | (:lambda (string) |
|
287 | 287 | (list 'lib:main-pl string))) |
|
288 | 288 | |
|
289 | 289 | (p:defrule expression-output expression |
|
290 | 290 | (:lambda (list) |
|
291 | 291 | (list 'lib:main-pl list))) |
|
292 | 292 | |
|
293 | 293 | (p:defrule label (and colon identifier) |
|
294 | 294 | (:lambda (list) |
|
295 | 295 | (intern (string (second list)) :keyword))) |
|
296 | 296 | |
|
297 | 297 | (p:defrule comment (and #\! (* (or qsp-string brace-comment text-spaces not-newline))) |
|
298 | 298 | (:constant nil)) |
|
299 | 299 | |
|
300 | 300 | (p:defrule brace-comment (and #\{ (* (not-brace character)) #\}) |
|
301 | 301 | (:constant nil)) |
|
302 | 302 | |
|
303 | 303 | (p:defrule local (and (p:~ "local") spaces variable (p:? (and spaces? #\= spaces? expression))) |
|
304 | 304 | (:lambda (list) |
|
305 | 305 | (list* 'lib:local (third list) |
|
306 | 306 | (when (fourth list) |
|
307 | 307 | (list (fourth (fourth list))))))) |
|
308 | 308 | |
|
309 | 309 | ;;; Blocks |
|
310 | 310 | |
|
311 | 311 | (p:defrule block (or block-act block-if block-loop)) |
|
312 | 312 | |
|
313 | 313 | (p:defrule block-if (and block-if-head block-if-body) |
|
314 | 314 | (:destructure (head body) |
|
315 | 315 | `(lib:qspcond (,@head ,@(first body)) |
|
316 | 316 | ,@(rest body)))) |
|
317 | 317 | |
|
318 | 318 | (p:defrule block-if-head (and (p:~ "if") spaces expression spaces? colon spaces?) |
|
319 | 319 | (:function remove-nil) |
|
320 | 320 | (:function cdr)) |
|
321 | 321 | |
|
322 | 322 | (p:defrule block-if-body (or block-if-ml block-if-sl) |
|
323 | 323 | (:destructure (if-body elseifs else &rest ws) |
|
324 | 324 | (declare (ignore ws)) |
|
325 | 325 | `(,(remove-nil if-body) ,@elseifs ,@(when else `((else ,@else)))))) |
|
326 | 326 | |
|
327 | 327 | (p:defrule block-if-sl (and line-body |
|
328 | 328 | (p:? block-if-elseif-inline) |
|
329 | 329 | (p:? block-if-else-inline) |
|
330 | 330 | spaces?)) |
|
331 | 331 | |
|
332 | 332 | (p:defrule block-if-ml (and (and #\newline spaces?) |
|
333 | 333 | block-body |
|
334 | 334 | (p:? block-if-elseif) |
|
335 | 335 | (p:? block-if-else) |
|
336 | 336 | block-if-end) |
|
337 | 337 | (:lambda (list) |
|
338 | 338 | (cdr list))) |
|
339 | 339 | |
|
340 | 340 | (p:defrule block-if-elseif-inline (and block-if-elseif-head line-body (p:? block-if-elseif-inline)) |
|
341 | 341 | (:destructure (head statements elseif) |
|
342 | 342 | `((,@(rest head) ,@(remove-nil statements)) ,@elseif))) |
|
343 | 343 | |
|
344 | 344 | (p:defrule block-if-elseif (and block-if-elseif-head statement-end block-body (p:? block-if-elseif)) |
|
345 | 345 | (:destructure (head ws statements elseif) |
|
346 | 346 | (declare (ignore ws)) |
|
347 | 347 | `((,@(rest head) ,@(remove-nil statements)) ,@elseif))) |
|
348 | 348 | |
|
349 | 349 | (p:defrule block-if-elseif-head (and (p:~ "elseif") spaces expression spaces? colon spaces?) |
|
350 | 350 | (:function remove-nil) |
|
351 | 351 | (:function intern-first)) |
|
352 | 352 | |
|
353 | 353 | (p:defrule block-if-else-inline (and block-if-else-head line-body) |
|
354 | 354 | (:function second)) |
|
355 | 355 | |
|
356 | 356 | (p:defrule block-if-else (and block-if-else-head #\newline spaces? block-body) |
|
357 | 357 | (:function fourth)) |
|
358 | 358 | |
|
359 | 359 | (p:defrule block-if-else-head (and (p:~ "else") spaces?) |
|
360 | 360 | (:constant nil)) |
|
361 | 361 | |
|
362 | 362 | (p:defrule block-if-end (and (p:~ "end") |
|
363 | 363 | (p:? (and spaces (p:~ "if")))) |
|
364 | 364 | (:constant nil)) |
|
365 | 365 | |
|
366 | 366 | (p:defrule block-act (and block-act-head (or block-ml block-sl)) |
|
367 | 367 | (:lambda (list) |
|
368 | 368 | (apply #'append list))) |
|
369 | 369 | |
|
370 | 370 | (p:defrule block-act-head (and (p:~ "act") spaces? qsp-string spaces? |
|
371 | 371 | (p:? block-act-head-img) |
|
372 | 372 | colon spaces?) |
|
373 | 373 | (:lambda (list) |
|
374 | 374 | (intern-first (list (first list) |
|
375 | 375 | (third list) |
|
376 | 376 | (or (fifth list) '(lib:str "")))))) |
|
377 | 377 | |
|
378 | 378 | (p:defrule block-act-head-img (and #\, spaces? qsp-string spaces?) |
|
379 | 379 | (:lambda (list) |
|
380 | 380 | (or (third list) ""))) |
|
381 | 381 | |
|
382 | 382 | (p:defrule block-loop (and block-loop-head (or block-ml block-sl)) |
|
383 | 383 | (:lambda (list) |
|
384 | 384 | (apply #'append list))) |
|
385 | 385 | |
|
386 | 386 | (p:defrule block-loop-head (and (p:~ "loop") spaces |
|
387 | 387 | (p:? (and block-loop-head-init spaces?)) |
|
388 | 388 | block-loop-head-while spaces? |
|
389 | 389 | (p:? (and block-loop-head-step spaces?)) |
|
390 | 390 | colon spaces?) |
|
391 | 391 | (:lambda (list) |
|
392 | 392 | (break "~S" list) |
|
393 | 393 | (list 'lib:qsploop |
|
394 | 394 | (elt list 2) |
|
395 | 395 | (elt list 6) |
|
396 | 396 | (elt list 9) |
|
397 | 397 | (elt list 10)))) |
|
398 | 398 | |
|
399 | 399 | (p:defrule block-loop-head-init (or local plain-assignment)) |
|
400 | 400 | |
|
401 | 401 | (p:defrule block-loop-head-while (and (p:~ "while") eq-expr) |
|
402 | 402 | (:function second)) |
|
403 | 403 | |
|
404 | 404 | (p:defrule block-loop-head-step (and (p:~ "step") (or plain-assignment op-assignment)) |
|
405 | 405 | (:function second)) |
|
406 | 406 | |
|
407 | 407 | (p:defrule block-sl line-body) |
|
408 | 408 | |
|
409 | 409 | (p:defrule block-ml (and newline-block-body block-end) |
|
410 | 410 | (:lambda (list) |
|
411 | 411 | (apply #'list* (butlast list)))) |
|
412 | 412 | |
|
413 | 413 | (p:defrule block-end (and (p:~ "end")) |
|
414 | 414 | (:constant nil)) |
|
415 | 415 | |
|
416 | 416 | ;;; Calls |
|
417 | 417 | |
|
418 | 418 | (p:defrule first-argument (and expression spaces?) |
|
419 | 419 | (:function first)) |
|
420 | 420 | (p:defrule next-argument (and "," spaces? expression) |
|
421 | 421 | (:function third)) |
|
422 | 422 | (p:defrule arguments (or parenthesized-arguments plain-arguments no-arguments)) |
|
423 | 423 | (p:defrule parenthesized-arguments (and spaces? #\( base-arguments #\)) |
|
424 | 424 | (:function third)) |
|
425 | 425 | (p:defrule plain-arguments (and spaces? base-arguments) |
|
426 | 426 | (:function second)) |
|
427 | 427 | (p:defrule no-arguments (or (and spaces? (p:& #\newline)) |
|
428 | 428 | (and spaces? (p:& #\&)) |
|
429 | 429 | spaces?) |
|
430 | 430 | (:constant nil)) |
|
431 | 431 | (p:defrule base-arguments (or (and first-argument (* next-argument)) spaces?) |
|
432 | 432 | (:lambda (list) |
|
433 | 433 | (if (null list) |
|
434 | 434 | nil |
|
435 | 435 | (list* (first list) (second list))))) |
|
436 | 436 | |
|
437 | 437 | ;;; Intrinsics |
|
438 | 438 | |
|
439 | 439 | (defmacro defintrinsics ((rule-name returning-rule-name non-returning-rule-name) &body clauses) |
|
440 | 440 | `(progn |
|
441 | 441 | ,@(loop :for clause :in clauses |
|
442 | 442 | :collect `(defintrinsic ,@clause)) |
|
443 | 443 | (p:defrule ,returning-rule-name (or ,@(remove-nil |
|
444 | 444 | (mapcar (lambda (clause) |
|
445 | 445 | (when (second clause) |
|
446 | 446 | (alexandria:symbolicate |
|
447 | 447 | 'intrinsic- (first clause)))) |
|
448 | 448 | clauses)))) |
|
449 | 449 | (p:defrule ,non-returning-rule-name (or ,@(remove-nil |
|
450 | 450 | (mapcar (lambda (clause) |
|
451 | 451 | (unless (second clause) |
|
452 | 452 | (alexandria:symbolicate |
|
453 | 453 | 'intrinsic- (first clause)))) |
|
454 | 454 | clauses)))) |
|
455 | 455 | (p:defrule ,rule-name (or ,returning-rule-name ,non-returning-rule-name)))) |
|
456 | 456 | |
|
457 | 457 | (defmacro defintrinsic (sym returning &optional (min-arity 0) max-arity &rest names) |
|
458 | 458 | (declare (ignore returning)) |
|
459 | 459 | (unless max-arity |
|
460 | 460 | (setf max-arity *max-args*)) |
|
461 | 461 | (setf names |
|
462 | 462 | (if names |
|
463 | 463 | (mapcar #'string-upcase names) |
|
464 | 464 | (list (string sym)))) |
|
465 | 465 | `(p:defrule ,(alexandria:symbolicate 'intrinsic- sym) |
|
466 | 466 | (and (p:? #\$) (or ,@(loop :for name :in names :collect `(p:~ ,name))) |
|
467 | 467 | arguments) |
|
468 | 468 | (:destructure (dollar name arguments) |
|
469 | 469 | (declare (ignore dollar)) |
|
470 | 470 | (unless (<= ,min-arity (length arguments) ,max-arity) |
|
471 | 471 | (error "Intrinsic ~A expects between ~A and ~A arguments but ~A were provided:~%~S" |
|
472 | 472 | name ,min-arity ,max-arity (length arguments) arguments)) |
|
473 | 473 | (list* ',(intern (string sym) "TXT2WEB.LIB") arguments)))) |
|
474 | 474 | |
|
475 | 475 | (defintrinsics (intrinsic returning-intrinsic non-returning-intrinsic) |
|
476 | 476 | ;; Transitions |
|
477 | 477 | (goto% nil 0 nil "gt" "goto") |
|
478 | 478 | (xgoto% nil 0 nil "xgt" "xgoto") |
|
479 | 479 | ;; Variables |
|
480 | 480 | (killvar nil 0 2) |
|
481 | 481 | ;; Expressions |
|
482 | 482 | (obj t 1 1) |
|
483 | 483 | (loc t 1 1) |
|
484 | 484 | (no t 1 1) |
|
485 | 485 | ;; Basic |
|
486 | 486 | (qspver t 0 0) |
|
487 | 487 | (curloc t 0 0) |
|
488 | 488 | (rand t 1 2) |
|
489 | 489 | (rnd t 0 0) |
|
490 | 490 | (qspmax t 1 nil "max") |
|
491 | 491 | (qspmin t 1 nil "min") |
|
492 | 492 | ;; Arrays |
|
493 | 493 | (killall nil 0 0) |
|
494 | 494 | (copyarr nil 2 4) |
|
495 | 495 | (arrsize t 1 1) |
|
496 | 496 | (arrpos t 2 3) |
|
497 | 497 | (arrcomp t 2 3) |
|
498 | 498 | ;; Strings |
|
499 | 499 | (len t 1 1) |
|
500 | 500 | (mid t 2 3) |
|
501 | 501 | (ucase t 1 1) |
|
502 | 502 | (lcase t 1 1) |
|
503 | 503 | (trim t 1 1) |
|
504 | 504 | (qspreplace t 2 3 "replace") |
|
505 | 505 | (instr t 2 3) |
|
506 | 506 | (isnum t 1 1) |
|
507 | 507 | (val t 1 1) |
|
508 | 508 | (qspstr t 1 1 "str") |
|
509 | 509 | (strcomp t 2 2) |
|
510 | 510 | (strfind t 2 3) |
|
511 | 511 | (strpos t 2 3) |
|
512 | 512 | ;; IF |
|
513 | 513 | (iif t 2 3) |
|
514 | 514 | ;; Subs |
|
515 | 515 | (gosub nil 1 nil "gosub" "gs") |
|
516 | 516 | (func t 1 nil) |
|
517 | 517 | (exit nil 0 0) |
|
518 | 518 | ;; Jump |
|
519 | 519 | (jump nil 1 1) |
|
520 | 520 | ;; Dynamic |
|
521 | 521 | (dynamic nil 1 nil) |
|
522 | 522 | (dyneval t 1 nil) |
|
523 | 523 | ;; Sound |
|
524 | 524 | (play nil 1 2) |
|
525 | 525 | (isplay t 1 1) |
|
526 | 526 | (close nil 1 1) |
|
527 | 527 | (closeall nil 0 0 "close all") |
|
528 | 528 | ;; Main window |
|
529 | 529 | (main-pl nil 1 1 "*pl") |
|
530 | 530 | (main-nl nil 0 1 "*nl") |
|
531 | 531 | (main-p nil 1 1 "*p") |
|
532 | 532 | (maintxt t 0 0) |
|
533 | 533 | (desc t 1 1) |
|
534 | 534 | (main-clear nil 0 0 "*clear" "*clr") |
|
535 | 535 | ;; Aux window |
|
536 | 536 | (showstat nil 1 1) |
|
537 | 537 | (stat-pl nil 1 1 "pl") |
|
538 | 538 | (stat-nl nil 0 1 "nl") |
|
539 | 539 | (stat-p nil 1 1 "p") |
|
540 | 540 | (stattxt t 0 0) |
|
541 | 541 | (stat-clear nil 0 0 "clear" "clr") |
|
542 | 542 | (cls nil 0 0) |
|
543 | 543 | ;; Dialog |
|
544 | 544 | (msg nil 1 1) |
|
545 | 545 | ;; Acts |
|
546 | 546 | (showacts nil 1 1) |
|
547 | 547 | (delact nil 1 1 "delact" "del act") |
|
548 | 548 | (curacts t 0 0) |
|
549 | 549 | (selact t 0 0) |
|
550 | 550 | (cla nil 0 0) |
|
551 | 551 | ;; Objects |
|
552 | 552 | (showobjs nil 1 1) |
|
553 | 553 | (addobj nil 1 3 "addobj" "add obj") |
|
554 | 554 | (delobj nil 1 1 "delobj" "del obj") |
|
555 | 555 | (killobj nil 0 1) |
|
556 | 556 | (countobj t 0 0) |
|
557 | 557 | (getobj t 1 1) |
|
558 | 558 | (selobj t 0 0) |
|
559 | 559 | (unsel nil 0 0 "unsel" "unselect") |
|
560 | 560 | ;; Menu |
|
561 | 561 | (menu nil 1 1) |
|
562 | 562 | ;; Images |
|
563 | 563 | (refint nil 0 0) |
|
564 | 564 | (view nil 0 1) |
|
565 | 565 | (img nil 1) |
|
566 | 566 | (*img nil 1) |
|
567 | 567 | ;; Fonts |
|
568 | 568 | (rgb t 3 3) |
|
569 | 569 | ;; Input |
|
570 | 570 | (showinput nil 1 1) |
|
571 | 571 | (usertxt t 0 0 "user_text" "usrtxt") |
|
572 | 572 | (cmdclear nil 0 0 "cmdclear" "cmdclr") |
|
573 | 573 | (input t 1 1) |
|
574 | 574 | ;; Files |
|
575 | 575 | (openqst nil 1 1) |
|
576 | 576 | (addqst nil 1 1 "addqst" "addlib" "inclib") |
|
577 | 577 | (killqst nil 1 1 "killqst" "dellib" "freelib") |
|
578 | 578 | (opengame nil 0 0) |
|
579 | 579 | (savegame nil 0 0) |
|
580 | 580 | ;; Real time |
|
581 | 581 | (wait nil 1 1) |
|
582 | 582 | (msecscount t 0 0) |
|
583 | 583 | (settimer nil 1 1)) |
|
584 | 584 | |
|
585 | 585 | ;;; Expression |
|
586 | 586 | |
|
587 | 587 | (p:defrule expression or-expr) |
|
588 | 588 | |
|
589 | 589 | (p:defrule or-expr (and and-expr (* (and spaces? or-op spaces? and-expr))) |
|
590 | 590 | (:function do-binop)) |
|
591 | 591 | |
|
592 | 592 | (p:defrule and-expr (and eq-expr (* (and spaces? and-op spaces? eq-expr))) |
|
593 | 593 | (:function do-binop)) |
|
594 | 594 | |
|
595 |
(p:defrule eq-expr (and sum-expr (* (and spaces? |
|
|
596 | "=" "<" ">") | |
|
595 | (p:defrule eq-expr (and sum-expr (* (and spaces? comp-op | |
|
597 | 596 | spaces? sum-expr))) |
|
598 | 597 | (:function do-binop)) |
|
599 | 598 | |
|
599 | (p:defrule comp-op (or "<=" ">=" "=<" "=>" "<>" "=" "<" ">") | |
|
600 | (:lambda (op) | |
|
601 | (cond ((string= op "=>") ">=") | |
|
602 | ((string= op "=<") "<=") | |
|
603 | (t op)))) | |
|
604 | ||
|
600 | 605 | (p:defrule sum-expr (and mod-expr (* (and spaces? (or #\+ #\-) spaces? mod-expr))) |
|
601 | 606 | (:function do-binop)) |
|
602 | 607 | |
|
603 | 608 | (p:defrule mod-expr (and mul-expr (* (and spaces? (p:~ "mod") spaces? mul-expr))) |
|
604 | 609 | (:function do-binop)) |
|
605 | 610 | |
|
606 | 611 | (p:defrule mul-expr (and unary-expr (* (and spaces? (or #\* #\/) spaces? unary-expr))) |
|
607 | 612 | (:function do-binop)) |
|
608 | 613 | |
|
609 | 614 | (p:defrule unary-expr (and (p:? (or #\+ #\-)) atom-expr) |
|
610 | 615 | (:lambda (list) |
|
611 | 616 | (let ((expr (remove-nil list))) |
|
612 | 617 | (if (= 1 (length expr)) |
|
613 | 618 | (first expr) |
|
614 | 619 | (intern-first expr))))) |
|
615 | 620 | |
|
616 | 621 | (p:defrule atom-expr (and (or variable literal returning-intrinsic paren-expr) spaces?) |
|
617 | 622 | (:function first)) |
|
618 | 623 | |
|
619 | 624 | (p:defrule paren-expr (and #\( spaces? expression spaces? #\)) |
|
620 | 625 | (:function third)) |
|
621 | 626 | |
|
622 | 627 | (p:defrule or-op (p:~ "or") |
|
623 | 628 | (:constant "or")) |
|
624 | 629 | |
|
625 | 630 | (p:defrule and-op (p:~ "and") |
|
626 | 631 | (:constant "and")) |
|
627 | 632 | |
|
628 | 633 | ;;; Variables |
|
629 | 634 | |
|
630 | 635 | (p:defrule variable (and identifier (p:? array-index)) |
|
631 | 636 | (:destructure (id idx-raw) |
|
632 | 637 | (let ((idx (case idx-raw |
|
633 | 638 | ((nil) 0) |
|
634 | 639 | (:last nil) |
|
635 | 640 | (t idx-raw)))) |
|
636 | 641 | (list 'lib:qspvar id idx)))) |
|
637 | 642 | |
|
638 | 643 | (p:defrule array-index (and #\[ spaces? (p:? expression) spaces? #\]) |
|
639 | 644 | (:lambda (list) |
|
640 | 645 | (or (third list) :last))) |
|
641 | 646 | |
|
642 | 647 | (p:defrule assignment (or kw-assignment plain-assignment op-assignment) |
|
643 | 648 | (:destructure (qspvar eq expr) |
|
644 | 649 | (declare (ignore eq)) |
|
645 | 650 | (list 'lib:set qspvar expr))) |
|
646 | 651 | |
|
647 | 652 | (p:defrule kw-assignment (and (or (p:~ "let") (p:~ "set")) spaces? (or op-assignment plain-assignment)) |
|
648 | 653 | (:function third)) |
|
649 | 654 | |
|
650 | 655 | (p:defrule op-assignment (and variable spaces? (or "+" "-" "*" "/") #\= spaces? expression) |
|
651 | 656 | (:destructure (qspvar ws1 op eq ws2 expr) |
|
652 | 657 | (declare (ignore ws1 ws2)) |
|
653 | 658 | (list qspvar eq (intern-first (list op qspvar expr))))) |
|
654 | 659 | |
|
655 | 660 | (p:defrule plain-assignment (and variable spaces? #\= spaces? expression) |
|
656 | 661 | (:function remove-nil)) |
|
657 | 662 | |
|
658 | 663 | ;;; Non-string literals |
|
659 | 664 | |
|
660 | 665 | (p:defrule literal (or qsp-string brace-string number)) |
|
661 | 666 | |
|
662 | 667 | (p:defrule number (+ (or #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)) |
|
663 | 668 | (:lambda (list) |
|
664 | 669 | (parse-integer (p:text list)))) |
@@ -1,391 +1,397 b'' | |||
|
1 | 1 | |
|
2 | 2 | (in-package txt2web.lib) |
|
3 | 3 | |
|
4 | 4 | ;;;; Parenscript macros which make the parser's intermediate |
|
5 | 5 | ;;;; representation directly compilable by Parenscript |
|
6 | 6 | ;;;; Some utility macros for other .ps sources too. |
|
7 | 7 | |
|
8 | 8 | ;;;; Block type | Has own locals | Has labels | async |
|
9 | 9 | ;;; Location | TRUE | TRUE | TRUE |
|
10 | 10 | ;;; Act | TRUE | TRUE | TRUE |
|
11 | 11 | ;;; {} | TRUE | TRUE | TRUE |
|
12 | 12 | ;;; IF | FALSE | TRUE | TRUE |
|
13 | 13 | ;;; FOR | FALSE | TRUE | TRUE |
|
14 | 14 | ;;; |
|
15 | 15 | ;;; IF and FOR are actually not blocks at all. They're implemented as Javascript's if and for loops. |
|
16 | 16 | ;;; Jumps back are also optimized to Javascript's while loops. |
|
17 | 17 | |
|
18 | 18 | ;;; Utils |
|
19 | 19 | |
|
20 | 20 | ;;; Common |
|
21 | 21 | |
|
22 | 22 | (defpsmacro label-block (() &body body) |
|
23 | 23 | (let ((has-labels (some #'keywordp body))) |
|
24 | 24 | `(block nil |
|
25 | 25 | ,@(when has-labels |
|
26 | 26 | '((var _labels (list)))) |
|
27 | 27 | (tagbody |
|
28 | 28 | ,@body |
|
29 | 29 | (void))))) |
|
30 | 30 | |
|
31 | 31 | (defpsmacro str (&rest forms) |
|
32 | 32 | (cond ((zerop (length forms)) |
|
33 | 33 | "") |
|
34 | 34 | ((and (= 1 (length forms)) |
|
35 | 35 | (stringp (first forms))) |
|
36 | 36 | (first forms)) |
|
37 | 37 | (t |
|
38 | 38 | `(& ,@forms)))) |
|
39 | 39 | |
|
40 | 40 | (defpsmacro locals-block (&body body) |
|
41 | 41 | "Includes labels too (through qsp-lambda)" |
|
42 | 42 | (let ((*locals* nil)) |
|
43 | 43 | (walker:walk 'locals body) |
|
44 | 44 | `(qsp-lambda |
|
45 | 45 | (create-locals ,*locals*) |
|
46 | 46 | ,@(walker:walk 'apply-vars body)))) |
|
47 | 47 | |
|
48 | 48 | ;;; 1loc |
|
49 | 49 | |
|
50 | 50 | (defparameter *special-variables* |
|
51 | 51 | '((usehtml 0) |
|
52 | (args 0) | |
|
53 | ($args 0) | |
|
52 | 54 | (result 0) |
|
53 | 55 | ($result 0) |
|
54 | 56 | ($ongload 0) |
|
55 | 57 | ($ongsave 0) |
|
56 | 58 | ($onobjadd 0) |
|
57 | 59 | ($onobjdel 0) |
|
58 | 60 | ($onobjsel 0) |
|
59 | 61 | ($onnewloc 0) |
|
60 | 62 | ($onactsel 0) |
|
61 | 63 | ($counter 0) |
|
62 | 64 | ($usercom 0))) |
|
63 | 65 | |
|
64 | 66 | (defpsmacro game ((name) &body body) |
|
65 | 67 | (setf body (walker:walk 'for-transform body)) |
|
66 | 68 | (setf *globals* *special-variables*) |
|
67 | 69 | (walker:walk 'globals body) |
|
68 | 70 | `(progn |
|
69 | 71 | ;; Game object |
|
70 | 72 | (setf (@ *games ,name) |
|
71 | 73 | (create)) |
|
72 | 74 | ;; Global variables from this game |
|
73 |
( |
|
|
75 | (setf (@ *default-globals ,name) | |
|
76 | (create-globals ,*globals*)) | |
|
74 | 77 | ;; Locations |
|
75 | 78 | ,@(loop :for location :in body |
|
76 | 79 | :collect `(setf (@ *games ,name ,(caadr location)) |
|
77 | 80 | ,location)))) |
|
78 | 81 | |
|
79 | 82 | (defpsmacro location ((name) &body body) |
|
80 | 83 | (declare (ignore name)) |
|
81 | 84 | "Name is used by the game macro above" |
|
82 | 85 | `(locals-block ,@body)) |
|
83 | 86 | |
|
84 | 87 | (defpsmacro goto% (target &rest args) |
|
85 | 88 | `(progn |
|
86 | (goto ,target ,args) | |
|
89 | (goto ,target ,@args) | |
|
87 | 90 | (exit))) |
|
88 | 91 | |
|
89 | 92 | (defpsmacro xgoto% (target &rest args) |
|
90 | 93 | `(progn |
|
91 | (xgoto ,target ,args) | |
|
94 | (xgoto ,target ,@args) | |
|
92 | 95 | (exit))) |
|
93 | 96 | |
|
94 | 97 | ;;; 2var |
|
95 | 98 | |
|
96 | 99 | (defvar *globals* nil) |
|
97 | 100 | (defvar *locals* nil) |
|
98 | 101 | |
|
99 | 102 | (defpsmacro create-globals (globals) |
|
100 | 103 | (flet ((indexes (name) |
|
101 | 104 | (remove nil |
|
102 | 105 | (remove-if #'listp |
|
103 | 106 | (mapcar #'second |
|
104 | 107 | (remove name globals |
|
105 | 108 | :key #'first |
|
106 | 109 | :test-not #'eq)))))) |
|
107 | 110 | (let ((names (remove-duplicates (mapcar #'first globals)))) |
|
108 |
`(c |
|
|
109 | (assign *globals | |
|
110 | (create | |
|
111 | `(create | |
|
111 | 112 |
|
|
112 | 113 |
|
|
113 | 114 |
|
|
114 | 115 |
|
|
115 |
|
|
|
116 | (api-call new-var ,name ,@indexes))))))) | |
|
116 | 117 | |
|
117 | 118 | (walker:deftransform globals qspvar (&rest var) |
|
118 | 119 | (pushnew var *globals* :test #'equal) |
|
119 | 120 | (walker:walk-continue)) |
|
120 | 121 | |
|
121 | 122 | (walker:deftransform globals local (var &rest expr) |
|
122 | 123 | (declare (ignore var)) |
|
123 | 124 | (walker:walk 'globals expr)) |
|
124 | 125 | |
|
125 | 126 | (defpsmacro create-locals (locals) |
|
126 | 127 | (when locals |
|
127 | 128 | `(progn |
|
128 | 129 | (var locals (create |
|
129 | 130 | ,@(loop :for (sym index) :in locals |
|
130 | 131 | :for name := (string-upcase sym) |
|
131 | 132 | :append `(,name (api-call new-var ,name)))))))) |
|
132 | 133 | |
|
133 | 134 | ;; locations, blocks, and acts all have their own locals namespace |
|
134 | 135 | (walker:deftransform-stop locals qspblock) |
|
135 | 136 | (walker:deftransform-stop locals act) |
|
136 | 137 | |
|
137 | 138 | (walker:deftransform locals local (var &optional expr) |
|
138 | 139 | (declare (ignore expr)) |
|
139 | 140 | (pushnew (rest var) *locals* :test #'equal) |
|
140 | 141 | nil) |
|
141 | 142 | |
|
142 | 143 | ;; index types: |
|
143 | 144 | ;; literal number |
|
144 | 145 | ;; literal string |
|
145 | 146 | ;; variable number |
|
146 | 147 | ;; variable string |
|
147 | 148 | ;; expression (may be possible to determine if it's a string or a number) |
|
148 | 149 | |
|
149 | 150 | (defun $-var-p (sym) |
|
150 | 151 | (char= #\$ (elt (string-upcase (symbol-name sym)) 0))) |
|
151 | 152 | |
|
152 | 153 | (defun literal-string-p (form) |
|
153 | 154 | (and (listp form) |
|
154 | 155 | (= 2 (length form)) |
|
155 | 156 | (eq 'str (first form)) |
|
156 | 157 | (stringp (second form)))) |
|
157 | 158 | |
|
158 | 159 | (defun variable-number-p (form) |
|
159 | 160 | (and (listp form) |
|
160 | 161 | (eq 'qspvar (first form)) |
|
161 | 162 | (not ($-var-p (second form))))) |
|
162 | 163 | |
|
163 | 164 | (defun variable-string-p (form) |
|
164 | 165 | (and (listp form) |
|
165 | 166 | (eq 'qspvar (first form)) |
|
166 | 167 | ($-var-p (second form)))) |
|
167 | 168 | |
|
168 | 169 | (walker:deftransform apply-vars set (var expr) |
|
169 | 170 | (destructuring-bind (qspvar name index) |
|
170 | 171 | var |
|
171 | 172 | (declare (ignore qspvar)) |
|
172 | 173 | (setf name (string-upcase name)) |
|
173 | 174 | (let ((slot `(getprop |
|
174 | 175 | ,(if (member name *locals* :key #'first) |
|
175 | 176 | 'locals '*globals) |
|
176 | 177 | ,name)) |
|
177 | 178 | (index (walker:walk 'apply-vars index)) |
|
178 | 179 | (value (walker:walk 'apply-vars expr))) |
|
179 | 180 | (cond |
|
180 | 181 | ((member name api:*serv-vars* :test #'equalp) |
|
181 | 182 | `(api:set-serv-var ,name ,index ,value)) |
|
182 | 183 | ((null index) |
|
183 | 184 | `(chain (elt ,slot) (push ,value))) |
|
184 | 185 | ((or (numberp index) |
|
185 | 186 | (variable-number-p index)) |
|
186 | 187 | `(setf (elt ,slot ,index) ,value)) |
|
187 | 188 | ((or (literal-string-p index) |
|
188 | 189 | (variable-string-p index)) |
|
189 | 190 | `(api:set-str-element ,slot ,index ,value)) |
|
190 | 191 | (t |
|
191 | 192 | `(api:set-any-element ,slot ,index ,value)))))) |
|
192 | 193 | |
|
193 | 194 | (walker:deftransform apply-vars local (var &optional expr) |
|
194 | 195 | ;; TODO: var can't be a service variable |
|
195 | 196 | (when expr |
|
196 | 197 | (walker:walk 'apply-vars (list 'set var expr)))) |
|
197 | 198 | |
|
198 | 199 | (walker:deftransform apply-vars qspvar (name index) |
|
199 | 200 | (let ((slot `(getprop |
|
200 | 201 | ,(if (member name *locals* :key #'first) 'locals '*globals) |
|
201 | 202 | ,(string-upcase name)))) |
|
202 | 203 | (cond |
|
203 | 204 | ((null index) |
|
204 | 205 | `(elt ,slot (1- (length ,slot)))) |
|
205 | 206 | ((or (numberp index) |
|
206 | 207 | (variable-number-p index)) |
|
207 | 208 | `(elt ,slot ,(walker:walk-continue index))) |
|
208 | 209 | ((or (literal-string-p index) |
|
209 | 210 | (variable-string-p index)) |
|
210 | 211 | `(elt ,slot (@ ,slot :indexes ,(walker:walk-continue index)))) |
|
211 | 212 | (t |
|
212 | 213 | `(get-element ,slot ,(walker:walk-continue index)))))) |
|
213 | 214 | |
|
214 | 215 | (walker:deftransform apply-vars qspblock (&rest block) |
|
215 | 216 | (declare (ignore block)) |
|
216 | 217 | (walker:whole)) |
|
217 | 218 | (walker:deftransform apply-vars act (&rest block) |
|
218 | 219 | (declare (ignore block)) |
|
219 | 220 | (walker:whole)) |
|
220 | 221 | (walker:deftransform apply-vars qspfor (var from to step body) |
|
221 | 222 | (list* 'qspfor var (mapcar #'walker:walk-continue (list from to step body)))) |
|
222 | 223 | |
|
224 | (defpsmacro get-slot (name) | |
|
225 | `(getprop | |
|
226 | (if (chain locals (includes name)) locals *globals) | |
|
227 | (string-upcase name))) | |
|
228 | ||
|
223 | 229 | ;;; 3expr |
|
224 | 230 | |
|
225 | 231 | (defpsmacro <> (op1 op2) |
|
226 | 232 | `(not (equal ,op1 ,op2))) |
|
227 | 233 | |
|
228 | 234 | (defpsmacro qspmod (&rest ops) |
|
229 | 235 | (case (length ops) |
|
230 | 236 | (1 (first ops)) |
|
231 | 237 | (2 `(mod ,@ops)) |
|
232 | 238 | (t `(mod ,(first ops) (qspmod ,@(rest ops)))))) |
|
233 | 239 | |
|
234 | 240 | ;;; 4code |
|
235 | 241 | |
|
236 | 242 | (defpsmacro exec (&body body) |
|
237 | 243 | (format nil "javascript: ~{~A~^~%~}" (mapcar #'ps* body))) |
|
238 | 244 | |
|
239 | 245 | ;;; 5arrays |
|
240 | 246 | |
|
241 | 247 | ;;; 6str |
|
242 | 248 | |
|
243 | 249 | (defpsmacro & (&rest args) |
|
244 | 250 | `(chain "" (concat ,@args))) |
|
245 | 251 | |
|
246 | 252 | ;;; 7if |
|
247 | 253 | |
|
248 | 254 | (defpsmacro qspcond (&rest clauses) |
|
249 | 255 | `(cond ,@(loop :for clause :in clauses |
|
250 | 256 | :for f := (if (eq 'txt2web::else (first clause)) |
|
251 | 257 | 't |
|
252 | 258 | (first clause)) |
|
253 | 259 | :collect (list f |
|
254 | 260 | `(tagbody |
|
255 | 261 | ,@(rest clause)))))) |
|
256 | 262 | |
|
257 | 263 | ;;; 8sub |
|
258 | 264 | |
|
259 | 265 | ;;; 9jump |
|
260 | 266 | ;;; Yep, that's a huge kludge since Javascript doesn't support arbitrary labels |
|
261 | 267 | |
|
262 | 268 | (defpsmacro jump (target) |
|
263 | 269 | `(return-from label-body ,(string-upcase (second target)))) |
|
264 | 270 | |
|
265 | 271 | (defpsmacro tagbody (&body body) |
|
266 | 272 | (let ((create-locals (if (eq (caar body) 'create-locals) |
|
267 | 273 | (list (car body)))) |
|
268 | 274 | (void (if (equal (car (last body)) '(void)) |
|
269 | 275 | '((void))))) |
|
270 | 276 | (when create-locals |
|
271 | 277 | (setf body (cdr body))) |
|
272 | 278 | (when void |
|
273 | 279 | (setf body (butlast body))) |
|
274 | 280 | (let ((funcs (list nil "_nil"))) |
|
275 | 281 | (dolist (form body) |
|
276 | 282 | (cond ((keywordp form) |
|
277 | 283 | (setf (first funcs) (reverse (first funcs))) |
|
278 | 284 | (push (string-upcase form) funcs) |
|
279 | 285 | (push nil funcs)) |
|
280 | 286 | (t |
|
281 | 287 | (push form (first funcs))))) |
|
282 | 288 | (setf (first funcs) (reverse (first funcs))) |
|
283 | 289 | (setf funcs (reverse funcs)) |
|
284 | 290 | `(progn |
|
285 | 291 | ,@create-locals |
|
286 | 292 | ,(if (= 2 (length funcs)) |
|
287 | 293 | `(progn |
|
288 | 294 | ,@body) |
|
289 | 295 | `(progn |
|
290 | 296 | (tagbody-blocks ,funcs) |
|
291 | 297 | (loop |
|
292 | 298 | :for _nextblock |
|
293 | 299 | := :_nil |
|
294 | 300 | :then (await (funcall (getprop _labels _nextblock))) |
|
295 | 301 | :while _nextblock))) |
|
296 | 302 | ,@void)))) |
|
297 | 303 | |
|
298 | 304 | (defvar *current-label*) |
|
299 | 305 | (defvar *has-jump-back*) |
|
300 | 306 | (walker:deftransform optimize-jump jump (target) |
|
301 | 307 | (cond ((string= (string-upcase (second target)) *current-label*) |
|
302 | 308 | (setf *has-jump-back* t) |
|
303 | 309 | '(continue)) |
|
304 | 310 | (t |
|
305 | 311 | (walker:walk-continue)))) |
|
306 | 312 | |
|
307 | 313 | (defpsmacro tagbody-blocks (funcs) |
|
308 | 314 | `(setf ,@(loop :for (label code . rest-labels) :on funcs :by #'cddr |
|
309 | 315 | :append `((@ _labels ,label) |
|
310 | 316 | (async-lambda () |
|
311 | 317 | (block label-body |
|
312 | 318 | (tagbody-block-body ,label ,code |
|
313 | 319 | ,(first rest-labels)))))))) |
|
314 | 320 | |
|
315 | 321 | (defpsmacro tagbody-block-body (label code next-label) |
|
316 | 322 | (let ((*current-label* label) |
|
317 | 323 | (*has-jump-back* nil)) |
|
318 | 324 | (let ((code (walker:walk 'optimize-jump code))) |
|
319 | 325 | (if *has-jump-back* |
|
320 | 326 | `(progn |
|
321 | 327 | (loop :do (progn |
|
322 | 328 | ,@code |
|
323 | 329 | (break))) |
|
324 | 330 | ,@(if next-label |
|
325 | 331 | (list next-label) |
|
326 | 332 | nil)) |
|
327 | 333 | `(progn |
|
328 | 334 | ,@code |
|
329 | 335 | ,@(if next-label |
|
330 | 336 | (list next-label) |
|
331 | 337 | nil)))))) |
|
332 | 338 | |
|
333 | 339 | (defpsmacro exit () |
|
334 | 340 | '(return-from nil (values))) |
|
335 | 341 | |
|
336 | 342 | ;;; 10dynamic |
|
337 | 343 | |
|
338 | 344 | (defpsmacro qspblock (&body body) |
|
339 | 345 | `(locals-block |
|
340 | 346 | ,@body)) |
|
341 | 347 | |
|
342 | 348 | (defpsmacro qsp-lambda (&body body) |
|
343 | 349 | `(async-lambda (args) |
|
344 | 350 | (label-block () |
|
345 | 351 | ,@body))) |
|
346 | 352 | |
|
347 | 353 | ;;; 11main |
|
348 | 354 | |
|
349 | 355 | (defpsmacro act (name img &body body) |
|
350 | 356 | `(api-call add-act ,name ,img |
|
351 | 357 | (locals-block |
|
352 | 358 | ,@body))) |
|
353 | 359 | |
|
354 | 360 | ;;; 12aux |
|
355 | 361 | |
|
356 | 362 | ;;; 13diag |
|
357 | 363 | |
|
358 | 364 | ;;; 14act |
|
359 | 365 | |
|
360 | 366 | ;;; 15objs |
|
361 | 367 | |
|
362 | 368 | ;;; 16menu |
|
363 | 369 | |
|
364 | 370 | ;;; 17sound |
|
365 | 371 | |
|
366 | 372 | ;;; 18img |
|
367 | 373 | |
|
368 | 374 | ;;; 19input |
|
369 | 375 | |
|
370 | 376 | ;;; 20time |
|
371 | 377 | |
|
372 | 378 | ;;; 21local |
|
373 | 379 | |
|
374 | 380 | ;;; 22loop |
|
375 | 381 | |
|
376 | 382 | (defpsmacro qsploop (init cond step &body body) |
|
377 | 383 | `(progn |
|
378 | 384 | ,init |
|
379 | 385 | (loop :while ,cond |
|
380 | 386 | :do (progn |
|
381 | 387 | ,@body |
|
382 | 388 | ,step)))) |
|
383 | 389 | |
|
384 | 390 | ;; Transform because it creates a (set ...) hence it has to be processed |
|
385 | 391 | ;; before the apply-vars transform. And macros are processed after all |
|
386 | 392 | ;; the transforms |
|
387 | 393 | (walker:deftransform for-transform qspfor (var from to step &rest body) |
|
388 | 394 | `(loop :for i :from ,from :to ,to :by ,step |
|
389 | 395 | :do (set ,var i) |
|
390 | 396 | :do (block nil |
|
391 | 397 | ,@(walker:walk-continue body)))) |
General Comments 0
You need to be logged in to leave comments.
Login now