##// END OF EJS Templates
Fix two regressions
naryl -
r64:44143cfd default
parent child Browse files
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") "&amp;")
102 105 (replace (regex "/</g") "&lt;")
103 106 (replace (regex "/>/g") "&gt;")
104 107 (replace (regex "/\"/g") "&quot;")
105 108 (replace (regex "/'/g") "&apos;")))
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 (length args))
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-var args i :num arg)
135 (set-var args i :str arg)))))
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 (store name &optional index)
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 &optional index)
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 nil num)
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 #:init-args #:get-result #:call-loc #:call-act
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? (or "<=" ">=" "=<" "=>" "<>"
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 (create-globals ,*globals*)
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 `(chain *object
109 (assign *globals
110 (create
111 `(create
111 112 ,@(loop :for sym :in names
112 113 :for indexes := (indexes sym)
113 114 :for name := (string-upcase sym)
114 115 :append `(,name
115 (api-call new-var ,name ,@indexes)))))))))
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