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