##// END OF EJS Templates
Menu, game saving
naryl -
r11:ca6bf409 default
parent child Browse files
Show More
@@ -0,0 +1,21 b''
1
2 (in-package sugar-qsp)
3
4 (eval-when (:compile-toplevel :load-toplevel :execute)
5 (defun src-file (filename)
6 (uiop/pathname:merge-pathnames*
7 filename
8 (asdf:system-source-directory :sugar-qsp)))
9 (defun compile-ps (filename)
10 (format nil "////// Parenscript file: ~A~%~%~A"
11 (file-namestring filename) (ps:ps-compile-file filename))))
12
13 (defclass compiler ()
14 ((body :accessor body :initform #.(alexandria:read-file-into-string (src-file "extras/body.html")))
15 (css :accessor css :initform (list #.(alexandria:read-file-into-string (src-file "extras/default.css"))))
16 (js :accessor js :initform (list #.(compile-ps (src-file "src/intrinsics.ps"))
17 #.(compile-ps (src-file "src/api.ps"))
18 #.(compile-ps (src-file "src/main.ps"))))
19 (compile :accessor compile-only :initarg :compile)
20 (target :accessor target :initarg :target)
21 (beautify :accessor beautify :initarg :beautify)))
@@ -0,0 +1,137 b''
1
2 (in-package sugar-qsp)
3
4 ;;;; Macros implementing some intrinsics where it makes sense
5 ;;;; E.g. an equivalent JS function exists, or it's a direct API call
6
7 ;;; 1loc
8
9 ;;; 2var
10
11 (ps:defpsmacro killvar (varname &optional (index :whole))
12 `(api-call kill-var ,varname ,index))
13
14 (ps:defpsmacro killall ()
15 `(api-call kill-all))
16
17 ;;; 3expr
18
19 (ps:defpsmacro obj (name)
20 `(funcall (root objs includes) ,name))
21
22 (ps:defpsmacro loc (name)
23 `(funcall (root locs includes) ,name))
24
25 (ps:defpsmacro no (arg)
26 `(- -1 ,arg))
27
28 ;;; 4code
29
30 (ps:defpsmacro qspver ()
31 "0.0.1")
32
33 (ps:defpsmacro curloc ()
34 `(root current-location))
35
36 (ps:defpsmacro rnd ()
37 `(funcall (root lib rand) 1 1000))
38
39 (ps:defpsmacro qspmax (&rest args)
40 `(max ,@args))
41
42 (ps:defpsmacro qspmin (&rest args)
43 `(min ,@args))
44
45 ;;; 5arrays
46
47 (ps:defpsmacro arrsize (name)
48 `(api-call array-size ,name))
49
50 ;;; 6str
51
52 (ps:defpsmacro len (s)
53 `(length ,s))
54
55 (ps:defpsmacro mid (s from &optional count)
56 `(ps:chain ,s (substring ,from ,count)))
57
58 (ps:defpsmacro ucase (s)
59 `(ps:chain ,s (to-upper-case)))
60
61 (ps:defpsmacro lcase (s)
62 `(ps:chain ,s (to-lower-case)))
63
64 (ps:defpsmacro trim (s)
65 `(ps:chain ,s (trim)))
66
67 (ps:defpsmacro replace (s from to)
68 `(ps:chain ,s (replace ,from ,to)))
69
70 (ps:defpsmacro val (s)
71 `(parse-int ,s 10))
72
73 (ps:defpsmacro qspstr (n)
74 `(ps:chain ,n (to-string)))
75
76 ;;; 7if
77
78 ;;; 8sub
79
80 ;;; 9loops
81
82 ;; JUMP is in ps-macros.lisp (because it's a part of a huge kludge)
83
84 (ps:defpsmacro exit ()
85 `(return-from nil (values)))
86
87 ;;; 10dynamic
88
89 ;;; 11main
90
91 (ps:defpsmacro desc (s)
92 (declare (ignore s))
93 "")
94
95 ;;; 12stat
96
97 (ps:defpsmacro showstat (enable)
98 `(api-call enable-frame :stat ,enable))
99
100 ;;; 13diag
101
102 (ps:defpsmacro msg (text)
103 `(alert ,text))
104
105 ;;; 14act
106
107 (ps:defpsmacro showacts (enable)
108 `(api-call enable-frame :acts ,enable))
109
110 (ps:defpsmacro delact (name)
111 `(api-call del-act ,name))
112
113 (ps:defpsmacro cla ()
114 `(api-call clear-act))
115
116 ;;; 15objs
117
118 (ps:defpsmacro showobjs (enable)
119 `(api-call enable-frame :objs ,enable))
120
121 (ps:defpsmacro countobj ()
122 `(length (root objs)))
123
124 (ps:defpsmacro getobj (index)
125 `(or (elt (root objs) ,index) ""))
126
127 ;;; 16menu
128
129 ;;; 17sound
130
131 ;;; 18img
132
133 ;;; 19input
134
135 ;;; 20time
136
137 ;;; misc
@@ -1,9 +1,6 b''
1 1
2 * Windows GUI
3 * Save-load game
2 * Windows GUI (for the compiler)
3 * Save-load game in slots
4 4 * Resizable frames
5 5 * Build Istreblenie
6 ** modifying it to suit compiler specifics
7 ** Implementing apis and intrinsics as needed
8
9 * Use real characters in cl-uglify-js No newline at end of file
6 ** modifying it to suit compiler specifics No newline at end of file
@@ -1,12 +1,15 b''
1 1
2 2 <div id="qsp">
3 3 <div class="qsp-col qsp-col1">
4 4 <div id="qsp-main" class="qsp-frame">&nbsp;</div>
5 5 <div id="qsp-acts" class="qsp-frame">&nbsp;</div>
6 6 <input id="qsp-input" class="qsp-frame">
7 7 </div>
8 8 <div class="qsp-col qsp-col2">
9 9 <div id="qsp-stat" class="qsp-frame">&nbsp;</div>
10 10 <div id="qsp-objs" class="qsp-frame">&nbsp;</div>
11 11 </div>
12 12 </div>
13
14 <div id="qsp-dropdown">
15 </div>
@@ -1,59 +1,84 b''
1 1
2 2 .qsp-frame {
3 3 border: 1px solid black;
4 4 overflow: auto;
5 5 padding: 5px;
6 6 box-sizing: border-box;
7 7 }
8 8
9 9 #qsp {
10 10 position: absolute;
11 11 display: flex;
12 12 flex-flow: row;
13 13 top: 0;
14 14 left: 0;
15 15 width: 100%;
16 16 height: 100%;
17 17 }
18 18
19 19 .qsp-col {
20 20 display: flex;
21 21 flex-flow: column;
22 22 }
23 23
24 24 .qsp-col1 {
25 25 flex: 7 7 70px;
26 26 }
27 27
28 28 .qsp-col2 {
29 29 flex: 3 3 30px;
30 30 }
31 31
32 32 #qsp-main {
33 33 flex: 6 6 60px;
34 34 }
35 35
36 36 #qsp-acts {
37 37 flex: 4 4 40px;
38 38 }
39 39
40 40 #qsp-input {
41 41 }
42 42
43 43 #qsp-stat {
44 44 flex: 5 5 50px;
45 45 }
46 46
47 47 #qsp-objs {
48 48 flex: 5 5 50px;
49 49 }
50 50
51 51 .qsp-act {
52 52 display: block;
53 53 padding: 2px;
54 54 font-size: large;
55 55 }
56 56
57 57 .qsp-act:hover {
58 58 outline: #9E9E9E outset 3px
59 59 }
60
61 // Dropdown
62
63 #qsp-dropdown {
64 display: none;
65 position: absolute;
66 background-color: #f1f1f1;
67 min-width: 160px;
68 overflow: auto;
69 box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
70 z-index: 1;
71 margin: auto;
72 top: 200;
73 }
74
75 #qsp-dropdown a {
76 color: black;
77 padding: 12px 16px;
78 text-decoration: none;
79 display: block;
80 }
81
82 #qsp-dropdown a:hover {
83 background-color: #ddd;
84 }
@@ -1,150 +1,200 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 (defm (root api make-menu-item-html) (num title img loc)
19 (+ "<a href='#' onclick='SugarQSP.api.runMenu(" num ", \"" loc "\")();'>"
20 "<img src='" img "'>"
21 title
22 "</a>"))
23
18 24 ;; To be used in saving game
19 25 (defm (root api stash-state) ()
20 26 (setf (root state-stash)
21 (ps:create vars (root vars)
22 objs (root objs)
23 next-location (root current-location)))
27 (*j-s-o-n.stringify
28 (ps:create vars (root vars)
29 objs (root objs)
30 next-location (root current-location))))
24 31 (values))
25 32
33 (defm (root api state-to-base64) ()
34 (btoa (encode-u-r-i-component (root state-stash))))
35
36 (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 (api-call clear-act)
40 (setf (root vars) (ps:@ data vars))
41 (setf (root objs) (ps:@ data objs))
42 (setf (root current-location) (ps:@ data next-location))
43 (funcall (root locs (root current-location)))
44 (api-call update-objs)
45 (values)))
46
26 47 ;;; Misc
27 48
28 49 (defm (root api clear-id) (id)
29 50 (setf (ps:chain document (get-element-by-id id) inner-text) ""))
30 51
31 52 (defm (root api get-id) (id)
32 53 (if (var "USEHTML" 0)
33 54 (ps:chain (document.get-element-by-id id) inner-h-t-m-l)
34 55 (ps:chain (document.get-element-by-id id) inner-text)))
35 56
36 57 (defm (root api set-id) (id contents)
37 58 (if (var "USEHTML" 0)
38 59 (setf (ps:chain (document.get-element-by-id id) inner-h-t-m-l) contents)
39 60 (setf (ps:chain (document.get-element-by-id id) inner-text) contents)))
40 61
41 62 (defm (root api append-id) (id contents)
42 63 (if (var "USEHTML" 0)
43 64 (incf (ps:chain (document.get-element-by-id id) inner-h-t-m-l) contents)
44 65 (incf (ps:chain (document.get-element-by-id id) inner-text) contents)))
45 66
46 67 ;;; Function calls
47 68
48 69 (defm (root api init-args) (args)
49 70 (dotimes (i (length args))
50 71 (if (numberp (elt args i))
51 72 (set (var args i) (elt args i))
52 73 (set (var $args i) (elt args i)))))
53 74
54 75 (defm (root api get-result) ()
55 76 (if (not (equal "" (var $result 0)))
56 77 (var $result 0)
57 78 (var result 0)))
58 79
59 80 ;;; Text windows
60 81
61 82 (defm (root api key-to-id) (key)
62 83 (case key
63 84 (:main "qsp-main")
64 85 (:stat "qsp-stat")
86 (:objs "qsp-objs")
87 (:acts "qsp-acts")
88 (:input "qsp-input")
89 (:dropdown "qsp-dropdown")
65 90 (t (report-error "Internal error!"))))
66 91
92 (defm (root api get-frame) (key)
93 (document.get-element-by-id (api-call key-to-id key)))
94
67 95 (defm (root api add-text) (key text)
68 96 (api-call append-id (api-call key-to-id key) text))
69 97
70 98 (defm (root api get-text) (key)
71 99 (api-call get-id (api-call key-to-id key)))
72 100
73 101 (defm (root api clear-text) (key)
74 102 (api-call clear-id (api-call key-to-id key)))
75 103
76 104 (defm (root api newline) (key)
77 (let ((div (document.get-element-by-id
78 (api-call key-to-id key))))
105 (let ((div (api-call get-frame key)))
79 106 (ps:chain div (append-child (document.create-element "br")))))
80 107
108 (defm (root api enable-frame) (key enable)
109 (let ((clss (ps:getprop (api-call get-frame key) 'class-list)))
110 (setf clss.style.display (if enable "block" "none"))
111 (values)))
112
81 113 ;;; Actions
82 114
83 115 (defm (root api add-act) (title img act)
84 116 (setf (ps:getprop (root acts) title)
85 (ps:create :img img :act act)))
117 (ps:create :img img :act act))
118 (api-call update-acts))
86 119
87 120 (defm (root api del-act) (title)
88 121 (delete (ps:getprop (root acts) title))
89 122 (api-call update-acts))
90 123
91 124 (defm (root api clear-act) ()
92 125 (setf (root acts) (ps:create))
93 126 (api-call clear-id "qsp-acts"))
94 127
95 128 (defm (root api update-acts) ()
96 129 (api-call clear-id "qsp-acts")
97 130 (ps:for-in (title (root acts))
98 131 (let ((obj (ps:getprop (root acts) title)))
99 132 (api-call append-id "qsp-acts"
100 133 (api-call make-act-html title (ps:getprop obj :img))))))
101 134
102 135 ;;; Variables
103 136
104 137 (defm (root api var-slot) (name)
105 138 (if (= (ps:@ name 0) #\$)
106 139 :str
107 140 :num))
108 141
109 142 (defm (root api var-real-name) (name)
110 143 (if (= (ps:@ name 0) #\$)
111 144 (ps:chain name (substr 1))
112 145 name))
113 146
114 147 (defm (root api ensure-var) (name index)
115 148 (unless (in name (root vars))
116 149 (setf (ps:getprop (root vars) name)
117 150 (ps:create)))
118 151 (unless (in index (ps:getprop (root vars) name))
119 152 (setf (ps:getprop (root vars) name index)
120 153 (ps:create :num 0 :str "")))
121 154 (values))
122 155
123 156 (defm (root api get-var) (name index)
124 157 (let ((var-name (api-call var-real-name name)))
125 158 (api-call ensure-var var-name index)
126 159 (ps:getprop (root vars) var-name index
127 160 (api-call var-slot name))))
128 161
129 162 (defm (root api set-var) (name index value)
130 163 (let ((var-name (api-call var-real-name name)))
131 164 (api-call ensure-var var-name index)
132 165 (setf (ps:getprop (root vars) var-name index
133 166 (api-call var-slot name))
134 167 value)
135 168 (values)))
136 169
170 (defm (root api get-array) (name type)
171 (ps:getprop (root vars) (api-call var-real-name name)))
172
137 173 (defm (root api kill-var) (name index)
138 174 (if (eq index :whole)
139 175 (ps:delete (ps:getprop (root vars) name))
140 176 (ps:delete (ps:getprop (root vars) name index)))
141 177 (values))
142 178
179 (defm (root api array-size) (name)
180 (ps:getprop (root vars) (api-call var-real-name name) 'length))
181
143 182 ;;; Objects
144 183
145 184 (defm (root api update-objs) ()
146 185 (let ((elt (document.get-element-by-id "qsp-objs")))
147 186 (setf elt.inner-h-t-m-l "<ul>")
148 187 (loop :for obj :in (root objs)
149 188 :do (incf elt.inner-h-t-m-l (+ "<li>" obj)))
150 189 (incf elt.inner-h-t-m-l "</ul>")))
190
191 ;;; Menu
192
193 (defm (root api menu) (menu-data)
194 (let ((elt (document.get-element-by-id "qsp-dropdown"))
195 (i 0))
196 (setf elt.inner-h-t-m-l "")
197 (loop :for item :in menu-data
198 :do (incf i)
199 :do (incf elt.inner-h-t-m-l (api-call make-menu-item-html i item.text item.icon item.loc)))
200 (setf elt.style.display "block")))
@@ -1,299 +1,313 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 &rest args)
13 13 (api-call clear-text :main)
14 14 (apply (root lib xgoto) target args))
15 15
16 16 (defm (root lib xgoto) (target &rest args)
17 17 (api-call clear-act)
18 18 (api-call init-args args)
19 (setf (root current-location) target)
19 (setf (root current-location) (ps:chain target (to-upper-case)))
20 20 (api-call stash-state)
21 (funcall (ps:getprop (root locations) (ps:chain target (to-upper-case)))))
21 (funcall (ps:getprop (root locs) (root current-location))))
22 22
23 23 ;;; 2var
24 24
25 (defm (root lib killvar) (varname &optional (index :whole))
26 (api-call kill-var varname index))
27
28 (defm (root lib killall) ()
29 (api-call kill-all))
30
31 25 ;;; 3expr
32 26
33 (defm (root lib obj) (name)
34 (funcall (root objs includes) name))
35
36 (defm (root lib loc) ()
37 (funcall (root locations includes) name))
38
39 (defm (root lib no) (arg)
40 (- -1 arg))
41
42 27 ;;; 4code
43 28
44 (defm (root lib qspver) ()
45 "0.0.1")
46
47 (defm (root lib curloc) ()
48 (root current-location))
49
50 (defm (root lib rand) (a b)
29 (defm (root lib rand) (a &optional (b 1))
51 30 (let ((min (min a b))
52 31 (max (max a b)))
53 32 (+ min (ps:chain *math (random (- max min))))))
54 33
55 (defm (root lib rnd) ()
56 (funcall (root lib rand) 1 1000))
57
58 (defm (root lib qspmax) (&rest args)
59 (apply (ps:@ *math max) args))
60
61 (defm (root lib qspmin) (&rest args)
62 (apply (ps:@ *math min) args))
63
64 34 ;;; 5arrays
65 35
66 36 (defm (root lib copyarr) (to from start count)
67 37 (ps:for ((i start))
68 38 ((< i (min (api-call array-size from)
69 39 (+ start count))))
70 40 ((incf i))
71 41 (api-call set-var to (+ start i)
72 42 (api-call get-var from (+ start i)))))
73 43
74 (defm (root lib arrsize) (name)
75 (api-call array-size name))
76
77 44 (defm (root lib arrpos) (name value &optional (start 0))
78 45 (ps:for ((i start)) ((< i (api-call array-size name))) ((incf i))
79 46 (when (eq (api-call get-var name i) value)
80 47 (return i)))
81 48 -1)
82 49
83 50 (defm (root lib arrcomp) (name pattern &optional (start 0))
84 51 (ps:for ((i start)) ((< i (api-call array-size name))) ((incf i))
85 52 (when (funcall (ps:getprop (api-call get-var name i) 'match) pattern)
86 53 (return i)))
87 54 -1)
88 55
89 56 ;;; 6str
90 57
91 (defm (root lib len) (s)
92 (length s))
93
94 (defm (root lib mid) (s from &optional count)
95 (s.substring from count))
96
97 (defm (root lib ucase) (s)
98 (s.to-upper-case))
99
100 (defm (root lib lcase) (s)
101 (s.to-lower-case))
102
103 (defm (root lib trim) (s)
104 (s.trim))
105
106 (defm (root lib replace) (s from to)
107 (s.replace from to))
108
109 58 (defm (root lib instr) (s subs &optional (start 1))
110 59 (+ start (ps:chain s (substring (- start 1)) (search subs))))
111 60
112 61 (defm (root lib isnum) (s)
113 62 (if (is-na-n s)
114 63 0
115 64 -1))
116 65
117 (defm (root lib val) (s)
118 (parse-int s 10))
119
120 (defm (root lib qspstr) (n)
121 (+ "" n))
122
123 66 (defm (root lib strcomp) (s pattern)
124 67 (if (s.match pattern)
125 68 -1
126 69 0))
127 70
128 71 (defm (root lib strfind) (s pattern group)
129 72 (let* ((re (ps:new (*reg-exp pattern)))
130 73 (match (re.exec s)))
131 74 (match.group group)))
132 75
133 76 (defm (root lib strpos) (s pattern &optional (group 0))
134 77 (let* ((re (ps:new (*reg-exp pattern)))
135 78 (match (re.exec s))
136 79 (found (match.group group)))
137 80 (if found
138 81 (s.search found)
139 82 0)))
140 83
141 84 ;;; 7if
142 85
86 ;; Has to be a function because it always evaluates all three of its
87 ;; arguments
143 88 (defm (root lib iif) (cond-expr then-expr else-expr)
144 (if (= -1 cond-expr) then-expr else-expr))
89 (if cond-expr then-expr else-expr))
145 90
146 91 ;;; 8sub
147 92
148 93 (defm (root lib gosub) (target &rest args)
149 94 (conserving-vars (args result)
150 95 (api-call init-args args)
151 (funcall (ps:getprop (root locations) target))
96 (funcall (ps:getprop (root locs) target))
152 97 (values)))
153 98
154 99 (defm (root lib func) (target &rest args)
155 100 (conserving-vars (args result)
156 101 (api-call init-args args)
157 (funcall (ps:getprop (root locations) target))
102 (funcall (ps:getprop (root locs) target))
158 103 (api-call get-result)))
159 104
160 105 ;;; 9loops
161 106
162 107 ;;; 10dynamic
163 108
164 109 (defm (root lib dyneval) (block &rest args)
165 110 (conserving-vars (args result)
166 111 (api-call init-args args)
167 112 (funcall block)
168 113 (api-call get-result)))
169 114
170 115 (defm (root lib dynamic) (&rest args)
171 116 (conserving-vars (args result)
172 117 (api-call init-args args)
173 118 (funcall block)
174 119 (values)))
175 120
176 121 ;;; 11main
177 122
178 123 (defm (root lib main-p) (s)
179 (api-call add-text :main s))
124 (api-call add-text :main s)
125 (values))
180 126
181 127 (defm (root lib main-pl) (s)
182 128 (api-call add-text :main s)
183 (api-call newline :main))
129 (api-call newline :main)
130 (values))
184 131
185 132 (defm (root lib main-nl) (s)
186 133 (api-call newline :main)
187 (api-call add-text :main s))
134 (api-call add-text :main s)
135 (values))
188 136
189 137 (defm (root lib maintxt) (s)
190 (api-call get-text :main))
138 (api-call get-text :main)
139 (values))
191 140
141 ;; For clarity (it leaves a lib.desc() call in JS)
192 142 (defm (root lib desc) (s)
193 143 "")
194 144
195 145 (defm (root lib main-clear) ()
196 (api-call clear-text :main))
146 (api-call clear-text :main)
147 (values))
197 148
198 149 ;;; 12stat
199 150
200 (defm (root lib showstat) ())
151 (defm (root lib stat-p) (s)
152 (api-call add-text :stat s)
153 (values))
201 154
202 (defm (root lib stat-p) ())
155 (defm (root lib stat-pl) (s)
156 (api-call add-text :stat s)
157 (api-call newline :stat)
158 (values))
203 159
204 (defm (root lib stat-pl) ())
160 (defm (root lib stat-nl) (s)
161 (api-call newline :stat)
162 (api-call add-text :stat s)
163 (values))
205 164
206 (defm (root lib stat-nl) ())
207
208 (defm (root lib stattxt) ())
165 (defm (root lib stattxt) (s)
166 (api-call get-text :stat)
167 (values))
209 168
210 (defm (root lib clear) ())
169 (defm (root lib stat-clear) ()
170 (api-call clear-text :stat)
171 (values))
211 172
212 (defm (root lib cls) ())
173 (defm (root lib cls) ()
174 (funcall (root lib stat-clear))
175 (funcall (root lib main-clear))
176 (funcall (root lib cla))
177 (funcall (root lib cmdclear))
178 (values))
213 179
214 180 ;;; 13diag
215 181
216 (defm (root lib msg) ())
217
218 182 ;;; 14act
219 183
220 (defm (root lib showacts) ())
221
222 (defm (root lib delact) (name)
223 (api-call del-act name))
224
225 (defm (root lib curacts) ())
226
227 (defm (root lib cla) ())
184 (defm (root lib curacts) ()
185 (let ((acts (root acts)))
186 (lambda ()
187 (setf (root acts) acts)
188 (values))))
228 189
229 190 ;;; 15objs
230 191
231 (defm (root lib showobjs) ())
232
233 192 (defm (root lib addobj) (name)
234 193 (ps:chain (root objs) (push name))
235 (api-call update-objs))
194 (api-call update-objs)
195 (values))
236 196
237 197 (defm (root lib delobj) (name)
238 198 (let ((index (ps:chain (root objs) (index-of name))))
239 199 (when (> index -1)
240 (ps:chain (root objs) (splice index 1))))
241 (api-call update-objs))
200 (funcall (root lib killobj) index)))
201 (values))
242 202
243 (defm (root lib killobj) ())
244
245 (defm (root lib countobj) ())
246
247 (defm (root lib getobj) ())
203 (defm (root lib killobj) (&optional num)
204 (if num
205 (ps:chain (root objs) (splice (1+ num) 1))
206 (setf (root objs) (list)))
207 (api-call update-objs)
208 (values))
248 209
249 210 ;;; 16menu
250 211
251 (defm (root lib menu) ())
212 (defm (root lib menu) (menu-name)
213 (let ((menu-data (list)))
214 (loop :for item :in (api-call get-array menu-name)
215 :do (cond ((string= item "")
216 (break))
217 ((string= item "-:-")
218 (ps:chain menu-data (push :delimiter)))
219 (t
220 (let* ((tokens (ps:chain item (split ":"))))
221 (when (= (length tokens) 2)
222 (tokens.push ""))
223 (let* ((text (ps:chain tokens (splice 0 (- tokens.length 2)) (join ":")))
224 (loc (ps:getprop tokens (- tokens.length 2)))
225 (icon (ps:getprop tokens (- tokens.length 1))))
226 (ps:chain menu-data
227 (push (ps:create text text
228 loc loc
229 icon icon))))))))
230 (api-call menu menu-data)
231 (values)))
252 232
253 233 ;;; 17sound
254 234
255 235 (defm (root lib play) ())
256 236
257 237 (defm (root lib isplay) ())
258 238
259 239 (defm (root lib close) ())
260 240
261 241 (defm (root lib closeall) ())
262 242
263 243 ;;; 18img
264 244
265 245 (defm (root lib refint) ())
266 246
267 247 (defm (root lib view) ())
268 248
269 249 ;;; 19input
270 250
271 251 (defm (root lib showinput) ())
272 252
273 253 (defm (root lib usertxt) ())
274 254
275 255 (defm (root lib cmdclear) ())
276 256
277 257 (defm (root lib input) ())
278 258
279 259 ;;; 20time
280 260
281 (defm (root lib wait) ())
261 ;; I wonder if there's a better solution than busy-wait
262 (defm (root lib wait) (msec)
263 (let* ((now (ps:new (*date)))
264 (exit-time (+ (funcall now.get-time) msec)))
265 (loop :while (< (funcall now.get-time) exit-time))))
282 266
283 267 (defm (root lib msecscount) ())
284 268
285 269 (defm (root lib settimer) ())
286 270
287 271 ;;; misc
288 272
289 273 (defm (root lib rgb) ())
290 274
291 275 (defm (root lib openqst) ())
292 276
293 277 (defm (root lib addqst) ())
294 278
295 279 (defm (root lib killqst) ())
296 280
297 (defm (root lib opengame) ())
281 (defm (root lib opengame) (&optional filename)
282 (let ((element (document.create-element :input)))
283 (element.set-attribute :type :file)
284 (element.set-attribute :id :qsp-opengame)
285 (element.set-attribute :tabindex -1)
286 (element.set-attribute "aria-hidden" t)
287 (setf element.style.display :block)
288 (setf element.style.visibility :hidden)
289 (setf element.style.position :fixed)
290 (setf element.onchange
291 (lambda (event)
292 (let* ((file (elt event.target.files 0))
293 (reader (ps:new (*file-reader))))
294 (setf reader.onload
295 (lambda (ev)
296 (block nil
297 (let ((target ev.current-target))
298 (unless target.result
299 (return))
300 (api-call base64-to-state target.result)))))
301 (reader.read-as-text file))))
302 (document.body.append-child element)
303 (element.click)
304 (document.body.remove-child element)))
298 305
299 (defm (root lib savegame) ())
306 (defm (root lib savegame) (&optional filename)
307 (let ((element (document.create-element :a)))
308 (element.set-attribute :href (+ "data:text/plain;charset=utf-8," (api-call state-to-base64)))
309 (element.set-attribute :download "savegame.sav")
310 (setf element.style.display :none)
311 (document.body.append-child element)
312 (element.click)
313 (document.body.remove-child element)))
@@ -1,172 +1,153 b''
1 1
2 2 (in-package sugar-qsp)
3 3
4 4 (defun entry-point-no-args ()
5 5 (entry-point uiop:*command-line-arguments*))
6 6
7 7 (defun entry-point (args)
8 8 (catch :terminate
9 9 (let ((compiler (apply #'make-instance 'compiler (parse-opts args))))
10 10 (write-compiled-file compiler)))
11 11 (values))
12 12
13 13 (defun parse-opts (args)
14 14 (let ((mode :source)
15 15 (data (list :source nil :target nil :js nil :css nil :body nil :compile nil :beautify nil)))
16 16 (loop :for arg :in args
17 17 :do (alexandria:switch (arg :test #'string=)
18 18 ("-o" (setf mode :target))
19 19 ("--js" (setf mode :js))
20 20 ("--css" (setf mode :css))
21 21 ("--body" (setf mode :body))
22 22 ("-c" (setf (getf data :compile) t))
23 23 ("--beautify" (setf (getf data :beautify) t))
24 24 (t (push arg (getf data mode)))))
25 25 (unless (= 1 (length (getf data :source)))
26 26 (print-usage)
27 27 (report-error "There should be exactly one source"))
28 28 (unless (> 1 (length (getf data :target)))
29 29 (print-usage)
30 30 (report-error "There should be no more than one target"))
31 31 (unless (> 1 (length (getf data :body)))
32 32 (print-usage)
33 33 (report-error "There should be no more than one body"))
34 34 (unless (getf data :target)
35 35 (setf (getf data :target)
36 36 (let* ((source (first (getf data :source)))
37 37 (tokens (uiop:split-string source :separator "."))
38 38 (target (format nil "~{~A~^.~}.html"
39 39 (butlast tokens))))
40 40 (list target))))
41 41 (list :source (first (getf data :source))
42 42 :target (first (getf data :target))
43 43 :js (getf data :js)
44 44 :css (getf data :css)
45 45 :body (first (getf data :body))
46 46 :compile (getf data :compile)
47 47 :beautify (getf data :beautify))))
48 48
49 49 (defun print-usage ()
50 50 (format t "USAGE: "))
51 51
52 52 (defun parse-file (filename)
53 53 (p:parse 'sugar-qsp-grammar
54 54 (alexandria:read-file-into-string filename)))
55 55
56 56 (defun make-javascript (locations)
57 (format nil "~{~A~^~%~%~}"
57 (format nil "////// THE GAME STARTS HERE~%~%~{~A~^~%~%~}"
58 58 (mapcar #'ps:ps* locations)))
59 59
60 60 (defun uglify-js::write-json-chars (quote s stream)
61 61 "Write JSON representations (chars or escape sequences) of
62 62 characters in string S to STREAM.
63 63 Monkey-patched to output plain utf-8 instead of escape-sequences."
64 64 (write-char quote stream)
65 65 (loop :for ch :across s
66 66 :for code := (char-code ch)
67 67 :with special
68 68 :do (cond ((eq ch quote)
69 69 (write-char #\\ stream) (write-char ch stream))
70 70 ((setq special (car (rassoc ch uglify-js::+json-lisp-escaped-chars+)))
71 71 (write-char #\\ stream) (write-char special stream))
72 72 (t
73 73 (write-char ch stream))))
74 74 (write-char quote stream))
75 75
76 76 (defun preprocess-js (js beautify)
77 77 (if beautify
78 78 (cl-uglify-js:ast-gen-code
79 79 (cl-uglify-js:ast-squeeze
80 80 (parse-js:parse-js js)
81 81 :sequences nil)
82 82 :beautify t)
83 83 (cl-uglify-js:ast-gen-code
84 84 (cl-uglify-js:ast-mangle
85 85 (cl-uglify-js:ast-squeeze
86 86 (parse-js:parse-js js)))
87 87 :beautify nil)))
88 88
89 89 (defun report-error (fmt &rest args)
90 90 (apply #'format t fmt args)
91 91 (throw :terminate nil))
92 92
93 93 ;;; JS
94 94
95 (defun src-file (filename)
96 (uiop/pathname:merge-pathnames*
97 filename
98 (asdf:system-source-directory :sugar-qsp)))
99
100 95 (defmethod js-sources ((compiler compiler))
101 96 (format nil "~{~A~^~%~%~}" (reverse (js compiler))))
102 97
103 (defun compile-ps (filename)
104 (format nil "////// Parenscript file: ~A~%~%~A"
105 (file-namestring filename) (ps:ps-compile-file filename)))
106
107 98 ;;; CSS
108 99
109 100 (defmethod css-sources ((compiler compiler))
110 101 (format nil "~{~A~^~%~%~}" (css compiler)))
111 102
112 103 ;;; HTML
113 104
114 105 (defmethod html-sources ((compiler compiler))
115 106 (let ((flute:*escape-html* nil)
116 107 (body-template (body compiler))
117 108 (js (js-sources compiler))
118 109 (css (css-sources compiler)))
119 110 (with-output-to-string (out)
120 111 (write
121 112 (flute:h
122 113 (html
123 114 (head
124 115 (title "SugarQSP"))
125 116 (body
126 117 body-template
127 118 (style css)
128 119 (script (preprocess-js js (beautify compiler))))))
129 120 :stream out
130 121 :pretty nil))))
131 122
132 (defclass compiler ()
133 ((body :accessor body :initform #.(alexandria:read-file-into-string (src-file "extras/body.html")))
134 (css :accessor css :initform (list #.(alexandria:read-file-into-string (src-file "extras/default.css"))))
135 (js :accessor js :initform (list #.(compile-ps (src-file "src/intrinsics.ps"))
136 #.(compile-ps (src-file "src/api.ps"))
137 #.(compile-ps (src-file "src/main.ps"))))
138 (compile :accessor compile-only :initarg :compile)
139 (target :accessor target :initarg :target)
140 (beautify :accessor beautify :initarg :beautify)))
141
142 123 (defmethod initialize-instance ((compiler compiler) &key source ((:js js-files)) ((:css css-files)) ((:body body-file)) compile &allow-other-keys)
143 124 (call-next-method)
144 125 (with-slots (body css js)
145 126 compiler
146 127 ;; Compile the game's JS
147 128 (push (make-javascript (parse-file source)) js)
148 129 ;; Does the user need us to do anything else
149 130 (unless compile
150 131 ;; Read in body
151 132 (when body-file
152 133 (setf body
153 134 (alexandria:read-file-into-string body-file)))
154 135 ;; Include js files
155 136 (dolist (js-file js-files)
156 137 (push (format nil "////// Included file ~A~%~A" js-file
157 138 (alexandria:read-file-into-string js-file))
158 139 js))
159 140 ;; Include css files
160 141 (dolist (css-file css-files)
161 142 (push (format nil "////// Included file ~A~%~A" css-file
162 143 (alexandria:read-file-into-string css-file))
163 144 css)))))
164 145
165 146 (defmethod write-compiled-file ((compiler compiler))
166 147 (alexandria:write-string-into-file
167 148 (if (compile-only compiler)
168 149 ;; Just the JS
169 150 (preprocess-js (js-sources compiler) (beautify compiler))
170 151 ;; All of it
171 152 (html-sources compiler))
172 153 (target compiler) :if-exists :supersede))
@@ -1,15 +1,21 b''
1 1
2 2 (in-package sugar-qsp)
3 3
4 4 (setf (root)
5 5 (ps:create vars (ps:create)
6 6 objs (list)
7 7 state-stash (ps:create)
8 8 acts (ps:create)
9 locations (ps:create)))
9 locs (ps:create)))
10 10
11 ;; Launch the game from the first location
11 12 (setf window.onload
12 13 (lambda ()
13 (funcall (ps:getprop (root locations)
14 (ps:chain *object (keys (root locations)) 0)))
14 (funcall (ps:getprop (root locs)
15 (ps:chain *object (keys (root locs)) 0)))
15 16 (values)))
17
18 ;; Close the dropdown on any click
19 (setf window.onclick
20 (lambda (event)
21 (setf (ps:@ (api-call get-frame :dropdown) style display) "none")))
@@ -1,571 +1,571 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 $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 qsp-keyword-p (id)
109 109 (member (intern (string-upcase id)) *keywords*))
110 110
111 111 (defun not-qsp-keyword-p (id)
112 112 (not (member (intern (string-upcase id)) *keywords*)))
113 113
114 114 (p:defrule qsp-keyword (qsp-keyword-p identifier-raw))
115 115
116 116 (p:defrule id-first (id-any-char character))
117 117 (p:defrule id-next (or (id-any-char character)
118 118 (digit-char-p character)))
119 119 (p:defrule identifier-raw (and id-first (* id-next))
120 120 (:lambda (list)
121 121 (let ((id (p:text list)))
122 122 (when (member id *keywords*)
123 123 (error "~A is a keyword" id))
124 124 (intern (string-upcase id)))))
125 125
126 126 (p:defrule identifier (not-qsp-keyword-p identifier-raw))
127 127
128 128 ;;; Strings
129 129
130 130 (p:defrule qsp-string (or normal-string brace-string))
131 131
132 132 (p:defrule normal-string (or sstring dstring)
133 133 (:lambda (str)
134 134 (list* 'str (or str (list "")))))
135 135
136 136 (p:defrule sstring (and #\' (* (or string-interpol
137 137 sstring-exec
138 138 sstring-chars))
139 139 #\')
140 140 (:function second))
141 141
142 142 (p:defrule dstring (and #\" (* (or string-interpol
143 143 dstring-exec
144 144 dstring-chars))
145 145 #\")
146 146 (:function second))
147 147
148 148 (p:defrule string-interpol (and "<<" expression ">>")
149 149 (:function second))
150 150
151 151 (p:defrule sstring-exec-body (+ (or squote-esc (not-doublequote character)))
152 152 (:text t))
153 153
154 154 (p:defrule dstring-exec-body (+ (or dquote-esc (not-quote character)))
155 155 (:text t))
156 156
157 157 (p:defrule sstring-exec (and (p:~ "\"exec:") sstring-exec-body #\")
158 158 (:lambda (list)
159 159 (list* 'exec (p:parse 'exec-body (second list)))))
160 160
161 161 (p:defrule dstring-exec (and (p:~ "'exec:") dstring-exec-body #\')
162 162 (:lambda (list)
163 163 (list* 'exec (p:parse 'exec-body (second list)))))
164 164
165 165 (p:defrule brace-string (and #\{ before-statement block-body #\})
166 166 (:lambda (list)
167 167 (list* 'qspblock (third list))))
168 168
169 169 ;;; Location
170 170
171 171 (p:defrule sugar-qsp-grammar (and (* (or spaces #\newline))
172 172 (* location))
173 173 (:function second))
174 174
175 175 (p:defrule location (and location-header block-body location-end)
176 176 (:destructure (header body end)
177 177 (declare (ignore end))
178 178 `(location (,header) ,@body)))
179 179
180 180 (p:defrule location-header (and #\#
181 181 (+ not-newline)
182 182 (and #\newline spaces? before-statement))
183 183 (:destructure (spaces1 name spaces2)
184 184 (declare (ignore spaces1 spaces2))
185 185 (string-upcase (string-trim " " (p:text name)))))
186 186
187 (p:defrule location-end (and #\- #\newline before-statement)
187 (p:defrule location-end (and #\- (* not-newline) #\newline before-statement)
188 188 (:constant nil))
189 189
190 190 ;;; Block body
191 191
192 192 (p:defrule newline-block-body (and #\newline spaces? block-body)
193 193 (:function third))
194 194
195 195 (p:defrule block-body (* statement)
196 196 (:function remove-nil))
197 197
198 198 ;; Just for <a href="exec:...'>
199 199 ;; Explicitly called from that rule's production
200 200 (p:defrule exec-body (and before-statement line-body)
201 201 (:function second))
202 202
203 203 (p:defrule line-body (and inline-statement (* next-inline-statement))
204 204 (:lambda (list)
205 205 (list* (first list) (second list))))
206 206
207 207 (p:defrule before-statement (* (or #\newline spaces))
208 208 (:constant nil))
209 209
210 210 (p:defrule statement-end (or statement-end-real statement-end-block-close))
211 211
212 212 (p:defrule statement-end-real (and (or #\newline
213 213 (and #\& spaces? (p:& statement%)))
214 214 before-statement)
215 215 (:constant nil))
216 216
217 217 (p:defrule statement-end-block-close (or (p:& #\}))
218 218 (:constant nil))
219 219
220 220 (p:defrule inline-statement (and statement% spaces?)
221 221 (:function first))
222 222
223 223 (p:defrule next-inline-statement (and #\& spaces? inline-statement)
224 224 (:function third))
225 225
226 226 (p:defrule not-a-non-statement (and (p:! (p:~ "elseif"))
227 227 (p:! (p:~ "else"))
228 228 (p:! (p:~ "end"))))
229 229
230 230 (p:defrule statement (and inline-statement statement-end)
231 231 (:function first))
232 232
233 233 (p:defrule statement% (and not-a-non-statement
234 234 (or label comment string-output
235 235 block non-returning-intrinsic assignment
236 236 expression-output))
237 237 (:function second))
238 238
239 239 (p:defrule expr-stopper (or comment block non-returning-intrinsic))
240 240
241 241 (p:defrule string-output qsp-string
242 242 (:lambda (string)
243 243 (list 'main-pl string)))
244 244
245 245 (p:defrule expression-output expression
246 246 (:lambda (list)
247 247 (list 'main-pl list)))
248 248
249 249 (p:defrule label (and colon identifier)
250 250 (:lambda (list)
251 251 (intern (string (second list)) :keyword)))
252 252
253 253 (p:defrule comment (and #\! (* (or text-spaces qsp-string brace-string not-newline)))
254 254 (:constant nil))
255 255
256 256 ;;; Blocks
257 257
258 258 (p:defrule block (or block-act block-if))
259 259
260 260 (p:defrule block-if (and block-if-head block-if-body)
261 261 (:destructure (head body)
262 262 `(qspcond (,@head ,@(first body))
263 263 ,@(rest body))))
264 264
265 265 (p:defrule block-if-head (and (p:~ "if") spaces expression spaces? colon spaces?)
266 266 (:function remove-nil)
267 267 (:function cdr))
268 268
269 269 (p:defrule block-if-body (or block-if-ml block-if-sl)
270 270 (:destructure (if-body elseifs else &rest ws)
271 271 (declare (ignore ws))
272 272 `(,(remove-nil if-body) ,@elseifs ,@(when else `((else ,@else))))))
273 273
274 274 (p:defrule block-if-sl (and line-body
275 275 (p:? block-if-elseif-inline)
276 276 (p:? block-if-else-inline)
277 277 spaces?))
278 278
279 279 (p:defrule block-if-ml (and (and #\newline spaces?)
280 280 block-body
281 281 (p:? block-if-elseif)
282 282 (p:? block-if-else)
283 283 block-if-end)
284 284 (:lambda (list)
285 285 (cdr list)))
286 286
287 287 (p:defrule block-if-elseif-inline (and block-if-elseif-head line-body (p:? block-if-elseif-inline))
288 288 (:destructure (head statements elseif)
289 289 `((,@(rest head) ,@(remove-nil statements)) ,@elseif)))
290 290
291 291 (p:defrule block-if-elseif (and block-if-elseif-head statement-end block-body (p:? block-if-elseif))
292 292 (:destructure (head ws statements elseif)
293 293 (declare (ignore ws))
294 294 `((,@(rest head) ,@(remove-nil statements)) ,@elseif)))
295 295
296 296 (p:defrule block-if-elseif-head (and (p:~ "elseif") spaces expression spaces? colon spaces?)
297 297 (:function remove-nil)
298 298 (:function intern-first))
299 299
300 300 (p:defrule block-if-else-inline (and block-if-else-head line-body)
301 301 (:function second))
302 302
303 303 (p:defrule block-if-else (and block-if-else-head #\newline spaces? block-body)
304 304 (:function fourth))
305 305
306 306 (p:defrule block-if-else-head (and (p:~ "else") spaces?)
307 307 (:constant nil))
308 308
309 309 (p:defrule block-if-end (and (p:~ "end")
310 310 (p:? (and spaces (p:~ "if"))))
311 311 (:constant nil))
312 312
313 313 (p:defrule block-act (and block-act-head (or block-act-ml block-act-sl))
314 314 (:lambda (list)
315 315 (apply #'append list)))
316 316
317 317 (p:defrule block-act-sl line-body)
318 318
319 319 (p:defrule block-act-ml (and newline-block-body block-act-end)
320 320 (:lambda (list)
321 321 (apply #'list* (butlast list))))
322 322
323 323 (p:defrule block-act-head (and (p:~ "act") spaces qsp-string spaces?
324 324 (p:? block-act-head-img)
325 325 colon spaces?)
326 326 (:lambda (list)
327 327 (intern-first (list (first list)
328 328 (third list)
329 329 (or (fifth list) '(str ""))))))
330 330
331 331 (p:defrule block-act-head-img (and #\, spaces? qsp-string spaces?)
332 332 (:lambda (list)
333 333 (or (third list) "")))
334 334
335 335 (p:defrule block-act-end (and (p:~ "end"))
336 336 (:constant nil))
337 337
338 338 ;;; Calls
339 339
340 340 (p:defrule first-argument (and expression spaces?)
341 341 (:function first))
342 342 (p:defrule next-argument (and "," spaces? expression)
343 343 (:function third))
344 344 (p:defrule arguments (or parenthesized-arguments plain-arguments no-arguments))
345 345 (p:defrule parenthesized-arguments (and spaces? #\( base-arguments #\))
346 346 (:function third))
347 347 (p:defrule plain-arguments (and spaces base-arguments)
348 348 (:function second))
349 349 (p:defrule no-arguments (or spaces (p:& #\newline) (p:& #\&))
350 350 (:constant nil))
351 351 (p:defrule base-arguments (and first-argument (* next-argument))
352 352 (:destructure (first rest)
353 353 (list* first rest)))
354 354
355 355 ;;; Intrinsics
356 356
357 357 (defmacro defintrinsics ((rule-name returning-rule-name non-returning-rule-name) &body clauses)
358 358 `(progn
359 359 ,@(loop :for clause :in clauses
360 360 :collect `(defintrinsic ,@clause))
361 361 (p:defrule ,returning-rule-name (or ,@(remove-nil
362 362 (mapcar (lambda (clause)
363 363 (when (second clause)
364 364 (alexandria:symbolicate
365 365 'intrinsic- (first clause))))
366 366 clauses))))
367 367 (p:defrule ,non-returning-rule-name (or ,@(remove-nil
368 368 (mapcar (lambda (clause)
369 369 (unless (second clause)
370 370 (alexandria:symbolicate
371 371 'intrinsic- (first clause))))
372 372 clauses))))
373 373 (p:defrule ,rule-name (or ,returning-rule-name ,non-returning-rule-name))))
374 374
375 375 (defmacro defintrinsic (sym returning &optional (min-arity 0) (max-arity 10) &rest names)
376 376 (declare (ignore returning))
377 377 (setf names
378 378 (if names
379 379 (mapcar #'string-upcase names)
380 380 (list (string sym))))
381 381 `(p:defrule ,(alexandria:symbolicate 'intrinsic- sym)
382 382 (and (p:? #\$) (or ,@(loop :for name :in names :collect `(p:~ ,name)))
383 383 arguments)
384 384 (:destructure (dollar name arguments)
385 385 (declare (ignore dollar))
386 386 (unless (<= ,min-arity (length arguments) ,max-arity)
387 387 (error "Intrinsic ~A expects between ~A and ~A arguments but ~A were provided:~%~S"
388 388 name ,min-arity ,max-arity (length arguments) arguments))
389 389 (list* ',sym arguments))))
390 390
391 391 (defintrinsics (intrinsic returning-intrinsic non-returning-intrinsic)
392 392 ;; Transitions
393 393 (goto nil 0 10 "gt" "goto")
394 394 (xgoto nil 0 10 "xgt" "xgoto")
395 395 ;; Variables
396 396 (killvar nil 0 2)
397 397 ;; Expressions
398 398 (obj t 1 1)
399 399 (loc t 1 1)
400 400 (no t 1 1)
401 401 ;; Basic
402 402 (qspver t 0 0)
403 403 (curloc t 0 0)
404 404 (rand t 1 2)
405 405 (rnd t 0 0)
406 406 (qspmax t 1 10 "max")
407 407 (qspmin t 1 10 "min")
408 408 ;; Arrays
409 409 (killall nil 0 0)
410 410 (copyarr nil 2 4)
411 411 (arrsize t 1 1)
412 412 (arrpos t 2 3)
413 413 (arrcomp t 2 3)
414 414 ;; Strings
415 415 (len t 1 1)
416 416 (mid t 2 3)
417 417 (ucase t 1 1)
418 418 (lcase t 1 1)
419 419 (trim t 1 1)
420 420 (replace t 2 3)
421 421 (instr t 2 3)
422 422 (isnum t 1 1)
423 423 (val t 1 1)
424 424 (qspstr t 1 1 "str")
425 425 (strcomp t 2 2)
426 426 (strfind t 2 3)
427 427 (strpos t 2 3)
428 428 ;; IF
429 429 (iif t 2 3)
430 430 ;; Subs
431 431 (gosub nil 1 10 "gosub" "gs")
432 432 (func t 1 10)
433 433 (exit nil 0 0)
434 434 ;; Jump
435 435 (jump nil 1 1)
436 436 ;; Dynamic
437 437 (dynamic nil 1 10)
438 438 (dyneval t 1 10)
439 439 ;; Main window
440 440 (main-p nil 1 1 "*p")
441 441 (main-pl nil 1 1 "*pl")
442 442 (main-nl nil 0 1 "*nl")
443 443 (maintxt t 0 0)
444 444 (desc t 1 1)
445 445 (main-clear nil 0 0 "*clear" "*clr")
446 446 ;; Aux window
447 447 (showstat nil 1 1)
448 448 (stat-p nil 1 1 "p")
449 449 (stat-pl nil 1 1 "pl")
450 450 (stat-nl nil 0 1 "nl")
451 451 (stattxt t 0 0)
452 452 (stat-clear nil 0 0 "clear" "clr")
453 453 (cls nil 0 0)
454 454 ;; Dialog
455 455 (msg nil 1 1)
456 456 ;; Acts
457 457 (showacts nil 1 1)
458 458 (delact nil 1 1 "delact" "del act")
459 459 (curacts t 0 0)
460 460 (cla nil 0 0)
461 461 ;; Objects
462 462 (showobjs nil 1 1)
463 463 (addobj nil 1 3 "addobj" "add obj")
464 464 (delobj nil 1 1 "delobj" "del obj")
465 465 (killobj nil 0 1)
466 466 (countobj t 0 0)
467 467 (getobj t 1 1)
468 468 ;; Menu
469 469 (menu nil 1 1)
470 470 ;; Sound
471 471 (play nil 1 2)
472 472 (isplay t 1 1)
473 473 (close nil 1 1)
474 474 (closeall nil 0 0 "close all")
475 475 ;; Images
476 476 (refint nil 0 0)
477 477 (view nil 0 1)
478 478 ;; Fonts
479 479 (rgb t 3 3)
480 480 ;; Input
481 481 (showinput nil 1 1)
482 482 (usertxt t 0 0 "user_text" "usrtxt")
483 483 (cmdclear nil 0 0 "cmdclear" "cmdclr")
484 484 (input t 1 1)
485 485 ;; Files
486 486 (openqst nil 1 1)
487 487 (addqst nil 1 1 "addqst" "addlib" "inclib")
488 488 (killqst nil 1 1 "killqst" "dellib" "freelib")
489 (opengame nil 0 1)
490 (savegame nil 0 1)
489 (opengame nil 0 0)
490 (savegame nil 0 0)
491 491 ;; Real time
492 492 (wait nil 1 1)
493 493 (msecscount t 0 0)
494 494 (settimer nil 1 1))
495 495
496 496 ;;; Expression
497 497
498 498 (p:defrule expression or-expr)
499 499
500 500 (p:defrule or-expr (and and-expr (* (and spaces? or-op spaces? and-expr)))
501 501 (:function do-binop))
502 502
503 503 (p:defrule and-expr (and eq-expr (* (and spaces? and-op spaces? eq-expr)))
504 504 (:function do-binop))
505 505
506 506 (p:defrule eq-expr (and cat-expr (* (and spaces? (or "<>" "<=" ">=" "=<" "=>"
507 507 #\= #\< #\> #\!)
508 508 spaces? cat-expr)))
509 509 (:function do-binop))
510 510
511 511 (p:defrule cat-expr (and sum-expr (* (and spaces? #\& spaces? (p:! expr-stopper) sum-expr)))
512 512 (:lambda (list)
513 513 (do-binop (list (first list) (mapcar (lambda (l)
514 514 (remove-nth l 3))
515 515 (second list))))))
516 516
517 517 (p:defrule sum-expr (and mod-expr (* (and spaces? (or #\+ #\-) spaces? mod-expr)))
518 518 (:function do-binop))
519 519
520 520 (p:defrule mod-expr (and mul-expr (* (and spaces? (p:~ "mod") spaces? mul-expr)))
521 521 (:function do-binop))
522 522
523 523 (p:defrule mul-expr (and unary-expr (* (and spaces? (or #\* #\/) spaces? unary-expr)))
524 524 (:function do-binop))
525 525
526 526 (p:defrule unary-expr (and (p:? (or #\+ #\-)) atom-expr)
527 527 (:lambda (list)
528 528 (let ((expr (remove-nil list)))
529 529 (if (= 1 (length expr))
530 530 (first expr)
531 531 (intern-first expr)))))
532 532
533 533 (p:defrule atom-expr (and (or variable literal returning-intrinsic paren-expr) spaces?)
534 534 (:function first))
535 535
536 536 (p:defrule paren-expr (and #\( spaces? expression spaces? #\))
537 537 (:function third))
538 538
539 539 (p:defrule or-op (p:~ "or")
540 540 (:constant "or"))
541 541
542 542 (p:defrule and-op (p:~ "and")
543 543 (:constant "and"))
544 544
545 545 ;;; Variables
546 546
547 547 (p:defrule variable (and identifier (p:? array-index))
548 548 (:destructure (id idx)
549 549 (list 'var id (or idx 0))))
550 550
551 551 (p:defrule array-index (and #\[ spaces? (p:? expression) spaces? #\])
552 552 (:lambda (list)
553 553 (or (third list) :end)))
554 554
555 555 (p:defrule assignment (or kw-assignment plain-assignment)
556 556 (:destructure (var eq expr)
557 557 (declare (ignore eq))
558 558 (list 'set var expr)))
559 559
560 560 (p:defrule plain-assignment (and variable spaces? #\= spaces? expression)
561 561 (:function remove-nil))
562 562 (p:defrule kw-assignment (and (or (p:~ "let") (p:~ "set")) spaces? plain-assignment)
563 563 (:function third))
564 564
565 565 ;;; Non-string literals
566 566
567 567 (p:defrule literal (or qsp-string brace-string number))
568 568
569 569 (p:defrule number (+ (or #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
570 570 (:lambda (list)
571 571 (parse-integer (p:text list))))
@@ -1,192 +1,188 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 19 (ps:defpsmacro conserving-vars (vars &body body)
20 20 "Calls body with safely stored away VARS (whole arrays, both namespaces), and restores their values after that returning what BODY returns."
21 21 `(let ((__conserved (list ,@(loop :for var :in vars
22 22 :collect `(root vars ,var)))))
23 23 ,@(loop :for var :in vars
24 24 :collect `(setf (root vars ,var) (ps:create :num 0 :str "")))
25 25 (unwind-protect
26 26 (progn ,@body)
27 27 (progn
28 28 ,@(loop :for var :in vars
29 29 :for i from 0
30 30 :collect `(setf (root vars ,var) (ps:@ __conserved ,i)))))))
31 31
32 32 ;;; Common
33 33
34 34 (defmacro defpsintrinsic (name)
35 35 `(ps:defpsmacro ,name (&rest args)
36 36 `(funcall (root lib ,',name)
37 37 ,@args)))
38 38
39 39 (defmacro defpsintrinsics (() &rest names)
40 40 `(progn ,@(loop :for name :in names
41 41 :collect `(defpsintrinsic ,name))))
42 42
43 43 (defpsintrinsics ()
44 killvar obj loc no qspver curloc rand rnd qspmax qspmin killall copyarr arrsize arrpos arrcomp len mid ucase lcase trim replace instr isnum val qspstr strcomp strfind strpos iif gosub func dynamic dyneval main-p main-pl main-nl maintxt main-clear showstat stat-p stat-pl stat-nl stattxt stat-clear cls msg showacts delact curacts cla showobjs addobj delobj killobj countobj getobj menu play isplay close closeall refint view rgb showinput usertxt cmdclear input openqst addqst killqst opengame savegame wait msecscount settimer)
44 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)
45 45
46 46 (ps:defpsmacro api-call (func &rest args)
47 47 `(funcall (root api ,func) ,@args))
48 48
49 49 (ps:defpsmacro label-block (&body body)
50 50 `(block nil
51 51 ,@(when (some #'keywordp body)
52 52 '((defvar __labels)))
53 53 (tagbody
54 54 ,@body)
55 55 (values)))
56 56
57 57 (ps:defpsmacro str (&rest forms)
58 58 (cond ((zerop (length forms))
59 59 "")
60 60 ((and (= 1 (length forms))
61 61 (stringp (first forms)))
62 62 (first forms))
63 63 (t
64 64 `(& ,@forms))))
65 65
66 66 ;;; 1loc
67 67
68 68 (ps:defpsmacro location ((name) &body body)
69 `(setf (root locations ,name)
69 `(setf (root locs ,name)
70 70 (lambda ()
71 71 (label-block
72 ,@body
73 (api-call update-acts)))))
72 ,@body))))
74 73
75 74 (ps:defpsmacro goto (target &rest args)
76 75 `(progn
77 76 (funcall (root lib goto) ,target ,@args)
78 77 (exit)))
79 78
80 79 (ps:defpsmacro xgoto (target &rest args)
81 80 `(progn
82 81 (funcall (root lib xgoto) ,target ,@args)
83 82 (exit)))
84 83
85 84 (ps:defpsmacro desc (target)
86 85 (declare (ignore target))
87 86 (report-error "DESC is not supported"))
88 87
89 88 ;;; 2var
90 89
91 90 (ps:defpsmacro var (name index)
92 91 `(api-call get-var ,(string name) ,index))
93 92
94 93 (ps:defpsmacro set ((var vname vindex) value)
95 94 (assert (eq var 'var))
96 95 `(api-call set-var ,(string vname) ,vindex ,value))
97 96
98 97 ;;; 3expr
99 98
100 99 (ps:defpsmacro <> (op1 op2)
101 100 `(not (equal ,op1 ,op2)))
102 101
103 102 (ps:defpsmacro ! (op1 op2)
104 103 `(not (equal ,op1 ,op2)))
105 104
106 105 ;;; 4code
107 106
108 107 (ps:defpsmacro exec (&body body)
109 108 (format nil "javascript: ~{~A~^~%~}" (mapcar #'ps:ps* body)))
110 109
111 110 ;;; 5arrays
112 111
113 112 ;;; 6str
114 113
115 114 (ps:defpsmacro & (&rest args)
116 115 `(ps:chain "" (concat ,@args)))
117 116
118 117 ;;; 7if
119 118
120 119 (ps:defpsmacro qspcond (&rest clauses)
121 120 `(cond ,@(loop :for clause :in clauses
122 121 :collect (list (first clause)
123 122 `(tagbody ,@(rest clause))))))
124 123
125 124 ;;; 8sub
126 125
127 126 ;;; 9loops
128 127 ;;; Yep, that's a huge kludge since Javascript doesn't support arbitrary labels
129 128
130 129 (ps:defpsmacro jump (target)
131 130 `(return-from ,(intern (string-upcase (second target)))
132 131 (funcall (ps:getprop __labels ,target))))
133 132
134 133 (ps:defpsmacro tagbody (&body body)
135 134 (let ((funcs (list nil :__nil)))
136 135 (dolist (form body)
137 136 (cond ((keywordp form)
138 137 (setf (first funcs) (reverse (first funcs)))
139 138 (push form funcs)
140 139 (push nil funcs))
141 140 (t
142 141 (push form (first funcs)))))
143 142 (setf (first funcs) (reverse (first funcs)))
144 143 (setf funcs (reverse funcs))
145 144 (if (= 2 (length funcs))
146 145 `(progn
147 146 ,@body)
148 147 `(progn
149 148 (setf ,@(loop :for f :on funcs :by #'cddr
150 149 :append (list `(ps:@ __labels ,(first f))
151 150 `(block ,(intern (string-upcase (string (first f))))
152 151 ,@(second f)
153 152 ,@(when (third f)
154 153 `((funcall
155 154 (ps:getprop __labels ,(third f)))))))))
156 155 (jump (str "__nil"))))))
157 156
158 (ps:defpsmacro exit ()
159 `(return-from nil (values)))
160
161 157 ;;; 10dynamic
162 158
163 159 (ps:defpsmacro qspblock (&body body)
164 160 `(lambda ()
165 161 (label-block
166 162 ,@body)))
167 163
168 164 ;;; 11main
169 165
170 166 (ps:defpsmacro act (name img &body body)
171 167 `(api-call add-act ,name ,img
172 168 (lambda ()
173 169 (label-block
174 170 ,@body))))
175 171
176 172 ;;; 12aux
177 173
178 174 ;;; 13diag
179 175
180 176 ;;; 14act
181 177
182 178 ;;; 15objs
183 179
184 180 ;;; 16menu
185 181
186 182 ;;; 17sound
187 183
188 184 ;;; 18img
189 185
190 186 ;;; 19input
191 187
192 188 ;;; 20time
@@ -1,11 +1,13 b''
1 1
2 2 (defsystem sugar-qsp
3 3 :description "QSP compiler to monolithic HTML page"
4 4 :depends-on (:alexandria :esrap
5 5 :parenscript :parse-js :cl-uglify-js :flute)
6 6 :pathname "src/"
7 7 :serial t
8 8 :components ((:file "package")
9 9 (:file "ps-macros")
10 (:file "intrinsic-macros")
11 (:file "class")
10 12 (:file "main")
11 13 (:file "parser")))
General Comments 0
You need to be logged in to leave comments. Login now