##// END OF EJS Templates
MENU
naryl -
r30:3c634d0a default
parent child Browse files
Show More
@@ -1,13 +1,22 b''
1
2 # start
3 act 'Показать меню':
4 gs 'menu'
5 end
6 -
1 7
2 8 # menu
9 killvar 'usr_menu'
3 10 ! нет иконки
4 $usr_menu[0] = 'Взять предмет:take_item'
11 $usr_menu[] = 'Взять предмет:take_item'
5 12 ! иконка задана gif-файлом
6 $usr_menu[1] = 'Положить предмет:put_item:images/put_item.gif'
13 $usr_menu[] = 'Положить предмет:put_item:images/put_item.gif'
7 14 ! иконка задана значением $icon_file
8 $usr_menu[2] = 'Осмотреть предмет:look_item:<<$icon_file>>'
15 $usr_menu[] = 'Осмотреть предмет:look_item:<<$icon_file>>'
16 ! Разделитель
17 $usr_menu[] = '-:-'
9 18 ! пункт меню задан 3-мя переменными
10 $usr_menu[3] = '<<$name>>:<<$loc>>:<<$file>>'
19 $usr_menu[] = '<<$name>>:<<$locname>>:<<$file>>'
11 20
12 21 menu 'usr_menu' &! покажет меню из 4-х пунктов
13 22 -
@@ -1,23 +1,23 b''
1 1
2 2 <div id="qsp">
3 3 <div class="qsp-col qsp-col1">
4 4 <div id="qsp-main" class="qsp-frame">&nbsp;</div>
5 5 <div id="qsp-acts" class="qsp-frame">&nbsp;</div>
6 6 <input id="qsp-input" class="qsp-frame">
7 7 </div>
8 8 <div class="qsp-col qsp-col2">
9 9 <div id="qsp-stat" class="qsp-frame">&nbsp;</div>
10 10 <div id="qsp-objs" class="qsp-frame">&nbsp;</div>
11 11 </div>
12 12 <div class="qsp-col qsp-col3">
13 13 <a id="qsp-btn-save"><img></a>
14 14 <a id="qsp-btn-open"><img></a>
15 15 </div>
16 16 </div>
17 17
18 18 <div id="qsp-dropdown">
19 19 </div>
20 20
21 <div id="qsp-image-container">
21 <div id="qsp-image-container" class="center-on-screen">
22 22 <img id="qsp-image">
23 23 </div>
@@ -1,121 +1,129 b''
1 1
2 2 .qsp-frame {
3 3 border: 1px solid black;
4 4 overflow: auto;
5 5 padding: 5px;
6 6 box-sizing: border-box;
7 7 }
8 8
9 9 #qsp {
10 10 position: absolute;
11 11 display: flex;
12 12 flex-flow: row;
13 13 top: 0;
14 14 left: 0;
15 15 width: 100%;
16 16 height: 100%;
17 17 }
18 18
19 19 .qsp-col {
20 20 display: flex;
21 21 flex-flow: column;
22 22 }
23 23
24 24 .qsp-col1 {
25 25 flex: 7 7 70px;
26 26 }
27 27
28 28 .qsp-col2 {
29 29 flex: 3 3 30px;
30 30 }
31 31
32 32 .qsp-col3 {
33 33 flex: 0 0 40px;
34 34 }
35 35
36 36 #qsp-main {
37 37 flex: 6 6 60px;
38 38 }
39 39
40 40 #qsp-acts {
41 41 flex: 4 4 40px;
42 42 }
43 43
44 44 #qsp-input {
45 45 }
46 46
47 47 #qsp-stat {
48 48 flex: 5 5 50px;
49 49 }
50 50
51 51 #qsp-objs {
52 52 flex: 5 5 50px;
53 53 }
54 54
55 55 .qsp-act {
56 56 display: block;
57 57 padding: 2px;
58 58 font-size: large;
59 59 }
60 60
61 61 .qsp-act:hover {
62 62 outline: #9E9E9E outset 3px
63 63 }
64 64
65 65 /* Dropdown */
66 66
67 67 #qsp-dropdown {
68 68 display: none;
69 69 position: absolute;
70 70 background-color: #f1f1f1;
71 71 min-width: 160px;
72 72 overflow: auto;
73 73 box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
74 74 z-index: 1;
75 75 margin: auto;
76 top: 200;
77 76 }
78 77
79 78 #qsp-dropdown a {
80 79 color: black;
81 80 padding: 12px 16px;
82 81 text-decoration: none;
83 82 display: block;
84 83 }
85 84
86 85 #qsp-dropdown a:hover {
87 86 background-color: #ddd;
88 87 }
89 88
90 89 /* Buttons */
91 90
92 91 .qsp-col3 a, .qsp-col3 img {
93 92 width: 50px;
94 93 height: 50px;
95 94 }
96 95
97 96 #qsp-btn-save img {
98 97 background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAACSklEQVRoge3ZPWgUQRjG8V/iRRFU/GoEQdSohSgWWqQSooVgK9iJFiKCnYV2BkFQsBRBQQwWVsHKIoWxEOzEwmhhogbRMhhFicTvYm+JCUlmZzabRLN/OPaWeZ+ZeW7feXf2lpqampqamnRaImKP4hA2VjCP27hbpoNGwbib2IPreB+IPYY7M7SvxWYM4QNO4QqWyQxVRgdeYnnB+K5A+wUcbB7z+O14hxPx08toLRDTgfv4mjrIJFrwwMS0HsABXMTxlE6LpNYqfE7pPJIBdOKhLM1uxIiLrpG5YtC4GSLMVGGkLdD+GpfxAuvxaVL7oCzN+jCG7iKDVmHkKS5hSSBuJ3bg6hRt+Zrpa553z8bEuoQrUVXk1exwKLBI1ZpPBnAL+0KBqanVim1Ykaifji+yNfIrVphipB1n8BgjCfqZ2IrTuIZXMcIUIydxFj8TtEW4JysW52JEKWtkVHUm4EdzjChSrkgLlmJXghb6m8ep9MN4K25XjvTF3sCaElrT6McS+0w2Mirb+JWhrH4CC/0+UpjUK7Ia5/86f4IeHMHeEvPJ+4km1chHE43k9KROpCyLPrViym8/vjW/b5Jt3acjL7/RzEX5bRg3sjKg+2fK7/PmZ9aZzdTqxwYzp06IBZFaDeHUCbFgUquy1Anx35TfRWXkd+WzCBOcQxEjQ7JH0PmiHW9CQUWM9Mr+Y+osO6ME8nF7Q4FFn8T2y14VDMt2vsFfqCRbZBvTdbLXFI9CgphHyjbsVu4+EcMInuH7HI1XU1NTU7OI+QMFe2N82rtssgAAAABJRU5ErkJggg==');
99 98 }
100 99
101 100 #qsp-btn-open img {
102 101 background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAACCklEQVRoge3Yz6sNYRzH8deVexNxSaEoroWFUuomOceSPVnIwsbSxs6Psrgphf9AsrZR7kJWsroUlrZKKZRSl4USGouZk9Npzp1n5pm5x4/nXdNzpvP9Pp/v95nv82OGRCKRSCQS/w8zuIn3yGpe37CEQ6sedQk38BA7G/jO4BQ+YEebQZUxVfH/OxzGVSxX2D4o2mO4jmt4jpNFe6d5mPFkRbvQ0H9h6OqUtR33/xXr0celGn4/sYjXoQ5dJ3ILB7AOW2r4bcIj7GsrkEFpXWyrwwbaf15nXWqv6SqK1abuHDmPjV0EMobhBWIZt8cZVu0j2ZDNHF7gblRozdiO49g1zqDOEzmKJ7gcGVQTzmLDSgZ1Eunh2dD9aewpsXtctH3cwzn5ALysoVWlXZvhleMV5ovfU8J363mciAliRLsRg0Q244vfT3A/zgT2cQHbImIY1S4ldPk9Ii+NH8V9H08DfbfiY6BtiHYpoYmMBr4bbwN9YzfVoEELnew9+blpwBv5u0oI9wPtQrUbkWFaXqOzsZ01IFg7pLQOyp/A58igmhCsHZJInYndNsHa/0wiVWTy9/a5NjprQGvag0Qmwd462iGltdQ8lihqlVVIIn/9/CAvrajDWgTRB8VhMt1/aSljVr4RToc6hJTWioe1jujJD4rfQx2qRvuTyX1JuTIh3UQikUgkEhPnF+1xZ9hHnLjAAAAAAElFTkSuQmCC');
103 102 }
104 103
105 #qsp-image-container {
104 .center-on-screen {
106 105 position: absolute;
107 106 top: 0;
108 107 left: 0;
109 108 height: 100%;
110 109 width: 100%;
111 display: none;
110 pointer-events: none;
111 display: flex;
112 112 justify-content: center;
113 113 align-items: center;
114 114 }
115 115
116 .center-on-screen > * {
117 pointer-events: auto;
118 }
119
120 #qsp-image-container {
121 display: none;
122 }
123
116 124 /* misc */
117 125
118 126 .disable a {
119 127 pointer-events: none;
120 128 cursor: default;
121 129 }
@@ -1,15 +1,34 b''
1 1
2 2 (in-package sugar-qsp.api)
3 3
4 4 (defpsmacro with-call-args (args &body body)
5 5 `(progn
6 6 (init-args ,args)
7 7 ,@body
8 8 (get-result)))
9 9
10 10 (defpsmacro with-frame (&body body)
11 11 `(progn
12 12 (push-local-frame)
13 13 (unwind-protect
14 14 ,@body
15 15 (pop-local-frame))))
16
17 (defpsmacro inline-call (func &rest args)
18 `(+ (ps-inline ,func)
19 "(\""
20 ,(first args)
21 ,@(loop :for arg :in (cdr args)
22 :collect "\", \""
23 :collect arg)
24 "\");"))
25
26 (defpsmacro with-sleep ((resume-func) &body body)
27 `(new (*promise
28 (lambda (resolve)
29 (start-sleeping)
30 (let ((,resume-func (lambda ()
31 (finish-sleeping)
32 (resolve)))))
33 ,@body))))
34
@@ -1,423 +1,456 b''
1 1
2 2 (in-package sugar-qsp.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 (+ "<a class='qsp-act' href='" (ps-inline call-act) "(\"" title "\");'>"
12 (+ "<a class='qsp-act' href='" (inline-call call-act title) "'>"
13 (if img (+ "<img src='" img "'>") "")
13 14 title
14 15 "</a>"))
15 16
16 17 (defun make-menu-item-html (num title img loc)
17 (+ "<a href='" (ps-inline run-menu) "(" num ", \"" loc "\")();'>"
18 "<img src='" img "'>"
18 (+ "<a href='" (inline-call finish-menu loc) "'>"
19 (if img (+ "<img src='" img "'>") "")
19 20 title
20 21 "</a>"))
21 22
23 (defun make-menu-delimiter ()
24 "<hr>")
25
22 26 (defun report-error (text)
23 27 (alert text))
24 28
25 29 (defun start-sleeping ()
26 (chain (by-id "qsp") class-list (add "disable"))
27 (setf (root sleeping) t))
30 (chain (by-id "qsp") class-list (add "disable")))
28 31
29 32 (defun finish-sleeping ()
30 (chain (by-id "qsp") class-list (remove "disable"))
31 (setf (root sleeping) nil))
33 (chain (by-id "qsp") class-list (remove "disable")))
32 34
33 35 (defun sleep (msec)
34 (start-sleeping)
35 (new (*promise
36 (lambda (resolve)
37 (set-timeout
38 (lambda ()
39 (finish-sleeping)
40 (resolve))
41 msec)))))
36 (with-sleep (resume)
37 (set-timeout resume msec)))
42 38
43 39 (defun init-dom ()
44 40 ;; Save/load buttons
45 41 (let ((btn (by-id "qsp-btn-save")))
46 42 (setf (@ btn onclick) savegame)
47 43 (setf (@ btn href) "#"))
48 44 (let ((btn (by-id "qsp-btn-open")))
49 45 (setf (@ btn onclick) opengame)
50 46 (setf (@ btn href) "#"))
51 47 ;; Close image on click
52 48 (setf (@ (by-id "qsp-image-container") onclick)
53 49 (show-image nil))
54 50 ;; Close the dropdown on any click
55 51 (setf (@ window onclick)
56 52 (lambda (event)
57 (setf (@ (get-frame :dropdown) style display) "none"))))
53 (setf (@ window mouse)
54 (list (@ event page-x)
55 (@ event page-y)))
56 (finish-menu nil))))
58 57
59 58 (defun call-serv-loc (var-name &rest args)
60 59 (let ((loc-name (get-var var-name 0 :str)))
61 60 (when loc-name
62 61 (let ((loc (getprop (root locs) loc-name)))
63 62 (when loc
64 63 (funcall loc args))))))
65 64
66 65 ;;; Misc
67 66
68 67 (defun newline (key)
69 68 (append-id (key-to-id key) "<br>" t))
70 69
71 70 (defun clear-id (id)
72 71 (setf (inner-html (by-id id)) ""))
73 72
74 73 (defvar text-escaper (chain document (create-element :textarea)))
75 74
76 75 (defun prepare-contents (s &optional force-html)
77 76 (if (or force-html (get-var "USEHTML" 0 :num))
78 77 s
79 78 (progn
80 79 (setf (@ text-escaper text-content) s)
81 80 (inner-html text-escaper))))
82 81
83 82 (defun get-id (id &optional force-html)
84 83 (inner-html (by-id id)))
85 84
86 85 (defun set-id (id contents &optional force-html)
87 86 (setf (inner-html (by-id id)) (prepare-contents contents force-html)))
88 87
89 88 (defun append-id (id contents &optional force-html)
90 89 (when contents
91 90 (incf (inner-html (by-id id)) (prepare-contents contents force-html))))
92 91
93 92 ;;; Function calls
94 93
95 94 (defun init-args (args)
96 95 (dotimes (i (length args))
97 96 (let ((arg (elt args i)))
98 97 (if (numberp arg)
99 98 (set-var args i :num arg)
100 99 (set-var args i :str arg)))))
101 100
102 101 (defun get-result ()
103 102 (if (not (equal "" (get-var "RESULT" 0 :str)))
104 103 (get-var "RESULT" 0 :str)
105 104 (get-var "RESULT" 0 :num)))
106 105
107 106 (defun call-loc (name args)
107 (setf name (chain name (to-upper-case)))
108 108 (with-frame
109 109 (with-call-args args
110 110 (funcall (getprop (root locs) name) args))))
111 111
112 112 (defun call-act (title)
113 (unless (root sleeping)
114 (with-frame
115 (funcall (getprop (root acts) title 'act)))))
113 (with-frame
114 (funcall (getprop (root acts) title 'act))))
116 115
117 116 ;;; Text windows
118 117
119 118 (defun key-to-id (key)
120 119 (case key
121 120 (:main "qsp-main")
122 121 (:stat "qsp-stat")
123 122 (:objs "qsp-objs")
124 123 (:acts "qsp-acts")
125 124 (:input "qsp-input")
125 (:image "qsp-image")
126 126 (:dropdown "qsp-dropdown")
127 127 (t (report-error "Internal error!"))))
128 128
129 129 (defun get-frame (key)
130 130 (by-id (key-to-id key)))
131 131
132 132 (defun add-text (key text)
133 133 (append-id (key-to-id key) text))
134 134
135 135 (defun get-text (key)
136 136 (get-id (key-to-id key)))
137 137
138 138 (defun clear-text (key)
139 139 (clear-id (key-to-id key)))
140 140
141 141 (defun enable-frame (key enable)
142 142 (let ((obj (get-frame key)))
143 143 (setf (@ obj style display) (if enable "block" "none"))
144 144 (void)))
145 145
146 146 ;;; Actions
147 147
148 148 (defun add-act (title img act)
149 149 (setf (getprop (root acts) title)
150 150 (create img img act act))
151 151 (update-acts))
152 152
153 153 (defun del-act (title)
154 154 (delete (getprop (root acts) title))
155 155 (update-acts))
156 156
157 157 (defun clear-act ()
158 158 (setf (root acts) (create))
159 159 (clear-id "qsp-acts"))
160 160
161 161 (defun update-acts ()
162 162 (clear-id "qsp-acts")
163 163 (let ((elt (by-id "qsp-acts")))
164 164 (for-in (title (root acts))
165 165 (let ((obj (getprop (root acts) title)))
166 166 (incf (inner-html elt) (make-act-html title (getprop obj :img)))))))
167 167
168 168
169 169 ;;; "Syntax"
170 170
171 171 (defun qspfor (name index from to step body)
172 172 (for ((i from))
173 173 ((< i to))
174 174 ((incf i step))
175 175 (set-var name index :num i)
176 176 (unless (funcall body)
177 177 (return-from qspfor))))
178 178
179 179 ;;; Variable class
180 180
181 181 (defun *var (name)
182 182 ;; From strings to numbers
183 183 (setf (@ this indexes) (create))
184 184 ;; From numbers to {num: 0, str: ""} objects
185 185 (setf (@ this values) (list))
186 186 (void))
187 187
188 188 (defun new-value ()
189 189 (create :num 0 :str ""))
190 190
191 191 (setf (@ *var prototype index-num)
192 192 (lambda (index)
193 193 (let ((num-index
194 194 (if (stringp index)
195 195 (if (in index (@ this indexes))
196 196 (getprop (@ this indexes) index)
197 197 (let ((n (length (@ this values))))
198 198 (setf (getprop (@ this indexes) index) n)
199 199 n))
200 200 index)))
201 201 (unless (in num-index (@ this values))
202 202 (setf (elt (@ this values) num-index) (new-value)))
203 203 num-index)))
204 204
205 205 (setf (@ *var prototype get)
206 206 (lambda (index slot)
207 207 (unless (or index (= 0 index))
208 208 (setf index (1- (length (@ this values)))))
209 209 (getprop (@ this values) (chain this (index-num index)) slot)))
210 210
211 211 (setf (@ *var prototype set)
212 212 (lambda (index slot value)
213 213 (unless (or index (= 0 index))
214 214 (setf index (length (@ this values))))
215 215 (case slot
216 216 (:num (setf value (chain *number (parse-int value))))
217 217 (:str (setf value (chain value (to-string)))))
218 218 (setf (getprop (@ this values)
219 219 (chain this (index-num index))
220 220 slot) value)
221 221 (void)))
222 222
223 223 (setf (@ *var prototype kill)
224 224 (lambda (index)
225 225 (setf (elt (@ this values) (chain this (index-num index)))
226 226 (new-value))
227 227 (delete (getprop 'this 'indexes index))))
228 228
229 229 ;;; Variables
230 230
231 231 (defun var-real-name (name)
232 232 (if (= (@ name 0) #\$)
233 233 (values (chain name (substr 1)) :str)
234 234 (values name :num)))
235 235
236 236 (defun ensure-var (name)
237 (setf name (chain name (to-upper-case)))
237 238 (let ((store (var-ref name)))
238 239 (unless store
239 240 (setf store (new (*var name)))
240 241 (setf (getprop (root vars) name) store))
241 242 store))
242 243
243 244 (defun var-ref (name)
244 245 (let ((local-store (current-local-frame)))
245 246 (cond ((and local-store (in name local-store))
246 247 (getprop local-store name))
247 248 ((in name (root vars))
248 249 (getprop (root vars) name))
249 250 (t nil))))
250 251
251 252 (defun get-var (name index slot)
252 253 (chain (ensure-var name) (get index slot)))
253 254
254 255 (defun set-var (name index slot value)
255 256 (chain (ensure-var name) (set index slot value))
256 257 (void))
257 258
258 259 (defun get-array (name)
260 (setf name (chain name (to-upper-case)))
259 261 (var-ref name))
260 262
261 263 (defun set-array (name value)
264 (setf name (chain name (to-upper-case)))
262 265 (let ((store (var-ref name)))
263 266 (setf (@ store values) (@ value values))
264 267 (setf (@ store indexes) (@ value indexes)))
265 268 (void))
266 269
267 270 (defun kill-var (name &optional index)
271 (setf name (chain name (to-upper-case)))
268 272 (if (and index (not (= 0 index)))
269 273 (chain (getprop (root vars) name) (kill index))
270 274 (delete (getprop (root vars) name)))
271 275 (void))
272 276
273 277 (defun array-size (name)
274 (getprop (var-ref name) 'length))
278 (@ (var-ref name) values length))
275 279
276 280 ;;; Locals
277 281
278 282 (defun push-local-frame ()
279 283 (chain (root locals) (push (create)))
280 284 (void))
281 285
282 286 (defun pop-local-frame ()
283 287 (chain (root locals) (pop))
284 288 (void))
285 289
286 290 (defun current-local-frame ()
287 291 (elt (root locals) (1- (length (root locals)))))
288 292
289 293 (defun new-local (name)
290 294 (let ((frame (current-local-frame)))
291 295 (unless (in name frame)
292 296 (setf (getprop frame name) (create)))
293 297 (void)))
294 298
295 299 ;;; Objects
296 300
297 301 (defun update-objs ()
298 302 (let ((elt (by-id "qsp-objs")))
299 303 (setf (inner-html elt) "<ul>")
300 304 (loop :for obj :in (root objs)
301 305 :do (incf (inner-html elt) (+ "<li>" obj)))
302 306 (incf (inner-html elt) "</ul>")))
303 307
304 308 ;;; Menu
305 309
306 (defun menu (menu-data)
307 (let ((elt (by-id "qsp-dropdown"))
310 (defun open-menu (menu-data)
311 (let ((elt (get-frame :dropdown))
308 312 (i 0))
309 (setf (inner-html elt) "")
310 313 (loop :for item :in menu-data
311 314 :do (incf i)
312 :do (incf (inner-html elt) (make-menu-item-html i
313 (@ item text)
314 (@ item icon)
315 (@ item loc))))
315 :do (incf (inner-html elt)
316 (if (eq item :delimiter)
317 (make-menu-delimiter i)
318 (make-menu-item-html i
319 (@ item :text)
320 (@ item :icon)
321 (@ item :loc)))))
322 (let ((mouse (@ window mouse)))
323 (setf (@ elt style left) (+ (elt mouse 0) "px"))
324 (setf (@ elt style top) (+ (elt mouse 1) "px"))
325 ;; Make sure it's inside the viewport
326 (when (> (@ document body inner-width)
327 (+ (elt mouse 0) (@ elt inner-width)))
328 (incf (@ elt style left) (@ elt inner-width)))
329 (when (> (@ document body inner-height)
330 (+ (elt mouse 0) (@ elt inner-height)))
331 (incf (@ elt style top) (@ elt inner-height))))
316 332 (setf (@ elt style display) "block")))
317 333
334 (defun finish-menu (loc)
335 (when (root menu-resume)
336 (let ((elt (get-frame :dropdown)))
337 (setf (inner-html elt) "")
338 (setf (@ elt style display) "none")
339 (funcall (root menu-resume))
340 (setf (root menu-resume) nil))
341 (when loc
342 (call-loc loc)))
343 (void))
344
345 (defun menu (menu-data)
346 (with-sleep (resume)
347 (open-menu menu-data)
348 (setf (root menu-resume) resume))
349 (void))
350
318 351 ;;; Content
319 352
320 353 (defun clean-audio ()
321 354 (loop :for k :in (chain *object (keys (root playing)))
322 355 :for v := (getprop (root playing) k)
323 356 :do (when (@ v ended)
324 357 (delete (@ (root playing) k)))))
325 358
326 359 (defun show-image (path)
327 (let ((img (by-id "qsp-image")))
360 (let ((img (get-frame :image)))
328 361 (cond (path
329 362 (setf (@ img src) path)
330 363 (setf (@ img style display) "flex"))
331 364 (t
332 365 (setf (@ img src) "")
333 366 (setf (@ img style display) "hidden")))))
334 367
335 368 ;;; Saves
336 369
337 370 (defun opengame ()
338 371 (let ((element (chain document (create-element :input))))
339 372 (chain element (set-attribute :type :file))
340 373 (chain element (set-attribute :id :qsp-opengame))
341 374 (chain element (set-attribute :tabindex -1))
342 375 (chain element (set-attribute "aria-hidden" t))
343 376 (setf (@ element style display) :block)
344 377 (setf (@ element style visibility) :hidden)
345 378 (setf (@ element style position) :fixed)
346 379 (setf (@ element onchange)
347 380 (lambda (event)
348 381 (let* ((file (@ event target files 0))
349 382 (reader (new (*file-reader))))
350 383 (setf (@ reader onload)
351 384 (lambda (ev)
352 385 (block nil
353 386 (let ((target (@ ev current-target)))
354 387 (unless (@ target result)
355 388 (return))
356 389 (base64-to-state (@ target result))
357 390 (unstash-state)))))
358 391 (chain reader (read-as-text file)))))
359 392 (chain document body (append-child element))
360 393 (chain element (click))
361 394 (chain document body (remove-child element))))
362 395
363 396 (defun savegame ()
364 397 (let ((element (chain document (create-element :a))))
365 398 (chain element (set-attribute :href (+ "data:text/plain;charset=utf-8," (state-to-base64))))
366 399 (chain element (set-attribute :download "savegame.sav"))
367 400 (setf (@ element style display) :none)
368 401 (chain document body (append-child element))
369 402 (chain element (click))
370 403 (chain document body (remove-child element))))
371 404
372 405 (defun stash-state (args)
373 406 (call-serv-loc "ONGSAVE")
374 407 (setf (root state-stash)
375 408 (chain *j-s-o-n (stringify
376 409 (create :vars (root vars)
377 410 :objs (root objs)
378 411 :loc-args args
379 412 :msecs (- (chain *date (now)) (root started-at))
380 413 :timer-interval (root timer-interval)
381 414 :main-html (inner-html
382 (by-id :qsp-main))
415 (get-frame :main))
383 416 :stat-html (inner-html
384 (by-id :qsp-stat))
417 (get-frame :stat))
385 418 :next-location (root current-location)))))
386 419 (void))
387 420
388 421 (defun unstash-state ()
389 422 (let ((data (chain *j-s-o-n (parse (root state-stash)))))
390 423 (clear-act)
391 424 (setf (root vars) (@ data :vars))
392 425 (loop :for k :in (chain *object (keys (root vars)))
393 426 :do (chain *object (set-prototype-of (getprop (root vars) k)
394 427 (@ *var prototype))))
395 428 (setf (root started-at) (- (chain *date (now)) (@ data :msecs)))
396 429 (setf (root objs) (@ data :objs))
397 430 (setf (root current-location) (@ data :next-location))
398 (setf (inner-html (by-id :qsp-main))
431 (setf (inner-html (get-frame :main))
399 432 (@ data :main-html))
400 (setf (inner-html (by-id :qsp-stat))
433 (setf (inner-html (get-frame :stat))
401 434 (@ data :stat-html))
402 435 (update-objs)
403 436 (set-timer (@ data :timer-interval))
404 437 (call-serv-loc "ONGLOAD")
405 438 (call-loc (root current-location) (@ data :loc-args))
406 439 (void)))
407 440
408 441 (defun state-to-base64 ()
409 442 (btoa (encode-u-r-i-component (root state-stash))))
410 443
411 444 (defun base64-to-state (data)
412 445 (setf (root state-stash) (decode-u-r-i-component (atob data))))
413 446
414 447 ;;; Timers
415 448
416 449 (defun set-timer (interval)
417 450 (setf (root timer-interval) interval)
418 451 (clear-interval (root timer-obj))
419 452 (setf (root timer-obj)
420 453 (set-interval
421 454 (lambda ()
422 455 (call-serv-loc "COUNTER"))
423 456 interval)))
@@ -1,174 +1,174 b''
1 1
2 2 (in-package sugar-qsp.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 11 (defpsmacro killvar (varname &optional index)
12 `(kill-var ,varname ,index))
12 `(api-call kill-var ,varname ,index))
13 13
14 14 (defpsmacro killall ()
15 15 `(api-call kill-all))
16 16
17 17 ;;; 3expr
18 18
19 19 (defpsmacro obj (name)
20 20 `(funcall (root objs includes) ,name))
21 21
22 22 (defpsmacro loc (name)
23 23 `(funcall (root locs includes) ,name))
24 24
25 25 (defpsmacro no (arg)
26 26 `(- -1 ,arg))
27 27
28 28 ;;; 4code
29 29
30 30 (defpsmacro qspver ()
31 31 "0.0.1")
32 32
33 33 (defpsmacro curloc ()
34 34 `(root current-location))
35 35
36 36 (defpsmacro rnd ()
37 37 `(funcall rand 1 1000))
38 38
39 39 (defpsmacro qspmax (&rest args)
40 40 (if (= 1 (length args))
41 41 `(*math.max.apply nil ,@args)
42 42 `(*math.max ,@args)))
43 43
44 44 (defpsmacro qspmin (&rest args)
45 45 (if (= 1 (length args))
46 46 `(*math.min.apply nil ,@args)
47 47 `(*math.min ,@args)))
48 48
49 49 ;;; 5arrays
50 50
51 51 (defpsmacro arrsize (name)
52 52 `(api-call array-size ,name))
53 53
54 54 ;;; 6str
55 55
56 56 (defpsmacro len (s)
57 57 `(length ,s))
58 58
59 59 (defpsmacro mid (s from &optional count)
60 60 `(chain ,s (substring ,from ,count)))
61 61
62 62 (defpsmacro ucase (s)
63 63 `(chain ,s (to-upper-case)))
64 64
65 65 (defpsmacro lcase (s)
66 66 `(chain ,s (to-lower-case)))
67 67
68 68 (defpsmacro trim (s)
69 69 `(chain ,s (trim)))
70 70
71 71 (defpsmacro replace (s from to)
72 72 `(chain ,s (replace ,from ,to)))
73 73
74 74 (defpsmacro val (s)
75 75 `(parse-int ,s 10))
76 76
77 77 (defpsmacro qspstr (n)
78 78 `(chain ,n (to-string)))
79 79
80 80 ;;; 7if
81 81
82 82 ;;; 8sub
83 83
84 84 ;;; 9loops
85 85
86 86 ;; JUMP is in ps-macros.lisp (because it's a part of a huge kludge)
87 87
88 88 (defpsmacro exit ()
89 89 `(return-from nil (values)))
90 90
91 91 ;;; 10dynamic
92 92
93 93 ;;; 11main
94 94
95 95 (defpsmacro desc (s)
96 96 (declare (ignore s))
97 97 "")
98 98
99 99 ;;; 12stat
100 100
101 101 (defpsmacro showstat (enable)
102 102 `(api-call enable-frame :stat ,enable))
103 103
104 104 ;;; 13diag
105 105
106 106 (defpsmacro msg (text)
107 107 `(alert ,text))
108 108
109 109 ;;; 14act
110 110
111 111 (defpsmacro showacts (enable)
112 112 `(api-call enable-frame :acts ,enable))
113 113
114 114 (defpsmacro delact (name)
115 115 `(api-call del-act ,name))
116 116
117 117 (defpsmacro cla ()
118 118 `(api-call clear-act))
119 119
120 120 ;;; 15objs
121 121
122 122 (defpsmacro showobjs (enable)
123 123 `(api-call enable-frame :objs ,enable))
124 124
125 125 (defpsmacro countobj ()
126 126 `(length (root objs)))
127 127
128 128 (defpsmacro getobj (index)
129 129 `(or (elt (root objs) ,index) ""))
130 130
131 131 ;;; 16menu
132 132
133 133 ;;; 17sound
134 134
135 135 (defpsmacro isplay (filename)
136 136 `(funcall (root playing includes) ,filename))
137 137
138 138 ;;; 18img
139 139
140 140 (defpsmacro view (&optional path)
141 141 `(api-call show-image ,path))
142 142
143 143 ;;; 19input
144 144
145 145 (defpsmacro showinput (enable)
146 146 `(api-call enable-frame :input ,enable))
147 147
148 148 ;;; 20time
149 149
150 150 (defpsmacro wait (msec)
151 151 `(await (api-call sleep ,msec)))
152 152
153 153 (defpsmacro settimer (interval)
154 154 `(api-call set-timer ,interval))
155 155
156 156 ;;; 21local
157 157
158 158 (defpsmacro local (var &optional expr)
159 159 `(progn
160 160 (api-call new-local ,(string (second var)))
161 161 ,@(when expr
162 162 `((set ,var ,expr)))))
163 163
164 164 ;;; 22for
165 165
166 166 ;;; misc
167 167
168 168 (defpsmacro opengame (&optional filename)
169 169 (declare (ignore filename))
170 170 `(api-call opengame))
171 171
172 172 (defpsmacro savegame (&optional filename)
173 173 (declare (ignore filename))
174 174 `(api-call savegame))
@@ -1,302 +1,302 b''
1 1
2 2 (in-package sugar-qsp.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 (funcall xgoto target (or args (list)))
12 (funcall xgoto target args)
13 13 (void))
14 14
15 15 (defun xgoto (target args)
16 16 (api:clear-act)
17 17 (setf (root current-location) (chain target (to-upper-case)))
18 18 (api:stash-state args)
19 (funcall (getprop (root locs) (root current-location))
20 (or args (list)))
19 (api:call-loc (root current-location) (or args (list)))
21 20 (void))
22 21
23 22 ;;; 2var
24 23
25 24 ;;; 3expr
26 25
27 26 ;;; 4code
28 27
29 28 (defun rand (a &optional (b 1))
30 29 (let ((min (min a b))
31 30 (max (max a b)))
32 31 (+ min (chain *math (random (- max min))))))
33 32
34 33 ;;; 5arrays
35 34
36 35 (defun copyarr (to from start count)
37 36 (multiple-value-bind (to-name to-slot)
38 37 (api:var-real-name to)
39 38 (multiple-value-bind (from-name from-slot)
40 39 (api:var-real-name from)
41 40 (for ((i start))
42 41 ((< i (min (api:array-size from-name)
43 42 (+ start count))))
44 43 ((incf i))
45 44 (api:set-var to-name (+ start i) to-slot
46 45 (api:get-var from-name (+ start i) from-slot))))))
47 46
48 47 (defun arrpos (name value &optional (start 0))
49 48 (multiple-value-bind (real-name slot)
50 49 (api:var-real-name name)
51 50 (for ((i start)) ((< i (api:array-size name))) ((incf i))
52 51 (when (eq (api:get-var real-name i slot) value)
53 52 (return-from arrpos i))))
54 53 -1)
55 54
56 55 (defun arrcomp (name pattern &optional (start 0))
57 56 (multiple-value-bind (real-name slot)
58 57 (api:var-real-name name)
59 58 (for ((i start)) ((< i (api:array-size name))) ((incf i))
60 59 (when (funcall (getprop (api:get-var real-name i slot) 'match) pattern)
61 60 (return-from arrcomp i))))
62 61 -1)
63 62
64 63 ;;; 6str
65 64
66 65 (defun instr (s subs &optional (start 1))
67 66 (+ start (chain s (substring (- start 1)) (search subs))))
68 67
69 68 (defun isnum (s)
70 69 (if (is-na-n s)
71 70 0
72 71 -1))
73 72
74 73 (defun strcomp (s pattern)
75 74 (if (chain s (match pattern))
76 75 -1
77 76 0))
78 77
79 78 (defun strfind (s pattern group)
80 79 (let* ((re (new (*reg-exp pattern)))
81 80 (match (chain re (exec s))))
82 81 (chain match (group group))))
83 82
84 83 (defun strpos (s pattern &optional (group 0))
85 84 (let* ((re (new (*reg-exp pattern)))
86 85 (match (chain re (exec s)))
87 86 (found (chain match (group group))))
88 87 (if found
89 88 (chain s (search found))
90 89 0)))
91 90
92 91 ;;; 7if
93 92
94 93 ;; Has to be a function because it always evaluates all three of its
95 94 ;; arguments
96 95 (defun iif (cond-expr then-expr else-expr)
97 96 (if cond-expr then-expr else-expr))
98 97
99 98 ;;; 8sub
100 99
101 100 (defun gosub (target &rest args)
102 (funcall (getprop (root locs) target) args)
101 (api:call-loc target args)
103 102 (void))
104 103
105 104 (defun func (target &rest args)
106 (funcall (getprop (root locs) target) args))
105 (api:call-loc target args))
107 106
108 107 ;;; 9loops
109 108
110 109 ;;; 10dynamic
111 110
112 111 (defun dynamic (block &rest args)
113 112 (when (stringp block)
114 113 (api:report-error "DYNAMIC can't evaluate arbitrary strings.\\nUse {braces} to create blocks for DYNAMIC."))
115 114 (api:with-call-args args
116 115 (funcall block args))
117 116 (void))
118 117
119 118 (defun dyneval (block &rest args)
120 119 (when (stringp block)
121 120 (api:report-error "DYNEVAL can't evaluate arbitrary strings.\\nUse {braces} to create blocks for DYNEVAL."))
122 121 (api:with-call-args args
123 122 (funcall block args)))
124 123
125 124 ;;; 11main
126 125
127 126 (defun main-p (s)
128 127 (api:add-text :main s)
129 128 (void))
130 129
131 130 (defun main-pl (s)
132 131 (api:add-text :main s)
133 132 (api:newline :main)
134 133 (void))
135 134
136 135 (defun main-nl (s)
137 136 (api:newline :main)
138 137 (api:add-text :main s)
139 138 (void))
140 139
141 140 (defun maintxt (s)
142 141 (api:get-text :main)
143 142 (void))
144 143
145 144 ;; For clarity (it leaves a lib.desc() call in JS)
146 145 (defun desc (s)
147 146 "")
148 147
149 148 (defun main-clear ()
150 149 (api:clear-text :main)
151 150 (void))
152 151
153 152 ;;; 12stat
154 153
155 154 (defun stat-p (s)
156 155 (api:add-text :stat s)
157 156 (void))
158 157
159 158 (defun stat-pl (s)
160 159 (api:add-text :stat s)
161 160 (api:newline :stat)
162 161 (void))
163 162
164 163 (defun stat-nl (s)
165 164 (api:newline :stat)
166 165 (api:add-text :stat s)
167 166 (void))
168 167
169 168 (defun stattxt (s)
170 169 (api:get-text :stat)
171 170 (void))
172 171
173 172 (defun stat-clear ()
174 173 (api:clear-text :stat)
175 174 (void))
176 175
177 176 (defun cls ()
178 177 (stat-clear)
179 178 (main-clear)
180 179 (cla)
181 180 (cmdclear)
182 181 (void))
183 182
184 183 ;;; 13diag
185 184
186 185 ;;; 14act
187 186
188 187 (defun curacts ()
189 188 (let ((acts (root acts)))
190 189 (lambda ()
191 190 (setf (root acts) acts)
192 191 (void))))
193 192
194 193 ;;; 15objs
195 194
196 195 (defun addobj (name)
197 196 (chain (root objs) (push name))
198 197 (api:update-objs)
199 198 (void))
200 199
201 200 (defun delobj (name)
202 201 (let ((index (chain (root objs) (index-of name))))
203 202 (when (> index -1)
204 203 (killobj (1+ index))))
205 204 (void))
206 205
207 206 (defun killobj (&optional (num nil))
208 207 (if (eq nil num)
209 208 (setf (root objs) (list))
210 209 (chain (root objs) (splice (1- num) 1)))
211 210 (api:update-objs)
212 211 (void))
213 212
214 213 ;;; 16menu
215 214
216 215 (defun menu (menu-name)
217 216 (let ((menu-data (list)))
218 (loop :for item :in (api:get-array (api:var-real-name menu-name))
217 (loop :for item-obj :in (@ (api:get-array (api:var-real-name menu-name)) values)
218 :for item := (@ item-obj :str)
219 219 :do (cond ((string= item "")
220 220 (break))
221 221 ((string= item "-:-")
222 222 (chain menu-data (push :delimiter)))
223 223 (t
224 224 (let* ((tokens (chain item (split ":"))))
225 225 (when (= (length tokens) 2)
226 226 (chain tokens (push "")))
227 227 (let* ((text (chain tokens (splice 0 (- (length tokens) 2)) (join ":")))
228 228 (loc (getprop tokens (- (length tokens) 2)))
229 229 (icon (getprop tokens (- (length tokens) 1))))
230 230 (chain menu-data
231 (push (create text text
232 loc loc
233 icon icon))))))))
231 (push (create :text text
232 :loc loc
233 :icon icon))))))))
234 234 (api:menu menu-data)
235 235 (void)))
236 236
237 237 ;;; 17sound
238 238
239 239 (defun play (filename &optional (volume 100))
240 240 (let ((audio (new (*audio filename))))
241 241 (setf (getprop (root playing) filename) audio)
242 242 (setf (@ audio volume) (* volume 0.01))
243 243 (chain audio (play))))
244 244
245 245 (defun close (filename)
246 246 (funcall (root playing filename) stop)
247 247 (delete (root playing filename))
248 248 (void))
249 249
250 250 (defun closeall ()
251 251 (loop :for k :in (chain *object (keys (root playing)))
252 252 :for v := (getprop (root playing) k)
253 253 :do (funcall v stop))
254 254 (setf (root playing) (create)))
255 255
256 256 ;;; 18img
257 257
258 258 (defun refint ()
259 259 ;; "Force interface update" Uh... what exactly do we do here?
260 260 (api:report-error "REFINT is not supported")
261 261 )
262 262
263 263 ;;; 19input
264 264
265 265 (defun usertxt ()
266 266 (let ((input (by-id "qsp-input")))
267 267 (@ input value)))
268 268
269 269 (defun cmdclear ()
270 270 (let ((input (by-id "qsp-input")))
271 271 (setf (@ input value) "")))
272 272
273 273 (defun input (text)
274 274 (chain window (prompt text)))
275 275
276 276 ;;; 20time
277 277
278 278 (defun msecscount ()
279 279 (- (chain *date (now)) (root started-at)))
280 280
281 281 ;;; 21local
282 282
283 283 ;;; 22for
284 284
285 285 ;;; misc
286 286
287 287 (defun rgb (red green blue)
288 288 (flet ((rgb-to-hex (comp)
289 289 (let ((hex (chain (*number comp) (to-string 16))))
290 290 (if (< (length hex) 2)
291 291 (+ "0" hex)
292 292 hex))))
293 293 (+ "#" (rgb-to-hex red) (rgb-to-hex green) (rgb-to-hex red))))
294 294
295 295 (defun openqst ()
296 296 (api:report-error "OPENQST is not supported."))
297 297
298 298 (defun addqst ()
299 299 (api:report-error "ADDQST is not supported. Bundle the library with the main game."))
300 300
301 301 (defun killqst ()
302 302 (api:report-error "KILLQST is not supported."))
@@ -1,39 +1,41 b''
1 1
2 2 (in-package sugar-qsp.js)
3 3
4 4 ;;; Contains symbols from standard JS library to avoid obfuscating
5 5 ;;; and/or namespacing them
6 6
7 7 (cl:defmacro syms (cl:&rest syms)
8 8 `(cl:progn
9 9 ,@(cl:loop :for sym :in syms
10 10 :collect `(cl:export ',sym))))
11 11
12 12 (syms
13 13 ;; main
14 14 window
15 15 *object
16 16 now
17 17 onload
18 18 keys includes
19 19 has-own-property
20 20 ;; api
21 21 document get-element-by-id
22 22 onclick onchange
23 atob btoa
23 atob btoa split
24 24 alert prompt
25 25 set-timeout set-interval clear-interval
26 26 *promise *j-s-o-n
27 27 href parse
28 28 set-prototype-of
29 29 body append-child remove-child
30 30 add ; remove (is already in COMMON-LISP)
31 31 create-element set-attribute class-list
32 32 *file-reader read-as-text
33 33 style display src
34 page-x page-y
35 top left
34 36 ;; lib
35 37 *number parse-int
36 to-upper-case concat
38 to-string to-upper-case concat
37 39 click target current-target files index-of result
38 40 decode-u-r-i-component splice
39 41 )
@@ -1,43 +1,44 b''
1 1
2 2 (in-package sugar-qsp.main)
3 3
4 4 (setf (root)
5 5 (create
6 6 ;;; Game session state
7 7 ;; Variables
8 8 vars (create)
9 9 ;; Inventory (objects)
10 10 objs (list)
11 11 current-location nil
12 12 ;; Game time
13 13 started-at (chain *date (now))
14 14 ;; Timers
15 15 timer-interval 500
16 16 timer-obj nil
17 17 ;;; Transient state
18 18 ;; Savegame data
19 19 state-stash (create)
20 20 ;; List of audio files being played
21 21 playing (create)
22 22 ;; Local variables stack (starts with an empty frame)
23 23 locals (list)
24 24 ;;; Game data
25 25 ;; ACTions
26 26 acts (create)
27 27 ;; Locations
28 28 locs (create)))
29 29
30 30 ;; Launch the game from the first location
31 31 (setf (@ window onload)
32 32 (lambda ()
33 33 (#.(intern "INIT-DOM" "SUGAR-QSP.API"))
34 34 ;; For MSECCOUNT
35 35 (setf (root started-at) (chain *date (now)))
36 36 ;; For $COUNTER and SETTIMER
37 37 (#.(intern "SET-TIMER" "SUGAR-QSP.API")
38 38 (root timer-interval))
39 ;; Start the first location
39 40 (funcall (getprop (root locs)
40 41 (chain *object (keys (root locs)) 0))
41 42 (list))
42 43 (values)))
43 44
@@ -1,614 +1,619 b''
1 1
2 2 (in-package sugar-qsp)
3 3
4 4 ;;;; Parses TXT source to an intermediate representation
5 5
6 6 ;;; Utility
7 7
8 8 (defun remove-nth (list nth)
9 9 (append (subseq list 0 nth)
10 10 (subseq list (1+ nth))))
11 11
12 12 (defun not-quote (char)
13 13 (not (eql #\' char)))
14 14
15 15
16 16 (defun not-doublequote (char)
17 17 (not (eql #\" char)))
18 18
19 19 (defun not-brace (char)
20 20 (not (eql #\} char)))
21 21
22 22 (defun not-integer (string)
23 23 (when (find-if-not #'digit-char-p string)
24 24 t))
25 25
26 26 (defun not-newline (char)
27 27 (not (eql #\newline char)))
28 28
29 29 (defun id-any-char (char)
30 30 (and
31 31 (not (digit-char-p char))
32 32 (not (eql #\newline char))
33 33 (not (find char " !:&=<>+-*/,'\"()[]{}"))))
34 34
35 35 (defun intern-first (list)
36 36 (list* (intern (string-upcase (first list)) :lib)
37 37 (rest list)))
38 38
39 39 (eval-when (:compile-toplevel :load-toplevel :execute)
40 40 (defun remove-nil (list)
41 41 (remove nil list)))
42 42
43 43 (defun binop-rest (list)
44 44 (destructuring-bind (ws1 operator ws2 operand2)
45 45 list
46 46 (declare (ignore ws1 ws2))
47 47 (list (intern (string-upcase operator) :lib) operand2)))
48 48
49 49 (defun do-binop% (left-op other-ops)
50 50 (if (null other-ops)
51 51 left-op
52 52 (destructuring-bind ((operator right-op) &rest rest-ops)
53 53 other-ops
54 54 (if (and (listp left-op)
55 55 (eq (first left-op)
56 56 operator))
57 57 (do-binop% (append left-op (list right-op)) rest-ops)
58 58 (do-binop% (list operator left-op right-op) rest-ops)))))
59 59
60 60 (defun do-binop (list)
61 61 (destructuring-bind (left-op rest-ops)
62 62 list
63 63 (do-binop% left-op
64 64 (mapcar #'binop-rest rest-ops))))
65 65
66 66 (p:defrule line-continuation (and #\_ #\newline)
67 67 (:constant nil))
68 68
69 69 (p:defrule text-spaces (+ (or #\space #\tab line-continuation))
70 70 (:text t))
71 71
72 72 (p:defrule spaces (+ (or #\space #\tab line-continuation))
73 73 (:constant nil)
74 74 (:error-report nil))
75 75
76 76 (p:defrule spaces? (* (or #\space #\tab line-continuation))
77 77 (:constant nil)
78 78 (:error-report nil))
79 79
80 80 (p:defrule colon #\:
81 81 (:constant nil))
82 82
83 83 (p:defrule equal #\=
84 84 (:constant nil))
85 85
86 86 (p:defrule alphanumeric (alphanumericp character))
87 87
88 88 (p:defrule not-newline (not-newline character))
89 89
90 90 (p:defrule squote-esc "''"
91 91 (:lambda (list)
92 92 (p:text (elt list 0))))
93 93
94 94 (p:defrule dquote-esc "\"\""
95 95 (:lambda (list)
96 96 (p:text (elt list 0))))
97 97
98 98 (p:defrule sstring-chars (+ (and (and (p:! "<<") (p:! (p:~ "\"exec:")))
99 99 (or squote-esc (not-quote character))))
100 100 (:lambda (list)
101 101 (p:text (mapcar #'second list))))
102 102
103 103 (p:defrule dstring-chars (+ (and (and (p:! "<<") (p:! (p:~ "'exec:")))
104 104 (or dquote-esc (not-doublequote character))))
105 105 (:lambda (list)
106 106 (p:text (mapcar #'second list))))
107 107
108 108 ;;; Identifiers
109 109
110 110 (defparameter *keywords* '(act addlib addobj addqst and arrcomp arrpos arrsize cla clear *clear close clr *clr cls cmdclear cmdclr copyarr counter countobj curacts curloc debug delact dellib delobj desc disablescroll disablesubex dynamic dyneval else elseif end exit for freelib func getobj gosub goto gs gt if iif inclib input instr isnum isplay jump killall killobj killqst killvar lcase lcolor 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 usercom user_text usrtxt val view wait xgoto xgt))
111 111
112 112 (defun trim-$ (str)
113 113 (if (char= #\$ (elt str 0))
114 114 (subseq str 1)
115 115 str))
116 116
117 117 (defun qsp-keyword-p (id)
118 118 (member (intern (trim-$ (string-upcase id))) *keywords*))
119 119
120 120 (defun not-qsp-keyword-p (id)
121 121 (not (member (intern (trim-$ (string-upcase id))) *keywords*)))
122 122
123 123 (p:defrule qsp-keyword (qsp-keyword-p identifier-raw))
124 124
125 125 (p:defrule id-first (id-any-char character))
126 126 (p:defrule id-next (or (id-any-char character)
127 127 (digit-char-p character)))
128 128 (p:defrule identifier-raw (and id-first (* id-next))
129 129 (:lambda (list)
130 130 (intern (string-upcase (p:text list)) :lib)))
131 131
132 132 (p:defrule identifier (not-qsp-keyword-p identifier-raw))
133 133
134 134 ;;; Strings
135 135
136 136 (p:defrule qsp-string (or normal-string brace-string))
137 137
138 138 (p:defrule normal-string (or sstring dstring)
139 139 (:lambda (str)
140 140 (list* 'lib:str (or str (list "")))))
141 141
142 142 (p:defrule sstring (and #\' (* (or string-interpol
143 143 sstring-exec
144 144 sstring-chars))
145 145 #\')
146 146 (:function second))
147 147
148 148 (p:defrule dstring (and #\" (* (or string-interpol
149 149 dstring-exec
150 150 dstring-chars))
151 151 #\")
152 152 (:function second))
153 153
154 154 (p:defrule string-interpol (and "<<" expression ">>")
155 155 (:function second))
156 156
157 157 (p:defrule sstring-exec-body (+ (or squote-esc (not-doublequote character)))
158 158 (:text t))
159 159
160 160 (p:defrule dstring-exec-body (+ (or dquote-esc (not-quote character)))
161 161 (:text t))
162 162
163 163 (p:defrule sstring-exec (and (p:~ "\"exec:") sstring-exec-body #\")
164 164 (:lambda (list)
165 165 (list* 'lib:exec (p:parse 'exec-body (second list)))))
166 166
167 167 (p:defrule dstring-exec (and (p:~ "'exec:") dstring-exec-body #\')
168 168 (:lambda (list)
169 169 (list* 'lib:exec (p:parse 'exec-body (second list)))))
170 170
171 171 (p:defrule brace-string (and #\{ before-statement block-body #\})
172 172 (:lambda (list)
173 173 (list* 'lib:qspblock (third list))))
174 174
175 175 ;;; Location
176 176
177 177 (p:defrule sugar-qsp-grammar (and (* (or spaces #\newline))
178 178 (* location))
179 179 (:function second))
180 180
181 181 (p:defrule location (and location-header block-body location-end)
182 182 (:destructure (header body end)
183 183 (declare (ignore end))
184 184 `(lib:location (,header) ,@body)))
185 185
186 186 (p:defrule location-header (and #\#
187 187 (+ not-newline)
188 188 (and #\newline spaces? before-statement))
189 189 (:destructure (spaces1 name spaces2)
190 190 (declare (ignore spaces1 spaces2))
191 191 (string-upcase (string-trim " " (p:text name)))))
192 192
193 193 (p:defrule location-end (and #\- (* not-newline) #\newline before-statement)
194 194 (:constant nil))
195 195
196 196 ;;; Block body
197 197
198 198 (p:defrule newline-block-body (and #\newline spaces? block-body)
199 199 (:function third))
200 200
201 201 (p:defrule block-body (* statement)
202 202 (:function remove-nil))
203 203
204 204 ;; Just for <a href="exec:...'>
205 205 ;; Explicitly called from that rule's production
206 206 (p:defrule exec-body (and before-statement line-body)
207 207 (:function second))
208 208
209 209 (p:defrule line-body (and inline-statement (* next-inline-statement))
210 210 (:lambda (list)
211 211 (list* (first list) (second list))))
212 212
213 213 (p:defrule before-statement (* (or #\newline spaces))
214 214 (:constant nil))
215 215
216 216 (p:defrule statement-end (or statement-end-real statement-end-block-close))
217 217
218 218 (p:defrule statement-end-real (and (or #\newline
219 219 (and #\& spaces? (p:& statement%)))
220 220 before-statement)
221 221 (:constant nil))
222 222
223 223 (p:defrule statement-end-block-close (or (p:& #\}))
224 224 (:constant nil))
225 225
226 226 (p:defrule inline-statement (and statement% spaces?)
227 227 (:function first))
228 228
229 229 (p:defrule next-inline-statement (and #\& spaces? inline-statement)
230 230 (:function third))
231 231
232 232 (p:defrule not-a-non-statement (and (p:! (p:~ "elseif"))
233 233 (p:! (p:~ "else"))
234 234 (p:! (p:~ "end"))))
235 235
236 236 (p:defrule statement (and inline-statement statement-end)
237 237 (:function first))
238 238
239 239 (p:defrule statement% (and not-a-non-statement
240 240 (or label comment string-output
241 241 block non-returning-intrinsic local
242 242 assignment expression-output))
243 243 (:function second))
244 244
245 245 (p:defrule expr-stopper (or comment block non-returning-intrinsic))
246 246
247 247 (p:defrule string-output qsp-string
248 248 (:lambda (string)
249 249 (list 'lib:main-pl string)))
250 250
251 251 (p:defrule expression-output expression
252 252 (:lambda (list)
253 253 (list 'lib:main-pl list)))
254 254
255 255 (p:defrule label (and colon identifier)
256 256 (:lambda (list)
257 257 (intern (string (second list)) :keyword)))
258 258
259 259 (p:defrule comment (and #\! (* (or qsp-string brace-comment text-spaces not-newline)))
260 260 (:constant nil))
261 261
262 262 (p:defrule brace-comment (and #\{ (* (not-brace character)) #\})
263 263 (:constant nil))
264 264
265 265 (p:defrule local (and (p:~ "local") spaces variable (p:? (and spaces? #\= spaces? expression)))
266 266 (:lambda (list)
267 267 (list* 'lib:local (third list)
268 268 (when (fourth list)
269 269 (list (fourth (fourth list)))))))
270 270
271 271 ;;; Blocks
272 272
273 273 (p:defrule block (or block-act block-if block-for))
274 274
275 275 (p:defrule block-if (and block-if-head block-if-body)
276 276 (:destructure (head body)
277 277 `(lib:qspcond (,@head ,@(first body))
278 278 ,@(rest body))))
279 279
280 280 (p:defrule block-if-head (and (p:~ "if") spaces expression spaces? colon spaces?)
281 281 (:function remove-nil)
282 282 (:function cdr))
283 283
284 284 (p:defrule block-if-body (or block-if-ml block-if-sl)
285 285 (:destructure (if-body elseifs else &rest ws)
286 286 (declare (ignore ws))
287 287 `(,(remove-nil if-body) ,@elseifs ,@(when else `((else ,@else))))))
288 288
289 289 (p:defrule block-if-sl (and line-body
290 290 (p:? block-if-elseif-inline)
291 291 (p:? block-if-else-inline)
292 292 spaces?))
293 293
294 294 (p:defrule block-if-ml (and (and #\newline spaces?)
295 295 block-body
296 296 (p:? block-if-elseif)
297 297 (p:? block-if-else)
298 298 block-if-end)
299 299 (:lambda (list)
300 300 (cdr list)))
301 301
302 302 (p:defrule block-if-elseif-inline (and block-if-elseif-head line-body (p:? block-if-elseif-inline))
303 303 (:destructure (head statements elseif)
304 304 `((,@(rest head) ,@(remove-nil statements)) ,@elseif)))
305 305
306 306 (p:defrule block-if-elseif (and block-if-elseif-head statement-end block-body (p:? block-if-elseif))
307 307 (:destructure (head ws statements elseif)
308 308 (declare (ignore ws))
309 309 `((,@(rest head) ,@(remove-nil statements)) ,@elseif)))
310 310
311 311 (p:defrule block-if-elseif-head (and (p:~ "elseif") spaces expression spaces? colon spaces?)
312 312 (:function remove-nil)
313 313 (:function intern-first))
314 314
315 315 (p:defrule block-if-else-inline (and block-if-else-head line-body)
316 316 (:function second))
317 317
318 318 (p:defrule block-if-else (and block-if-else-head #\newline spaces? block-body)
319 319 (:function fourth))
320 320
321 321 (p:defrule block-if-else-head (and (p:~ "else") spaces?)
322 322 (:constant nil))
323 323
324 324 (p:defrule block-if-end (and (p:~ "end")
325 325 (p:? (and spaces (p:~ "if"))))
326 326 (:constant nil))
327 327
328 328 (p:defrule block-act (and block-act-head (or block-ml block-sl))
329 329 (:lambda (list)
330 330 (apply #'append list)))
331 331
332 332 (p:defrule block-act-head (and (p:~ "act") spaces? qsp-string spaces?
333 333 (p:? block-act-head-img)
334 334 colon spaces?)
335 335 (:lambda (list)
336 336 (intern-first (list (first list)
337 337 (third list)
338 338 (or (fifth list) '(lib:str ""))))))
339 339
340 340 (p:defrule block-act-head-img (and #\, spaces? qsp-string spaces?)
341 341 (:lambda (list)
342 342 (or (third list) "")))
343 343
344 344 (p:defrule block-for (and block-for-head (or block-ml block-sl))
345 345 (:lambda (list)
346 346 (apply #'append list)))
347 347
348 348 (p:defrule block-for-head (and (p:~ "for") spaces variable spaces? equal spaces? expression
349 349 (p:~ "to") spaces expression
350 350 block-for-head-step
351 351 colon spaces?)
352 352 (:lambda (list)
353 353 (unless (eq (fourth (third list)) :num)
354 354 (error "For counter variable must be numeric."))
355 355 (list 'lib:qspfor
356 356 (elt list 2)
357 357 (elt list 6)
358 358 (elt list 9)
359 359 (elt list 10))))
360 360
361 361 (p:defrule block-for-head-step (p:? (and (p:~ "step") spaces expression spaces?))
362 362 (:lambda (list)
363 363 (if list
364 364 (third list)
365 365 1)))
366 366
367 367 (p:defrule block-sl line-body)
368 368
369 369 (p:defrule block-ml (and newline-block-body block-end)
370 370 (:lambda (list)
371 371 (apply #'list* (butlast list))))
372 372
373 373 (p:defrule block-end (and (p:~ "end"))
374 374 (:constant nil))
375 375
376 376 ;;; Calls
377 377
378 378 (p:defrule first-argument (and expression spaces?)
379 379 (:function first))
380 380 (p:defrule next-argument (and "," spaces? expression)
381 381 (:function third))
382 382 (p:defrule arguments (or parenthesized-arguments plain-arguments no-arguments))
383 383 (p:defrule parenthesized-arguments (and spaces? #\( base-arguments #\))
384 384 (:function third))
385 385 (p:defrule plain-arguments (and spaces? base-arguments)
386 386 (:function second))
387 387 (p:defrule no-arguments (or (and spaces? (p:& #\newline))
388 388 (and spaces? (p:& #\&))
389 389 spaces?)
390 390 (:constant nil))
391 391 (p:defrule base-arguments (or (and first-argument (* next-argument)) spaces?)
392 392 (:lambda (list)
393 393 (if (null list)
394 394 nil
395 395 (list* (first list) (second list)))))
396 396
397 397 ;;; Intrinsics
398 398
399 399 (defmacro defintrinsics ((rule-name returning-rule-name non-returning-rule-name) &body clauses)
400 400 `(progn
401 401 ,@(loop :for clause :in clauses
402 402 :collect `(defintrinsic ,@clause))
403 403 (p:defrule ,returning-rule-name (or ,@(remove-nil
404 404 (mapcar (lambda (clause)
405 405 (when (second clause)
406 406 (alexandria:symbolicate
407 407 'intrinsic- (first clause))))
408 408 clauses))))
409 409 (p:defrule ,non-returning-rule-name (or ,@(remove-nil
410 410 (mapcar (lambda (clause)
411 411 (unless (second clause)
412 412 (alexandria:symbolicate
413 413 'intrinsic- (first clause))))
414 414 clauses))))
415 415 (p:defrule ,rule-name (or ,returning-rule-name ,non-returning-rule-name))))
416 416
417 417 (defmacro defintrinsic (sym returning &optional (min-arity 0) (max-arity 10) &rest names)
418 418 (declare (ignore returning))
419 419 (setf names
420 420 (if names
421 421 (mapcar #'string-upcase names)
422 422 (list (string sym))))
423 423 `(p:defrule ,(alexandria:symbolicate 'intrinsic- sym)
424 424 (and (p:? #\$) (or ,@(loop :for name :in names :collect `(p:~ ,name)))
425 425 arguments)
426 426 (:destructure (dollar name arguments)
427 427 (declare (ignore dollar))
428 428 (unless (<= ,min-arity (length arguments) ,max-arity)
429 429 (error "Intrinsic ~A expects between ~A and ~A arguments but ~A were provided:~%~S"
430 430 name ,min-arity ,max-arity (length arguments) arguments))
431 431 (list* ',(intern (string sym) :lib) arguments))))
432 432
433 433 (defintrinsics (intrinsic returning-intrinsic non-returning-intrinsic)
434 434 ;; Transitions
435 435 (goto% nil 0 10 "gt" "goto")
436 436 (xgoto% nil 0 10 "xgt" "xgoto")
437 437 ;; Variables
438 438 (killvar nil 0 2)
439 439 ;; Expressions
440 440 (obj t 1 1)
441 441 (loc t 1 1)
442 442 (no t 1 1)
443 443 ;; Basic
444 444 (qspver t 0 0)
445 445 (curloc t 0 0)
446 446 (rand t 1 2)
447 447 (rnd t 0 0)
448 448 (qspmax t 1 10 "max")
449 449 (qspmin t 1 10 "min")
450 450 ;; Arrays
451 451 (killall nil 0 0)
452 452 (copyarr nil 2 4)
453 453 (arrsize t 1 1)
454 454 (arrpos t 2 3)
455 455 (arrcomp t 2 3)
456 456 ;; Strings
457 457 (len t 1 1)
458 458 (mid t 2 3)
459 459 (ucase t 1 1)
460 460 (lcase t 1 1)
461 461 (trim t 1 1)
462 462 (replace t 2 3)
463 463 (instr t 2 3)
464 464 (isnum t 1 1)
465 465 (val t 1 1)
466 466 (qspstr t 1 1 "str")
467 467 (strcomp t 2 2)
468 468 (strfind t 2 3)
469 469 (strpos t 2 3)
470 470 ;; IF
471 471 (iif t 2 3)
472 472 ;; Subs
473 473 (gosub nil 1 10 "gosub" "gs")
474 474 (func t 1 10)
475 475 (exit nil 0 0)
476 476 ;; Jump
477 477 (jump nil 1 1)
478 478 ;; Dynamic
479 479 (dynamic nil 1 10)
480 480 (dyneval t 1 10)
481 481 ;; Sound
482 482 (play nil 1 2)
483 483 (isplay t 1 1)
484 484 (close nil 1 1)
485 485 (closeall nil 0 0 "close all")
486 486 ;; Main window
487 487 (main-pl nil 1 1 "*pl")
488 488 (main-nl nil 0 1 "*nl")
489 489 (main-p nil 1 1 "*p")
490 490 (maintxt t 0 0)
491 491 (desc t 1 1)
492 492 (main-clear nil 0 0 "*clear" "*clr")
493 493 ;; Aux window
494 494 (showstat nil 1 1)
495 495 (stat-pl nil 1 1 "pl")
496 496 (stat-nl nil 0 1 "nl")
497 497 (stat-p nil 1 1 "p")
498 498 (stattxt t 0 0)
499 499 (stat-clear nil 0 0 "clear" "clr")
500 500 (cls nil 0 0)
501 501 ;; Dialog
502 502 (msg nil 1 1)
503 503 ;; Acts
504 504 (showacts nil 1 1)
505 505 (delact nil 1 1 "delact" "del act")
506 506 (curacts t 0 0)
507 507 (cla nil 0 0)
508 508 ;; Objects
509 509 (showobjs nil 1 1)
510 510 (addobj nil 1 3 "addobj" "add obj")
511 511 (delobj nil 1 1 "delobj" "del obj")
512 512 (killobj nil 0 1)
513 513 (countobj t 0 0)
514 514 (getobj t 1 1)
515 515 ;; Menu
516 516 (menu nil 1 1)
517 517 ;; Images
518 518 (refint nil 0 0)
519 519 (view nil 0 1)
520 520 ;; Fonts
521 521 (rgb t 3 3)
522 522 ;; Input
523 523 (showinput nil 1 1)
524 524 (usertxt t 0 0 "user_text" "usrtxt")
525 525 (cmdclear nil 0 0 "cmdclear" "cmdclr")
526 526 (input t 1 1)
527 527 ;; Files
528 528 (openqst nil 1 1)
529 529 (addqst nil 1 1 "addqst" "addlib" "inclib")
530 530 (killqst nil 1 1 "killqst" "dellib" "freelib")
531 531 (opengame nil 0 0)
532 532 (savegame nil 0 0)
533 533 ;; Real time
534 534 (wait nil 1 1)
535 535 (msecscount t 0 0)
536 536 (settimer nil 1 1))
537 537
538 538 ;;; Expression
539 539
540 540 (p:defrule expression or-expr)
541 541
542 542 (p:defrule or-expr (and and-expr (* (and spaces? or-op spaces? and-expr)))
543 543 (:function do-binop))
544 544
545 545 (p:defrule and-expr (and eq-expr (* (and spaces? and-op spaces? eq-expr)))
546 546 (:function do-binop))
547 547
548 548 (p:defrule eq-expr (and sum-expr (* (and spaces? (or "<=" ">=" "=<" "=>" "<>"
549 549 "=" "<" ">" "!")
550 550 spaces? sum-expr)))
551 551 (:function do-binop))
552 552
553 553 (p:defrule sum-expr (and mod-expr (* (and spaces? (or #\+ #\-) spaces? mod-expr)))
554 554 (:function do-binop))
555 555
556 556 (p:defrule mod-expr (and mul-expr (* (and spaces? (p:~ "mod") spaces? mul-expr)))
557 557 (:function do-binop))
558 558
559 559 (p:defrule mul-expr (and unary-expr (* (and spaces? (or #\* #\/) spaces? unary-expr)))
560 560 (:function do-binop))
561 561
562 562 (p:defrule unary-expr (and (p:? (or #\+ #\-)) atom-expr)
563 563 (:lambda (list)
564 564 (let ((expr (remove-nil list)))
565 565 (if (= 1 (length expr))
566 566 (first expr)
567 567 (intern-first expr)))))
568 568
569 569 (p:defrule atom-expr (and (or variable literal returning-intrinsic paren-expr) spaces?)
570 570 (:function first))
571 571
572 572 (p:defrule paren-expr (and #\( spaces? expression spaces? #\))
573 573 (:function third))
574 574
575 575 (p:defrule or-op (p:~ "or")
576 576 (:constant "or"))
577 577
578 578 (p:defrule and-op (p:~ "and")
579 579 (:constant "and"))
580 580
581 581 ;;; Variables
582 582
583 583 (p:defrule variable (and identifier (p:? array-index))
584 584 (:destructure (id idx)
585 (if (char= #\$ (elt (string id) 0))
586 (list 'lib:qspvar (intern (subseq (string-upcase id) 1) :lib) (or idx 0) :str)
587 (list 'lib:qspvar id (or idx 0) :num))))
585 (let ((idx (case idx
586 (nil 0)
587 (:last nil)
588 (t idx))))
589 (if (char= #\$ (elt (string id) 0))
590 (list 'lib:qspvar (intern (subseq (string-upcase id) 1) :lib) idx :str)
591 (list 'lib:qspvar id idx :num)))))
588 592
589 593 (p:defrule array-index (and #\[ spaces? (p:? expression) spaces? #\])
590 (:function third))
594 (:lambda (list)
595 (or (third list) :last)))
591 596
592 597 (p:defrule assignment (or kw-assignment plain-assignment op-assignment)
593 598 (:destructure (qspvar eq expr)
594 599 (declare (ignore eq))
595 600 (list 'lib:set qspvar expr)))
596 601
597 602 (p:defrule kw-assignment (and (or (p:~ "let") (p:~ "set")) spaces? (or op-assignment plain-assignment))
598 603 (:function third))
599 604
600 605 (p:defrule op-assignment (and variable spaces? (or "+" "-" "*" "/") #\= spaces? expression)
601 606 (:destructure (qspvar ws1 op eq ws2 expr)
602 607 (declare (ignore ws1 ws2))
603 608 (list qspvar eq (intern-first (list op qspvar expr)))))
604 609
605 610 (p:defrule plain-assignment (and variable spaces? #\= spaces? expression)
606 611 (:function remove-nil))
607 612
608 613 ;;; Non-string literals
609 614
610 615 (p:defrule literal (or qsp-string brace-string number))
611 616
612 617 (p:defrule number (+ (or #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
613 618 (:lambda (list)
614 619 (parse-integer (p:text list))))
General Comments 0
You need to be logged in to leave comments. Login now