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