##// END OF EJS Templates
Localization. Renamed to txt2web
naryl -
r46:b7997ef3 default
parent child Browse files
Show More
@@ -0,0 +1,24 b''
1
2 (defsystem qsp-txt2web
3 :description "QSP compiler to monolithic HTML page"
4 :depends-on (:alexandria :system-locale ;; General
5 :esrap ;; Parsing
6 :parenscript :flute ;; Codegening
7 )
8 :pathname "src/"
9 :serial t
10 :components ((:file "package")
11 (:file "utils")
12 (:file "l10n")
13 (:file "walker")
14
15 (:file "patches")
16 (:file "js-syms")
17 (:file "main-macros")
18 (:file "ps-macros")
19 (:file "api-macros")
20 (:file "intrinsic-macros")
21
22 (:file "class")
23 (:file "main")
24 (:file "parser")))
@@ -0,0 +1,19 b''
1
2 (in-package txt2web)
3
4 (defparameter *languages* (list "en" "ru"))
5
6 (defparameter *l10n-strings*
7 (mapcan (lambda (lang)
8 (cons (intern (string-upcase lang) :keyword)
9 (read-code-from-string
10 (load-src
11 (concatenate 'string "strings/" lang ".sexp")))))
12 *languages*))
13
14 (defun lformat (target key &rest args)
15 (let* ((lang (intern (string-upcase (first (system-locale:languages)))
16 :keyword))
17 (strings (or (getf *l10n-strings* lang)
18 (getf *l10n-strings* :en))))
19 (apply #'format target (getf strings key) args)))
@@ -0,0 +1,24 b''
1
2 (in-package txt2web)
3
4 (defun src-file (filename)
5 (uiop/pathname:merge-pathnames*
6 filename
7 (asdf:system-source-directory :txt2web)))
8
9 (defun read-progn-from-string (string)
10 `(progn
11 ,@(read-code-from-string string)))
12
13 (defun read-code-from-string (string)
14 (with-input-from-string (in string)
15 (let ((*package* *package*))
16 (loop :for form := (read in nil :eof)
17 :until (eq form :eof)
18 :when (eq (first form) 'cl:in-package)
19 :do (setf *package* (find-package (second form)))
20 :else
21 :collect form))))
22
23 (defun load-src (filename)
24 (alexandria:read-file-into-string (src-file filename)))
@@ -0,0 +1,12 b''
1 (:usage "Usage: ~A <source> [options]
2 Options:
3 -o <filename> - Output filename
4 --js <filenames...> - List of extra .js files to include in the game
5 --css <filenames...> - List of .css files to include in the game. Default is in extras/default.css
6 --body <filename> - Alternative page body. Default is in extras/body.html
7
8 -c - Just compile the game to a .js file without making it a full web page
9 --beautify - Make the JS content pretty. By default it gets minified.
10
11 Note that the files in extras/ are not actually used. They're just there for the reference~%")
12
@@ -0,0 +1,12 b''
1 (:usage "Использование: ~A <source> [options]
2 Опции:
3 -o <имя файла> - Имя .html файла для записи скомпилированной игры
4 --js <имена файлов...> - Список дополнительных .js файлов
5 --css <имена файлов...> - Список .css файлов. Стиль по-умолчанию - в файле extras/default.css
6 --body <имя файла> - Альтернативное тело страницы. Тело по-умолчанию - в файле extras/body.html
7
8 -c - Просто скомпилировать игру в .js файл, не компонуя полную .html страницу
9 --beautify - Не минифицировать .js скрипты
10
11 Файлы в extras на самом деле компилятором не используются. Используйте только как образец.~%")
12
@@ -1,39 +1,46 b''
1 1
2 BIN = sugar-qsp
2 BIN = txt2web
3 PKG = $(BIN)
4 DIST = txt2web.tar.xz
3 5
4 6 LISP = sbcl
5 7
6 8 all: $(BIN)
7 9
10 dist: $(DIST)
11
8 12 graphs: diagrams.png
9 13
10 $(BIN): src/*.lisp src/*.ps
14 $(BIN): *.asd src/*.lisp src/*.ps strings/*.sexp
11 15 buildapp.$(LISP) --asdf-path .\
12 16 --asdf-tree .qlot/dists\
13 --load-system sugar-qsp\
14 --entry sugar-qsp:entry-point\
15 --compress-core\
17 --load-system $(PKG)\
18 --entry $(PKG):entry-point\
16 19 --output $(BIN)
17 20
18 21 install-deps:
19 22 sbcl --load install-deps.lisp
20 23
21 24 update-deps:
22 25 sbcl --load update-deps.lisp
23 26
24 27 %.png: %.dot
25 28 dot $< -T png -o $@
26 29
27 dist: $(BIN)
28 tar cfvJ sugar-qsp.tar.xz $(BIN) extras
30 $(DIST): $(BIN) extras/*
31 tar cfvJ $@ $< extras
32
33 upload: $(DIST)
34 curl --upload-file $(DIST) https://transfer.sh/$(DIST)
35 @echo
29 36
30 37 distclean: clean clean-deps
31 38
32 39 clean:
33 -rm sugar-qsp
40 rm -f $(BIN) $(DIST)
34 41
35 42 clean-deps:
36 -rm qlfile.lock
37 -rm -rf .qlot
43 rm qlfile.lock
44 rm -rf .qlot
38 45
39 .PHONY: all graphs install-deps update-deps clean
46 .PHONY: all graphs install-deps update-deps clean upload
@@ -1,44 +1,44 b''
1 1 (инструкции на Русском - внизу)
2 2
3 # sugar-qsp
3 # txt2web
4 4 Compiler for QSP games which creates monolithic HTML pages.
5 5
6 6 ## Usage
7 7
8 8 There are three mastery levels
9 9
10 10 1. Just build me the game:<br/>
11 `sugar-qsp game.txt`<br/>
11 `txt2web game.txt`<br/>
12 12 And it will create the game in game.html
13 13
14 14 2. I know what I'm doing:<br/>
15 `sugar-qsp game.txt -o game.html --body body.html --js jquery.js my-js-library.js --css styles/*.css`<br/>
15 `txt2web game.txt -o game.html --body body.html --js jquery.js my-js-library.js --css styles/*.css`<br/>
16 16 All options are self-explanatory. The result is a monolithic html specified
17 17 with the `-o` option. Default `body.html` (used by the first mastery level) can
18 18 be found in `extas` directory.
19 19
20 20 3. I'm a frontend developer!<br/>
21 `sugar-qsp game.txt -c -o game.js`<br/>
21 `txt2web game.txt -c -o game.js`<br/>
22 22 It just builds the game script into a js you can put on your website. To run
23 23 the game execute `SugarQSP.start()`
24 24
25 # sugar-qsp
25 # txt2web
26 26 Компилятор для игр на QSP создающий монолитные страницы на HTML.
27 27
28 28 ## Инструкции
29 29
30 30 Есть три уровня мастерства.
31 31
32 32 1. **Просто собери мне игру**:<br/>
33 `sugar-qsp game.txt`<br/>
33 `txt2web game.txt`<br/>
34 34 Создаст игру в game.html
35 35
36 36 2. **Я знаю что делаю**:<br/>
37 `sugar-qsp game.txt -o game.html --body body.html --js jquery.js my-js-library.js --css styles/*.css`<br/>
37 `txt2web game.txt -o game.html --body body.html --js jquery.js my-js-library.js --css styles/*.css`<br/>
38 38 Если вы знаете что делаете, то для вас смысл опций очевиден. `body.html` и `default.css`
39 39 лежат в каталоге `extras`.
40 40
41 41 3. **Я - фронтендер!**<br/>
42 `sugar-qsp game.txt -c -o game.js`<br/>
42 `txt2web game.txt -c -o game.js`<br/>
43 43 Просто соберёт игру в Javascript файл который вы можете разместить на своём
44 44 сайте как вам угодно.
@@ -1,15 +1,17 b''
1 1
2 * Localization
2 3 * Save-load game in slots
3 4
4 5 * CLI build for Windows
5 6
6 7 * Reporting error lines in the parser
7 8 * Report duplicate label (in the parser)
8 9 * reporting error lines at runtime (by storing them in every form in the parser
9 10 * Report JUMP with missing label (in tagbody)
11 * Localizing parser errors...
10 12
11 13 * Build Istreblenie
12 14 * Build Цветохимия
13 15
14 16 * Windows GUI (for the compiler)
15 17 * Resizable frames
@@ -1,10 +1,13 b''
1 1 ql alexandria
2 ql system-locale
2 3 ql esrap
3 4 ql parenscript
4 5 ql flute
5 6
6 7 ql cl-ppcre
7 8 ql anaphora
8 9 ql named-readtables
9 10 ql assoc-utils
10 11 ql let-over-lambda
12 ql documentation-utils
13 ql trivial-indent
@@ -1,40 +1,52 b''
1 1 ("quicklisp" .
2 2 (:class qlot/source/dist:source-dist
3 3 :initargs (:distribution "http://beta.quicklisp.org/dist/quicklisp.txt" :%version :latest)
4 4 :version "2020-04-27"))
5 5 ("alexandria" .
6 6 (:class qlot/source/ql:source-ql
7 7 :initargs (:%version :latest)
8 8 :version "ql-2020-04-27"))
9 ("system-locale" .
10 (:class qlot/source/ql:source-ql
11 :initargs (:%version :latest)
12 :version "ql-2020-04-27"))
9 13 ("esrap" .
10 14 (:class qlot/source/ql:source-ql
11 15 :initargs (:%version :latest)
12 16 :version "ql-2020-04-27"))
13 17 ("parenscript" .
14 18 (:class qlot/source/ql:source-ql
15 19 :initargs (:%version :latest)
16 20 :version "ql-2020-04-27"))
17 21 ("flute" .
18 22 (:class qlot/source/ql:source-ql
19 23 :initargs (:%version :latest)
20 24 :version "ql-2020-04-27"))
21 25 ("cl-ppcre" .
22 26 (:class qlot/source/ql:source-ql
23 27 :initargs (:%version :latest)
24 28 :version "ql-2020-04-27"))
25 29 ("anaphora" .
26 30 (:class qlot/source/ql:source-ql
27 31 :initargs (:%version :latest)
28 32 :version "ql-2020-04-27"))
29 33 ("named-readtables" .
30 34 (:class qlot/source/ql:source-ql
31 35 :initargs (:%version :latest)
32 36 :version "ql-2020-04-27"))
33 37 ("assoc-utils" .
34 38 (:class qlot/source/ql:source-ql
35 39 :initargs (:%version :latest)
36 40 :version "ql-2020-04-27"))
37 41 ("let-over-lambda" .
38 42 (:class qlot/source/ql:source-ql
39 43 :initargs (:%version :latest)
40 44 :version "ql-2020-04-27"))
45 ("documentation-utils" .
46 (:class qlot/source/ql:source-ql
47 :initargs (:%version :latest)
48 :version "ql-2020-04-27"))
49 ("trivial-indent" .
50 (:class qlot/source/ql:source-ql
51 :initargs (:%version :latest)
52 :version "ql-2020-04-27"))
@@ -1,32 +1,14 b''
1 1
2 2 (in-package sugar-qsp)
3 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 read-code-from-string (string)
10 (with-input-from-string (in string)
11 (let ((*package* *package*))
12 `(progn
13 ,@(loop :for form := (read in nil :eof)
14 :until (eq form :eof)
15 :when (eq (first form) 'cl:in-package)
16 :do (setf *package* (find-package (second form)))
17 :else
18 :collect form)))))
19 (defun load-src (filename)
20 (alexandria:read-file-into-string (src-file filename))))
21
22 4 (defclass compiler ()
23 5 ((body :accessor body :initform #.(load-src "extras/body.html"))
24 6 (css :accessor css :initform (list #.(load-src "extras/default.css")))
25 7 (js :accessor js :initform (reverse
26 8 (list
27 '#.(read-code-from-string (load-src "src/main.ps"))
28 '#.(read-code-from-string (load-src "src/api.ps"))
29 '#.(read-code-from-string (load-src "src/intrinsics.ps")))))
9 '#.(read-progn-from-string (load-src "src/main.ps"))
10 '#.(read-progn-from-string (load-src "src/api.ps"))
11 '#.(read-progn-from-string (load-src "src/intrinsics.ps")))))
30 12 (compile :accessor compile-only :initarg :compile)
31 13 (target :accessor target :initarg :target)
32 14 (beautify :accessor beautify :initarg :beautify)))
@@ -1,8 +1,8 b''
1 1
2 (in-package sugar-qsp.main)
2 (in-package txt2web.main)
3 3
4 4 (defmacro+ps api-call (name &rest args)
5 `(,(intern (string-upcase name) "SUGAR-QSP.API") ,@args))
5 `(,(intern (string-upcase name) "TXT2WEB.API") ,@args))
6 6
7 7 (defpsmacro has (key obj)
8 8 `(chain ,obj (has-own-property ,key)))
@@ -1,163 +1,153 b''
1 1
2 (in-package sugar-qsp)
2 (in-package txt2web)
3 3
4 4 (defvar *app-name* "")
5 5
6 6 (defun entry-point-no-args ()
7 7 (entry-point uiop:*command-line-arguments*))
8 8
9 9 (defun entry-point (args)
10 10 (setf *app-name* (first args))
11 (let ((*package* (find-package :sugar-qsp)))
11 (let ((*package* (find-package :txt2web)))
12 12 (catch :terminate
13 13 (let ((compiler (apply #'make-instance 'compiler (parse-opts (rest args)))))
14 14 (write-compiled-file compiler))))
15 15 (values))
16 16
17 17 (defun parse-opts (args)
18 18 (let ((mode :sources)
19 19 (data (list :sources nil :target nil :js nil :css nil :body nil :compile nil :beautify nil)))
20 20 (loop :for arg :in args
21 21 :do (alexandria:switch (arg :test #'string=)
22 22 ("-o" (setf mode :target))
23 23 ("--js" (setf mode :js))
24 24 ("--css" (setf mode :css))
25 25 ("--body" (setf mode :body))
26 26 ("-c" (setf (getf data :compile) t))
27 27 ("--beautify" (setf (getf data :beautify) t))
28 28 (t (push arg (getf data mode)))))
29 29 (unless (< 0 (length (getf data :sources)))
30 30 (report-error "There should be at least one source"))
31 31 (unless (> 1 (length (getf data :target)))
32 32 (report-error "There should be no more than one target"))
33 33 (unless (> 1 (length (getf data :body)))
34 34 (report-error "There should be no more than one body"))
35 35 (unless (getf data :target)
36 36 (setf (getf data :target)
37 37 (let* ((sources (first (getf data :sources)))
38 38 (tokens (uiop:split-string sources :separator "."))
39 39 (target (format nil "~{~A~^.~}.html"
40 40 (butlast tokens))))
41 41 (list target))))
42 42 (list :sources (getf data :sources)
43 43 :target (first (getf data :target))
44 44 :js (getf data :js)
45 45 :css (getf data :css)
46 46 :body (first (getf data :body))
47 47 :compile (getf data :compile)
48 48 :beautify (getf data :beautify))))
49 49
50 50 (defun print-usage ()
51 (format t "Usage: ~A <source> [options]~%" *app-name*)
52 (format t "Options:~%")
53 (format t " -o <filename> - Output filename~%")
54 (format t " --js <filenames...> - List of extra .js files to include in the game~%")
55 (format t " --css <filenames...> - List of .css files to include in the game. Default is in extras/default.css~%")
56 (format t " --body <filename> - Alternative page body. Default is in extras/body.html~%")
57 (format t "~%")
58 (format t " -c - Just compile the game to a .js file without making it a full web page~%")
59 (format t " --beautify - Make the JS content pretty. By default it gets minified.~%")
60 (format t "~%")
61 (format t "Note that the files in extras/ are not actually used. They're just there for the reference"))
51 (lformat t :usage *app-name*))
62 52
63 53 (defun parse-file (filename)
64 (p:parse 'sugar-qsp-grammar
54 (p:parse 'txt2web-grammar
65 55 (alexandria:read-file-into-string filename)))
66 56
67 57 (defun report-error (fmt &rest args)
68 58 (format t "ERROR: ~A~%" (apply #'format nil fmt args))
69 59 (print-usage)
70 60 (throw :terminate nil))
71 61
72 62 ;;; JS
73 63
74 64 (defun minify-package (package-designator minify prefix)
75 65 (setf (ps:ps-package-prefix package-designator) prefix)
76 66 (if minify
77 67 (ps:obfuscate-package package-designator)
78 68 (ps:unobfuscate-package package-designator)))
79 69
80 70 (defmethod js-sources ((compiler compiler))
81 71 (let ((ps:*ps-print-pretty* (beautify compiler)))
82 72 (cond ((beautify compiler)
83 (minify-package "SUGAR-QSP.MAIN" nil "qsp_")
84 (minify-package "SUGAR-QSP.API" nil "qsp_api_")
85 (minify-package "SUGAR-QSP.LIB" nil "qsp_lib_"))
73 (minify-package "TXT2WEB.MAIN" nil "qsp_")
74 (minify-package "TXT2WEB.API" nil "qsp_api_")
75 (minify-package "TXT2WEB.LIB" nil "qsp_lib_"))
86 76 (t
87 (minify-package "SUGAR-QSP.MAIN" t "_")
88 (minify-package "SUGAR-QSP.API" t "a_")
89 (minify-package "SUGAR-QSP.LIB" t "l_")))
77 (minify-package "TXT2WEB.MAIN" t "_")
78 (minify-package "TXT2WEB.API" t "a_")
79 (minify-package "TXT2WEB.LIB" t "l_")))
90 80 (format nil "~{~A~^~%~%~}" (mapcar #'ps:ps* (reverse (js compiler))))))
91 81
92 82 ;;; CSS
93 83
94 84 (defmethod css-sources ((compiler compiler))
95 85 (format nil "~{~A~^~%~%~}" (css compiler)))
96 86
97 87 ;;; HTML
98 88
99 89 (defmethod html-sources ((compiler compiler))
100 90 (let ((flute:*escape-html* nil)
101 91 (body-template (body compiler))
102 92 (js (js-sources compiler))
103 93 (css (css-sources compiler)))
104 94 (with-output-to-string (out)
105 95 (write
106 96 (flute:h
107 97 (html
108 98 (head
109 99 (title "SugarQSP"))
110 100 (body
111 101 body-template
112 102 (style css)
113 103 (script js))))
114 104 :stream out
115 105 :pretty nil))))
116 106
117 107 (defun filename-game (filename)
118 108 (let ((filename (alexandria:lastcar (uiop:split-string filename :separator "/"))))
119 109 (format nil "~{~A~^.~}" (butlast (uiop:split-string filename :separator ".")))))
120 110
121 111 (defmethod initialize-instance ((compiler compiler) &key sources ((:js js-files)) ((:css css-files)) ((:body body-file)) compile &allow-other-keys)
122 112 (call-next-method)
123 113 (with-slots (body css js)
124 114 compiler
125 115 ;; Compile the game's JS
126 116 (dolist (source sources)
127 117 (let ((ps (parse-file source))
128 118 (game-name (filename-game source)))
129 119 (destructuring-bind (kw &rest locations)
130 120 ps
131 121 (unless (eq kw 'lib:game)
132 122 (report-error "Internal error!"))
133 123 (push
134 124 `(lib:game (,game-name) ,@locations)
135 125 js))))
136 126 ;; Does the user need us to do anything else
137 127 (unless compile
138 128 ;; Read in body
139 129 (when body-file
140 130 (setf body
141 131 (alexandria:read-file-into-string body-file)))
142 132 ;; Include js files
143 133 (dolist (js-file js-files)
144 134 (push (format nil "////// Included file ~A~%~A" js-file
145 135 (alexandria:read-file-into-string js-file))
146 136 js))
147 137 ;; Include css files
148 138 (when css-files
149 139 ;; User option overrides the default css
150 140 (setf css nil)
151 141 (dolist (css-file css-files)
152 142 (push (format nil "////// Included file ~A~%~A" css-file
153 143 (alexandria:read-file-into-string css-file))
154 144 css))))))
155 145
156 146 (defmethod write-compiled-file ((compiler compiler))
157 147 (alexandria:write-string-into-file
158 148 (if (compile-only compiler)
159 149 ;; Just the JS
160 150 (js-sources compiler)
161 151 ;; All of it
162 152 (html-sources compiler))
163 153 (target compiler) :if-exists :supersede))
@@ -1,54 +1,54 b''
1 1
2 (in-package sugar-qsp.main)
2 (in-package txt2web.main)
3 3
4 4 ;;; Game session state (saved in savegames)
5 5 ;; Variables
6 6 (var *globals (create))
7 7 ;; Inventory (objects)
8 8 (var *objs (create))
9 9 (var *current-location nil)
10 10 ;; Game time
11 11 (var *started-at (chain *date (now)))
12 12 ;; Timers
13 13 (var *timer-interval 500)
14 14 (var *timer-obj nil)
15 15 ;; Games
16 16 (var *loaded-games (list))
17 17
18 18 ;;; Transient state
19 19 ;; ACTions
20 20 (var *acts (create))
21 21 ;; Savegame data
22 22 (var *state-stash (create))
23 23 ;; List of audio files being played
24 24 (var *playing (create))
25 25 ;; Local variables stack (starts with an empty frame)
26 26 (var *locals (list))
27 27 ;; Promise to continue running the game after menu
28 28 (var *menu-resume nil)
29 29
30 30 ;;; Game data
31 31 ;; Games (filename -> [locations])
32 32 (var *games (list))
33 33 ;; The main (non library) game. Updated by openqst
34 34 (var *main-game nil)
35 35 ;; Active locations
36 36 (var *locs (create))
37 37
38 38 (setf (@ window onload)
39 39 (lambda ()
40 (#.(intern "INIT-DOM" "SUGAR-QSP.API"))
40 (#.(intern "INIT-DOM" "TXT2WEB.API"))
41 41 ;; For MSECCOUNT
42 42 (setf *started-at (chain *date (now)))
43 43 ;; For $COUNTER and SETTIMER
44 (#.(intern "SET-TIMER" "SUGAR-QSP.API")
44 (#.(intern "SET-TIMER" "TXT2WEB.API")
45 45 *timer-interval)
46 46 ;; Start the first game
47 (#.(intern "RUN-GAME" "SUGAR-QSP.API")
47 (#.(intern "RUN-GAME" "TXT2WEB.API")
48 48 (chain *object (keys *games) 0))
49 49 (values)))
50 50
51 51 ;;; Some very common utilities (for both api and lib)
52 52
53 53 (defun by-id (id)
54 54 (chain document (get-element-by-id id)))
@@ -1,108 +1,108 b''
1 1
2 2 (in-package cl-user)
3 3
4 (defpackage :sugar-qsp.js)
4 (defpackage :txt2web.js)
5 5
6 (defpackage :sugar-qsp.main
7 (:use :cl :ps :sugar-qsp.js)
6 (defpackage :txt2web.main
7 (:use :cl :ps :txt2web.js)
8 8 (:export #:api-call #:by-id
9 9 #:has
10 10
11 11 #:*globals #:*objs #:*current-location
12 12 #:*started-at #:*timer-interval #:*timer-obj #:*loaded-games
13 13
14 14 #:*acts #:*state-stash #:*playing #:*locals
15 15
16 16 #:*games #:*main-game #:*locs #:*menu-resume
17 17 ))
18 18
19 19 (defpackage :code-walker
20 20 (:use :cl)
21 21 (:export #:deftransform
22 22 #:deftransform-stop
23 23 #:walk
24 24 #:whole
25 25 #:walk-continue))
26 26
27 27 ;;; API functions
28 (defpackage :sugar-qsp.api
29 (:use :cl :ps :sugar-qsp.main :sugar-qsp.js)
28 (defpackage :txt2web.api
29 (:use :cl :ps :txt2web.main :txt2web.js)
30 30 (:export #:with-frame #:with-call-args
31 31 #:stash-state
32 32
33 33 #:report-error #:sleep #:init-dom #:call-serv-loc #:*serv-vars*
34 34 #:newline #:clear-id #:prepare-contents #:get-id #:set-id #:append-id
35 35 #:init-args #:get-result #:call-loc #:call-act
36 36 #:get-frame #:add-text #:get-text #:clear-text #:enable-frame
37 37 #:add-act #:del-act #:clear-act #:update-acts
38 38 #:set-str-element #:set-any-element #:set-serv-var
39 39 #:*var #:new-value #:index-num #:get #:set #:kill
40 40 #:var-real-name #:ensure-var #:var-ref #:get-var #:set-var
41 41 #:get-array #:set-array #:kill-var #:array-size
42 42 #:push-local-frame #:pop-local-frame #:current-local-frame #:new-local
43 43 #:update-objs
44 44 #:menu
45 45 #:clean-audio
46 46 #:show-image
47 47 #:opengame #:savegame
48 48 ))
49 49
50 50 ;;; QSP library functions and macros
51 (defpackage :sugar-qsp.lib
52 (:use :cl :ps :sugar-qsp.main :sugar-qsp.js)
53 (:local-nicknames (#:api :sugar-qsp.api)
51 (defpackage :txt2web.lib
52 (:use :cl :ps :txt2web.main :txt2web.js)
53 (:local-nicknames (#:api :txt2web.api)
54 54 (#:walker :code-walker))
55 55 (:export #:str #:exec #:qspblock #:qspfor #:game #:location
56 56 #:qspcond #:qspvar #:set #:local #:jump
57 57
58 58 #:killvar #:killall
59 59 #:obj #:loc #:no
60 60 #:qspver #:curloc
61 61 #:rnd #:qspmax #:qspmin
62 62 #:arrsize #:len
63 63 #:mid #:ucase #:lcase #:trim #:replace #:val #:qspstr
64 64 #:exit #:desc
65 65 #:showstat #:msg
66 66 #:showacts #:delact #:cla
67 67 #:showobjs #:countobj #:getobj
68 68 #:isplay
69 69 #:view
70 70 #:showinput
71 71 #:wait #:settimer
72 72 #:local
73 73 #:opengame #:savegame
74 74
75 75 #:goto #:xgoto
76 76 #:rand
77 77 #:copyarr #:arrpos #:arrcomp
78 78 #:instr #:isnum #:strcomp #:strfind #:strpos
79 79 #:iif
80 80 #:gosub #:func
81 81 #:dynamic #:dyneval
82 82 #:main-p #:main-pl #:main-nl #:maintxt #:desc #:main-clear
83 83 #:stat-p #:stat-pl #:stat-nl #:stattxt #:stat-clear #:cls
84 84 #:curacts
85 85 #:addobj #:delobj #:killobj
86 86 #:menu
87 87 #:play #:close #:closeall
88 88 #:refint
89 89 #:usertxt #:cmdclear #:input
90 90 #:msecscount
91 91 #:rgb
92 92 #:openqst #:addqst #:killqst
93 93 ))
94 94
95 (setf (ps:ps-package-prefix "SUGAR-QSP.MAIN") "qsp_")
96 (setf (ps:ps-package-prefix "SUGAR-QSP.API") "qsp_api_")
97 (setf (ps:ps-package-prefix "SUGAR-QSP.LIB") "qsp_lib_")
95 (setf (ps:ps-package-prefix "TXT2WEB.MAIN") "qsp_")
96 (setf (ps:ps-package-prefix "TXT2WEB.API") "qsp_api_")
97 (setf (ps:ps-package-prefix "TXT2WEB.LIB") "qsp_lib_")
98 98
99 99 ;;; The compiler
100 (defpackage :sugar-qsp
100 (defpackage :txt2web
101 101 (:use :cl)
102 102 (:local-nicknames (#:p #:esrap)
103 (#:lib :sugar-qsp.lib)
104 (#:api :sugar-qsp.api)
105 (#:main :sugar-qsp.main)
103 (#:lib :txt2web.lib)
104 (#:api :txt2web.api)
105 (#:main :txt2web.main)
106 106 (#:walker :code-walker))
107 107 (:export #:parse-file #:entry-point))
108 108
@@ -1,624 +1,624 b''
1 1
2 (in-package sugar-qsp)
2 (in-package txt2web)
3 3
4 4 ;;;; Parses TXT source to an intermediate representation
5 5
6 6 (eval-when (:compile-toplevel :load-toplevel :execute)
7 7 (defparameter *max-args* 10))
8 8
9 9 ;;; Utility
10 10
11 11 (defun remove-nth (list nth)
12 12 (append (subseq list 0 nth)
13 13 (subseq list (1+ nth))))
14 14
15 15 (defun not-quote (char)
16 16 (not (eql #\' char)))
17 17
18 18 (defun not-doublequote (char)
19 19 (not (eql #\" char)))
20 20
21 21 (defun not-brace (char)
22 22 (not (eql #\} char)))
23 23
24 24 (defun not-integer (string)
25 25 (when (find-if-not #'digit-char-p string)
26 26 t))
27 27
28 28 (defun not-newline (char)
29 29 (not (eql #\newline char)))
30 30
31 31 (defun id-any-char (char)
32 32 (and
33 33 (not (digit-char-p char))
34 34 (not (eql #\newline char))
35 35 (not (find char " !:&=<>+-*/,'\"()[]{}"))))
36 36
37 37 (defun intern-first (list)
38 (list* (intern (string-upcase (first list)) "SUGAR-QSP.LIB")
38 (list* (intern (string-upcase (first list)) "TXT2WEB.LIB")
39 39 (rest list)))
40 40
41 41 (eval-when (:compile-toplevel :load-toplevel :execute)
42 42 (defun remove-nil (list)
43 43 (remove nil list)))
44 44
45 45 (defun binop-rest (list)
46 46 (destructuring-bind (ws1 operator ws2 operand2)
47 47 list
48 48 (declare (ignore ws1 ws2))
49 (list (intern (string-upcase operator) "SUGAR-QSP.LIB") operand2)))
49 (list (intern (string-upcase operator) "TXT2WEB.LIB") operand2)))
50 50
51 51 (defun do-binop% (left-op other-ops)
52 52 (if (null other-ops)
53 53 left-op
54 54 (destructuring-bind ((operator right-op) &rest rest-ops)
55 55 other-ops
56 56 (if (and (listp left-op)
57 57 (eq (first left-op)
58 58 operator))
59 59 (do-binop% (append left-op (list right-op)) rest-ops)
60 60 (do-binop% (list operator left-op right-op) rest-ops)))))
61 61
62 62 (defun do-binop (list)
63 63 (destructuring-bind (left-op rest-ops)
64 64 list
65 65 (do-binop% left-op
66 66 (mapcar #'binop-rest rest-ops))))
67 67
68 68 (p:defrule line-continuation (and #\_ #\newline)
69 69 (:constant nil))
70 70
71 71 (p:defrule text-spaces (+ (or #\space #\tab line-continuation))
72 72 (:text t))
73 73
74 74 (p:defrule spaces (+ (or #\space #\tab line-continuation))
75 75 (:constant nil)
76 76 (:error-report nil))
77 77
78 78 (p:defrule spaces? (* (or #\space #\tab line-continuation))
79 79 (:constant nil)
80 80 (:error-report nil))
81 81
82 82 (p:defrule colon #\:
83 83 (:constant nil))
84 84
85 85 (p:defrule equal #\=
86 86 (:constant nil))
87 87
88 88 (p:defrule alphanumeric (alphanumericp character))
89 89
90 90 (p:defrule not-newline (not-newline character))
91 91
92 92 (p:defrule squote-esc "''"
93 93 (:lambda (list)
94 94 (p:text (elt list 0))))
95 95
96 96 (p:defrule dquote-esc "\"\""
97 97 (:lambda (list)
98 98 (p:text (elt list 0))))
99 99
100 100 (p:defrule sstring-chars (+ (and (and (p:! "<<") (p:! (p:~ "\"exec:")))
101 101 (or squote-esc (not-quote character))))
102 102 (:lambda (list)
103 103 (p:text (mapcar #'second list))))
104 104
105 105 (p:defrule dstring-chars (+ (and (and (p:! "<<") (p:! (p:~ "'exec:")))
106 106 (or dquote-esc (not-doublequote character))))
107 107 (:lambda (list)
108 108 (p:text (mapcar #'second list))))
109 109
110 110 ;;; Identifiers
111 111
112 112 (defparameter *keywords* '(act addlib addobj addqst and arrcomp arrpos arrsize cla clear *clear close clr *clr cls cmdclear cmdclr copyarr counter countobj curact curacts curloc debug delact dellib delobj desc disablescroll disablesubex dynamic dyneval else elseif end exit for freelib func getobj gosub goto gs gt if iif img *img inclib input instr isnum isplay jump killall killobj killqst killvar lcase lcolor len let loc local maintxt max menu mid min mod msecscount msg nl *nl no nosave obj opengame openqst or p *p pl *pl play qspver rand refint replace rgb rnd savegame selact selobj set settimer showacts showinput showobjs showstat stattxt step str strcomp strfind strpos to trim ucase unsel unselect usercom user_text usrtxt val view wait xgoto xgt))
113 113
114 114 (defun trim-$ (str)
115 115 (if (char= #\$ (elt str 0))
116 116 (subseq str 1)
117 117 str))
118 118
119 119 (defun qsp-keyword-p (id)
120 120 (member (intern (trim-$ (string-upcase id))) *keywords*))
121 121
122 122 (defun not-qsp-keyword-p (id)
123 123 (not (member (intern (trim-$ (string-upcase id))) *keywords*)))
124 124
125 125 (p:defrule qsp-keyword (qsp-keyword-p identifier-raw))
126 126
127 127 (p:defrule id-first (id-any-char character))
128 128 (p:defrule id-next (or (id-any-char character)
129 129 (digit-char-p character)))
130 130 (p:defrule identifier-raw (and id-first (* id-next))
131 131 (:lambda (list)
132 (intern (string-upcase (p:text list)) "SUGAR-QSP.LIB")))
132 (intern (string-upcase (p:text list)) "TXT2WEB.LIB")))
133 133
134 134 (p:defrule identifier (not-qsp-keyword-p identifier-raw))
135 135
136 136 ;;; Strings
137 137
138 138 (p:defrule qsp-string (or normal-string brace-string))
139 139
140 140 (p:defrule normal-string (or sstring dstring)
141 141 (:lambda (str)
142 142 (list* 'lib:str (or str (list "")))))
143 143
144 144 (p:defrule sstring (and #\' (* (or string-interpol
145 145 sstring-exec
146 146 sstring-chars))
147 147 #\')
148 148 (:function second))
149 149
150 150 (p:defrule dstring (and #\" (* (or string-interpol
151 151 dstring-exec
152 152 dstring-chars))
153 153 #\")
154 154 (:function second))
155 155
156 156 (p:defrule string-interpol (and "<<" expression ">>")
157 157 (:function second))
158 158
159 159 (p:defrule sstring-exec-body (+ (or squote-esc (not-doublequote character)))
160 160 (:text t))
161 161
162 162 (p:defrule dstring-exec-body (+ (or dquote-esc (not-quote character)))
163 163 (:text t))
164 164
165 165 (p:defrule sstring-exec (and (p:~ "\"exec:") sstring-exec-body #\")
166 166 (:lambda (list)
167 167 (list* 'lib:exec (p:parse 'exec-body (second list)))))
168 168
169 169 (p:defrule dstring-exec (and (p:~ "'exec:") dstring-exec-body #\')
170 170 (:lambda (list)
171 171 (list* 'lib:exec (p:parse 'exec-body (second list)))))
172 172
173 173 (p:defrule brace-string (and #\{ before-statement block-body #\})
174 174 (:lambda (list)
175 175 (list* 'lib:qspblock (third list))))
176 176
177 177 ;;; Location
178 178
179 (p:defrule sugar-qsp-grammar (and (* (or spaces #\newline))
179 (p:defrule txt2web-grammar (and (* (or spaces #\newline))
180 180 (* location))
181 181 (:lambda (list)
182 182 `(lib:game ,@(second list))))
183 183
184 184 (p:defrule location (and location-header block-body location-end)
185 185 (:destructure (header body end)
186 186 (declare (ignore end))
187 187 `(lib:location (,header) ,@body)))
188 188
189 189 (p:defrule location-header (and #\#
190 190 (+ not-newline)
191 191 (and #\newline spaces? before-statement))
192 192 (:destructure (spaces1 name spaces2)
193 193 (declare (ignore spaces1 spaces2))
194 194 (string-upcase (string-trim " " (p:text name)))))
195 195
196 196 (p:defrule location-end (and #\- (* not-newline) #\newline before-statement)
197 197 (:constant nil))
198 198
199 199 ;;; Block body
200 200
201 201 (p:defrule newline-block-body (and #\newline spaces? block-body)
202 202 (:function third))
203 203
204 204 (p:defrule block-body (* statement)
205 205 (:function remove-nil))
206 206
207 207 ;; Just for <a href="exec:...'>
208 208 ;; Explicitly called from that rule's production
209 209 (p:defrule exec-body (and before-statement line-body)
210 210 (:function second))
211 211
212 212 (p:defrule line-body (and inline-statement (* next-inline-statement))
213 213 (:lambda (list)
214 214 (list* (first list) (second list))))
215 215
216 216 (p:defrule before-statement (* (or #\newline spaces))
217 217 (:constant nil))
218 218
219 219 (p:defrule statement-end (or statement-end-real statement-end-block-close))
220 220
221 221 (p:defrule statement-end-real (and (or #\newline
222 222 (and #\& spaces? (p:& statement%)))
223 223 before-statement)
224 224 (:constant nil))
225 225
226 226 (p:defrule statement-end-block-close (or (p:& #\}))
227 227 (:constant nil))
228 228
229 229 (p:defrule inline-statement (and statement% spaces?)
230 230 (:function first))
231 231
232 232 (p:defrule next-inline-statement (and #\& spaces? inline-statement)
233 233 (:function third))
234 234
235 235 (p:defrule not-a-non-statement (and (p:! (p:~ "elseif"))
236 236 (p:! (p:~ "else"))
237 237 (p:! (p:~ "end"))))
238 238
239 239 (p:defrule statement (and inline-statement statement-end)
240 240 (:function first))
241 241
242 242 (p:defrule statement% (and not-a-non-statement
243 243 (or label comment string-output
244 244 block non-returning-intrinsic local
245 245 assignment expression-output))
246 246 (:function second))
247 247
248 248 (p:defrule expr-stopper (or comment block non-returning-intrinsic))
249 249
250 250 (p:defrule string-output qsp-string
251 251 (:lambda (string)
252 252 (list 'lib:main-pl string)))
253 253
254 254 (p:defrule expression-output expression
255 255 (:lambda (list)
256 256 (list 'lib:main-pl list)))
257 257
258 258 (p:defrule label (and colon identifier)
259 259 (:lambda (list)
260 260 (intern (string (second list)) :keyword)))
261 261
262 262 (p:defrule comment (and #\! (* (or qsp-string brace-comment text-spaces not-newline)))
263 263 (:constant nil))
264 264
265 265 (p:defrule brace-comment (and #\{ (* (not-brace character)) #\})
266 266 (:constant nil))
267 267
268 268 (p:defrule local (and (p:~ "local") spaces variable (p:? (and spaces? #\= spaces? expression)))
269 269 (:lambda (list)
270 270 (list* 'lib:local (third list)
271 271 (when (fourth list)
272 272 (list (fourth (fourth list)))))))
273 273
274 274 ;;; Blocks
275 275
276 276 (p:defrule block (or block-act block-if block-for))
277 277
278 278 (p:defrule block-if (and block-if-head block-if-body)
279 279 (:destructure (head body)
280 280 `(lib:qspcond (,@head ,@(first body))
281 281 ,@(rest body))))
282 282
283 283 (p:defrule block-if-head (and (p:~ "if") spaces expression spaces? colon spaces?)
284 284 (:function remove-nil)
285 285 (:function cdr))
286 286
287 287 (p:defrule block-if-body (or block-if-ml block-if-sl)
288 288 (:destructure (if-body elseifs else &rest ws)
289 289 (declare (ignore ws))
290 290 `(,(remove-nil if-body) ,@elseifs ,@(when else `((else ,@else))))))
291 291
292 292 (p:defrule block-if-sl (and line-body
293 293 (p:? block-if-elseif-inline)
294 294 (p:? block-if-else-inline)
295 295 spaces?))
296 296
297 297 (p:defrule block-if-ml (and (and #\newline spaces?)
298 298 block-body
299 299 (p:? block-if-elseif)
300 300 (p:? block-if-else)
301 301 block-if-end)
302 302 (:lambda (list)
303 303 (cdr list)))
304 304
305 305 (p:defrule block-if-elseif-inline (and block-if-elseif-head line-body (p:? block-if-elseif-inline))
306 306 (:destructure (head statements elseif)
307 307 `((,@(rest head) ,@(remove-nil statements)) ,@elseif)))
308 308
309 309 (p:defrule block-if-elseif (and block-if-elseif-head statement-end block-body (p:? block-if-elseif))
310 310 (:destructure (head ws statements elseif)
311 311 (declare (ignore ws))
312 312 `((,@(rest head) ,@(remove-nil statements)) ,@elseif)))
313 313
314 314 (p:defrule block-if-elseif-head (and (p:~ "elseif") spaces expression spaces? colon spaces?)
315 315 (:function remove-nil)
316 316 (:function intern-first))
317 317
318 318 (p:defrule block-if-else-inline (and block-if-else-head line-body)
319 319 (:function second))
320 320
321 321 (p:defrule block-if-else (and block-if-else-head #\newline spaces? block-body)
322 322 (:function fourth))
323 323
324 324 (p:defrule block-if-else-head (and (p:~ "else") spaces?)
325 325 (:constant nil))
326 326
327 327 (p:defrule block-if-end (and (p:~ "end")
328 328 (p:? (and spaces (p:~ "if"))))
329 329 (:constant nil))
330 330
331 331 (p:defrule block-act (and block-act-head (or block-ml block-sl))
332 332 (:lambda (list)
333 333 (apply #'append list)))
334 334
335 335 (p:defrule block-act-head (and (p:~ "act") spaces? qsp-string spaces?
336 336 (p:? block-act-head-img)
337 337 colon spaces?)
338 338 (:lambda (list)
339 339 (intern-first (list (first list)
340 340 (third list)
341 341 (or (fifth list) '(lib:str ""))))))
342 342
343 343 (p:defrule block-act-head-img (and #\, spaces? qsp-string spaces?)
344 344 (:lambda (list)
345 345 (or (third list) "")))
346 346
347 347 (p:defrule block-for (and block-for-head (or block-ml block-sl))
348 348 (:lambda (list)
349 349 (apply #'append list)))
350 350
351 351 (p:defrule block-for-head (and (p:~ "for") spaces variable spaces? equal spaces? expression
352 352 (p:~ "to") spaces expression
353 353 block-for-head-step
354 354 colon spaces?)
355 355 (:lambda (list)
356 356 (list 'lib:qspfor
357 357 (elt list 2)
358 358 (elt list 6)
359 359 (elt list 9)
360 360 (elt list 10))))
361 361
362 362 (p:defrule block-for-head-step (p:? (and (p:~ "step") spaces expression spaces?))
363 363 (:lambda (list)
364 364 (if list
365 365 (third list)
366 366 1)))
367 367
368 368 (p:defrule block-sl line-body)
369 369
370 370 (p:defrule block-ml (and newline-block-body block-end)
371 371 (:lambda (list)
372 372 (apply #'list* (butlast list))))
373 373
374 374 (p:defrule block-end (and (p:~ "end"))
375 375 (:constant nil))
376 376
377 377 ;;; Calls
378 378
379 379 (p:defrule first-argument (and expression spaces?)
380 380 (:function first))
381 381 (p:defrule next-argument (and "," spaces? expression)
382 382 (:function third))
383 383 (p:defrule arguments (or parenthesized-arguments plain-arguments no-arguments))
384 384 (p:defrule parenthesized-arguments (and spaces? #\( base-arguments #\))
385 385 (:function third))
386 386 (p:defrule plain-arguments (and spaces? base-arguments)
387 387 (:function second))
388 388 (p:defrule no-arguments (or (and spaces? (p:& #\newline))
389 389 (and spaces? (p:& #\&))
390 390 spaces?)
391 391 (:constant nil))
392 392 (p:defrule base-arguments (or (and first-argument (* next-argument)) spaces?)
393 393 (:lambda (list)
394 394 (if (null list)
395 395 nil
396 396 (list* (first list) (second list)))))
397 397
398 398 ;;; Intrinsics
399 399
400 400 (defmacro defintrinsics ((rule-name returning-rule-name non-returning-rule-name) &body clauses)
401 401 `(progn
402 402 ,@(loop :for clause :in clauses
403 403 :collect `(defintrinsic ,@clause))
404 404 (p:defrule ,returning-rule-name (or ,@(remove-nil
405 405 (mapcar (lambda (clause)
406 406 (when (second clause)
407 407 (alexandria:symbolicate
408 408 'intrinsic- (first clause))))
409 409 clauses))))
410 410 (p:defrule ,non-returning-rule-name (or ,@(remove-nil
411 411 (mapcar (lambda (clause)
412 412 (unless (second clause)
413 413 (alexandria:symbolicate
414 414 'intrinsic- (first clause))))
415 415 clauses))))
416 416 (p:defrule ,rule-name (or ,returning-rule-name ,non-returning-rule-name))))
417 417
418 418 (defmacro defintrinsic (sym returning &optional (min-arity 0) max-arity &rest names)
419 419 (declare (ignore returning))
420 420 (unless max-arity
421 421 (setf max-arity *max-args*))
422 422 (setf names
423 423 (if names
424 424 (mapcar #'string-upcase names)
425 425 (list (string sym))))
426 426 `(p:defrule ,(alexandria:symbolicate 'intrinsic- sym)
427 427 (and (p:? #\$) (or ,@(loop :for name :in names :collect `(p:~ ,name)))
428 428 arguments)
429 429 (:destructure (dollar name arguments)
430 430 (declare (ignore dollar))
431 431 (unless (<= ,min-arity (length arguments) ,max-arity)
432 432 (error "Intrinsic ~A expects between ~A and ~A arguments but ~A were provided:~%~S"
433 433 name ,min-arity ,max-arity (length arguments) arguments))
434 (list* ',(intern (string sym) "SUGAR-QSP.LIB") arguments))))
434 (list* ',(intern (string sym) "TXT2WEB.LIB") arguments))))
435 435
436 436 (defintrinsics (intrinsic returning-intrinsic non-returning-intrinsic)
437 437 ;; Transitions
438 438 (goto% nil 0 nil "gt" "goto")
439 439 (xgoto% nil 0 nil "xgt" "xgoto")
440 440 ;; Variables
441 441 (killvar nil 0 2)
442 442 ;; Expressions
443 443 (obj t 1 1)
444 444 (loc t 1 1)
445 445 (no t 1 1)
446 446 ;; Basic
447 447 (qspver t 0 0)
448 448 (curloc t 0 0)
449 449 (rand t 1 2)
450 450 (rnd t 0 0)
451 451 (qspmax t 1 nil "max")
452 452 (qspmin t 1 nil "min")
453 453 ;; Arrays
454 454 (killall nil 0 0)
455 455 (copyarr nil 2 4)
456 456 (arrsize t 1 1)
457 457 (arrpos t 2 3)
458 458 (arrcomp t 2 3)
459 459 ;; Strings
460 460 (len t 1 1)
461 461 (mid t 2 3)
462 462 (ucase t 1 1)
463 463 (lcase t 1 1)
464 464 (trim t 1 1)
465 465 (replace t 2 3)
466 466 (instr t 2 3)
467 467 (isnum t 1 1)
468 468 (val t 1 1)
469 469 (qspstr t 1 1 "str")
470 470 (strcomp t 2 2)
471 471 (strfind t 2 3)
472 472 (strpos t 2 3)
473 473 ;; IF
474 474 (iif t 2 3)
475 475 ;; Subs
476 476 (gosub nil 1 nil "gosub" "gs")
477 477 (func t 1 nil)
478 478 (exit nil 0 0)
479 479 ;; Jump
480 480 (jump nil 1 1)
481 481 ;; Dynamic
482 482 (dynamic nil 1 nil)
483 483 (dyneval t 1 nil)
484 484 ;; Sound
485 485 (play nil 1 2)
486 486 (isplay t 1 1)
487 487 (close nil 1 1)
488 488 (closeall nil 0 0 "close all")
489 489 ;; Main window
490 490 (main-pl nil 1 1 "*pl")
491 491 (main-nl nil 0 1 "*nl")
492 492 (main-p nil 1 1 "*p")
493 493 (maintxt t 0 0)
494 494 (desc t 1 1)
495 495 (main-clear nil 0 0 "*clear" "*clr")
496 496 ;; Aux window
497 497 (showstat nil 1 1)
498 498 (stat-pl nil 1 1 "pl")
499 499 (stat-nl nil 0 1 "nl")
500 500 (stat-p nil 1 1 "p")
501 501 (stattxt t 0 0)
502 502 (stat-clear nil 0 0 "clear" "clr")
503 503 (cls nil 0 0)
504 504 ;; Dialog
505 505 (msg nil 1 1)
506 506 ;; Acts
507 507 (showacts nil 1 1)
508 508 (delact nil 1 1 "delact" "del act")
509 509 (curacts t 0 0)
510 510 (selact t 0 0)
511 511 (cla nil 0 0)
512 512 ;; Objects
513 513 (showobjs nil 1 1)
514 514 (addobj nil 1 3 "addobj" "add obj")
515 515 (delobj nil 1 1 "delobj" "del obj")
516 516 (killobj nil 0 1)
517 517 (countobj t 0 0)
518 518 (getobj t 1 1)
519 519 (selobj t 0 0)
520 520 ;; Menu
521 521 (menu nil 1 1)
522 522 ;; Images
523 523 (refint nil 0 0)
524 524 (view nil 0 1)
525 525 (img nil 1)
526 526 (*img nil 1)
527 527 ;; Fonts
528 528 (rgb t 3 3)
529 529 ;; Input
530 530 (showinput nil 1 1)
531 531 (usertxt t 0 0 "user_text" "usrtxt")
532 532 (cmdclear nil 0 0 "cmdclear" "cmdclr")
533 533 (input t 1 1)
534 534 ;; Files
535 535 (openqst nil 1 1)
536 536 (addqst nil 1 1 "addqst" "addlib" "inclib")
537 537 (killqst nil 1 1 "killqst" "dellib" "freelib")
538 538 (opengame nil 0 0)
539 539 (savegame nil 0 0)
540 540 ;; Real time
541 541 (wait nil 1 1)
542 542 (msecscount t 0 0)
543 543 (settimer nil 1 1))
544 544
545 545 ;;; Expression
546 546
547 547 (p:defrule expression or-expr)
548 548
549 549 (p:defrule or-expr (and and-expr (* (and spaces? or-op spaces? and-expr)))
550 550 (:function do-binop))
551 551
552 552 (p:defrule and-expr (and eq-expr (* (and spaces? and-op spaces? eq-expr)))
553 553 (:function do-binop))
554 554
555 555 (p:defrule eq-expr (and sum-expr (* (and spaces? (or "<=" ">=" "=<" "=>" "<>"
556 556 "=" "<" ">" "!")
557 557 spaces? sum-expr)))
558 558 (:function do-binop))
559 559
560 560 (p:defrule sum-expr (and mod-expr (* (and spaces? (or #\+ #\-) spaces? mod-expr)))
561 561 (:function do-binop))
562 562
563 563 (p:defrule mod-expr (and mul-expr (* (and spaces? (p:~ "mod") spaces? mul-expr)))
564 564 (:function do-binop))
565 565
566 566 (p:defrule mul-expr (and unary-expr (* (and spaces? (or #\* #\/) spaces? unary-expr)))
567 567 (:function do-binop))
568 568
569 569 (p:defrule unary-expr (and (p:? (or #\+ #\-)) atom-expr)
570 570 (:lambda (list)
571 571 (let ((expr (remove-nil list)))
572 572 (if (= 1 (length expr))
573 573 (first expr)
574 574 (intern-first expr)))))
575 575
576 576 (p:defrule atom-expr (and (or variable literal returning-intrinsic paren-expr) spaces?)
577 577 (:function first))
578 578
579 579 (p:defrule paren-expr (and #\( spaces? expression spaces? #\))
580 580 (:function third))
581 581
582 582 (p:defrule or-op (p:~ "or")
583 583 (:constant "or"))
584 584
585 585 (p:defrule and-op (p:~ "and")
586 586 (:constant "and"))
587 587
588 588 ;;; Variables
589 589
590 590 (p:defrule variable (and identifier (p:? array-index))
591 591 (:destructure (id idx-raw)
592 592 (let ((idx (case idx-raw
593 593 ((nil) 0)
594 594 (:last nil)
595 595 (t idx-raw))))
596 596 (list 'lib:qspvar id idx))))
597 597
598 598 (p:defrule array-index (and #\[ spaces? (p:? expression) spaces? #\])
599 599 (:lambda (list)
600 600 (or (third list) :last)))
601 601
602 602 (p:defrule assignment (or kw-assignment plain-assignment op-assignment)
603 603 (:destructure (qspvar eq expr)
604 604 (declare (ignore eq))
605 605 (list 'lib:set qspvar expr)))
606 606
607 607 (p:defrule kw-assignment (and (or (p:~ "let") (p:~ "set")) spaces? (or op-assignment plain-assignment))
608 608 (:function third))
609 609
610 610 (p:defrule op-assignment (and variable spaces? (or "+" "-" "*" "/") #\= spaces? expression)
611 611 (:destructure (qspvar ws1 op eq ws2 expr)
612 612 (declare (ignore ws1 ws2))
613 613 (list qspvar eq (intern-first (list op qspvar expr)))))
614 614
615 615 (p:defrule plain-assignment (and variable spaces? #\= spaces? expression)
616 616 (:function remove-nil))
617 617
618 618 ;;; Non-string literals
619 619
620 620 (p:defrule literal (or qsp-string brace-string number))
621 621
622 622 (p:defrule number (+ (or #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
623 623 (:lambda (list)
624 624 (parse-integer (p:text list))))
@@ -1,377 +1,377 b''
1 1
2 (in-package sugar-qsp.lib)
2 (in-package txt2web.lib)
3 3
4 4 ;;;; Parenscript macros which make the parser's intermediate
5 5 ;;;; representation directly compilable by Parenscript
6 6 ;;;; Some utility macros for other .ps sources too.
7 7
8 8 ;;;; Block type | Has own locals | Has labels | async
9 9 ;;; Location | TRUE | TRUE | TRUE
10 10 ;;; Act | TRUE | TRUE | TRUE
11 11 ;;; {} | TRUE | TRUE | TRUE
12 12 ;;; IF | FALSE | TRUE | TRUE
13 13 ;;; FOR | FALSE | TRUE | TRUE
14 14 ;;;
15 15 ;;; IF and FOR are actually not blocks at all. They're implemented as Javascript's if and for loops.
16 16 ;;; Jumps back are also optimized to Javascript's while loops.
17 17
18 18 ;;; Utils
19 19
20 20 ;;; Common
21 21
22 22 (defpsmacro label-block (() &body body)
23 23 (let ((has-labels (some #'keywordp body)))
24 24 `(block nil
25 25 ,@(when has-labels
26 26 '((var _labels (list))))
27 27 (tagbody
28 28 ,@body
29 29 (void)))))
30 30
31 31 (defpsmacro str (&rest forms)
32 32 (cond ((zerop (length forms))
33 33 "")
34 34 ((and (= 1 (length forms))
35 35 (stringp (first forms)))
36 36 (first forms))
37 37 (t
38 38 `(& ,@forms))))
39 39
40 40 (defpsmacro locals-block (&body body)
41 41 "Includes labels too (through qsp-lambda)"
42 42 (let ((*locals* nil))
43 43 (walker:walk 'locals body)
44 44 `(qsp-lambda
45 45 (create-locals ,*locals*)
46 46 ,@(walker:walk 'apply-vars body))))
47 47
48 48 ;;; 1loc
49 49
50 50 (defparameter *special-variables*
51 51 '((usehtml 0)
52 52 (result 0)
53 53 ($result 0)
54 54 ($ongload 0)
55 55 ($ongsave 0)
56 56 ($onobjadd 0)
57 57 ($onobjdel 0)
58 58 ($onobjsel 0)
59 59 ($onnewloc 0)
60 60 ($onactsel 0)
61 61 ($counter 0)
62 62 ($usercom 0)))
63 63
64 64 (defpsmacro game ((name) &body body)
65 65 (setf body (walker:walk 'for-transform body))
66 66 (setf *globals* *special-variables*)
67 67 (walker:walk 'globals body)
68 68 `(progn
69 69 ;; Game object
70 70 (setf (@ *games ,name)
71 71 (create))
72 72 ;; Global variables from this game
73 73 (create-globals ,*globals*)
74 74 ;; Locations
75 75 ,@(loop :for location :in body
76 76 :collect `(setf (@ *games ,name ,(caadr location))
77 77 ,location))))
78 78
79 79 (defpsmacro location ((name) &body body)
80 80 (declare (ignore name))
81 81 "Name is used by the game macro above"
82 82 `(locals-block ,@body))
83 83
84 84 (defpsmacro goto% (target &rest args)
85 85 `(progn
86 86 (goto ,target ,args)
87 87 (exit)))
88 88
89 89 (defpsmacro xgoto% (target &rest args)
90 90 `(progn
91 91 (xgoto ,target ,args)
92 92 (exit)))
93 93
94 94 ;;; 2var
95 95
96 96 (defvar *globals* nil)
97 97 (defvar *locals* nil)
98 98
99 99 (defpsmacro create-globals (globals)
100 100 (flet ((indexes (name)
101 101 (remove nil
102 102 (remove-if #'listp
103 103 (mapcar #'second
104 104 (remove name globals
105 105 :key #'first
106 106 :test-not #'eq))))))
107 107 (let ((names (remove-duplicates (mapcar #'first globals))))
108 108 `(chain *object
109 109 (assign *globals
110 110 (create
111 111 ,@(loop :for sym :in names
112 112 :for indexes := (indexes sym)
113 113 :for name := (string-upcase sym)
114 114 :append `(,name
115 115 (api-call new-var ,name ,@indexes)))))))))
116 116
117 117 (walker:deftransform globals qspvar (&rest var)
118 118 (pushnew var *globals* :test #'equal)
119 119 (walker:walk-continue))
120 120
121 121 (walker:deftransform globals local (var &rest expr)
122 122 (declare (ignore var))
123 123 (walker:walk 'globals expr))
124 124
125 125 (defpsmacro create-locals (locals)
126 126 (when locals
127 127 `(progn
128 128 (var locals (create
129 129 ,@(loop :for (sym index) :in locals
130 130 :for name := (string-upcase sym)
131 131 :append `(,name (api-call new-var ,name))))))))
132 132
133 133 ;; locations, blocks, and acts all have their own locals namespace
134 134 (walker:deftransform-stop locals qspblock)
135 135 (walker:deftransform-stop locals act)
136 136
137 137 (walker:deftransform locals local (var &optional expr)
138 138 (declare (ignore expr))
139 139 (pushnew (rest var) *locals* :test #'equal)
140 140 nil)
141 141
142 142 ;; index types:
143 143 ;; literal number
144 144 ;; literal string
145 145 ;; variable number
146 146 ;; variable string
147 147 ;; expression (may be possible to determine if it's a string or a number)
148 148
149 149 (defun $-var-p (sym)
150 150 (char= #\$ (elt (string-upcase (symbol-name sym)) 0)))
151 151
152 152 (defun literal-string-p (form)
153 153 (and (listp form)
154 154 (= 2 (length form))
155 155 (eq 'str (first form))
156 156 (stringp (second form))))
157 157
158 158 (defun variable-number-p (form)
159 159 (and (listp form)
160 160 (eq 'qspvar (first form))
161 161 (not ($-var-p (second form)))))
162 162
163 163 (defun variable-string-p (form)
164 164 (and (listp form)
165 165 (eq 'qspvar (first form))
166 166 ($-var-p (second form))))
167 167
168 168 (walker:deftransform apply-vars set (var expr)
169 169 (destructuring-bind (qspvar name index)
170 170 var
171 171 (declare (ignore qspvar))
172 172 (setf name (string-upcase name))
173 173 (let ((slot `(getprop
174 174 ,(if (member name *locals* :key #'first)
175 175 'locals '*globals)
176 176 ,name))
177 177 (index (walker:walk 'apply-vars index))
178 178 (value (walker:walk 'apply-vars expr)))
179 179 (cond
180 180 ((member name api:*serv-vars* :test #'equalp)
181 181 `(api:set-serv-var ,name ,index ,value))
182 182 ((null index)
183 183 `(chain (elt ,slot) (push ,value)))
184 184 ((or (numberp index)
185 185 (variable-number-p index))
186 186 `(setf (elt ,slot ,index) ,value))
187 187 ((or (literal-string-p index)
188 188 (variable-string-p index))
189 189 `(api:set-str-element ,slot ,index ,value))
190 190 (t
191 191 `(api:set-any-element ,slot ,index ,value))))))
192 192
193 193 (walker:deftransform apply-vars local (var &optional expr)
194 194 ;; TODO: var can't be a service variable
195 195 (when expr
196 196 (walker:walk 'apply-vars (list 'set var expr))))
197 197
198 198 (walker:deftransform apply-vars qspvar (name index)
199 199 (let ((slot `(getprop
200 200 ,(if (member name *locals* :key #'first) 'locals '*globals)
201 201 ,(string-upcase name))))
202 202 (cond
203 203 ((null index)
204 204 `(elt ,slot (1- (length ,slot))))
205 205 ((or (numberp index)
206 206 (variable-number-p index))
207 207 `(elt ,slot ,(walker:walk-continue index)))
208 208 ((or (literal-string-p index)
209 209 (variable-string-p index))
210 210 `(elt ,slot (@ ,slot :indexes ,(walker:walk-continue index))))
211 211 (t
212 212 `(get-element ,slot ,(walker:walk-continue index))))))
213 213
214 214 (walker:deftransform apply-vars qspblock (&rest block)
215 215 (declare (ignore block))
216 216 (walker:whole))
217 217 (walker:deftransform apply-vars act (&rest block)
218 218 (declare (ignore block))
219 219 (walker:whole))
220 220 (walker:deftransform apply-vars qspfor (var from to step body)
221 221 (list* 'qspfor var (mapcar #'walker:walk-continue (list from to step body))))
222 222
223 223 ;;; 3expr
224 224
225 225 (defpsmacro <> (op1 op2)
226 226 `(not (equal ,op1 ,op2)))
227 227
228 228 (defpsmacro ! (op1 op2)
229 229 `(not (equal ,op1 ,op2)))
230 230
231 231 ;;; 4code
232 232
233 233 (defpsmacro exec (&body body)
234 234 (format nil "javascript: ~{~A~^~%~}" (mapcar #'ps* body)))
235 235
236 236 ;;; 5arrays
237 237
238 238 ;;; 6str
239 239
240 240 (defpsmacro & (&rest args)
241 241 `(chain "" (concat ,@args)))
242 242
243 243 ;;; 7if
244 244
245 245 (defpsmacro qspcond (&rest clauses)
246 246 `(cond ,@(loop :for clause :in clauses
247 247 :collect (list (first clause)
248 248 `(tagbody
249 249 ,@(rest clause))))))
250 250
251 251 ;;; 8sub
252 252
253 253 ;;; 9loops
254 254 ;;; Yep, that's a huge kludge since Javascript doesn't support arbitrary labels
255 255
256 256 (defpsmacro jump (target)
257 257 `(return-from label-body ,(string-upcase (second target))))
258 258
259 259 (defpsmacro tagbody (&body body)
260 260 (let ((create-locals (if (eq (caar body) 'create-locals)
261 261 (list (car body))))
262 262 (void (if (equal (car (last body)) '(void))
263 263 '((void)))))
264 264 (when create-locals
265 265 (setf body (cdr body)))
266 266 (when void
267 267 (setf body (butlast body)))
268 268 (let ((funcs (list nil "_nil")))
269 269 (dolist (form body)
270 270 (cond ((keywordp form)
271 271 (setf (first funcs) (reverse (first funcs)))
272 272 (push (string-upcase form) funcs)
273 273 (push nil funcs))
274 274 (t
275 275 (push form (first funcs)))))
276 276 (setf (first funcs) (reverse (first funcs)))
277 277 (setf funcs (reverse funcs))
278 278 `(progn
279 279 ,@create-locals
280 280 ,(if (= 2 (length funcs))
281 281 `(progn
282 282 ,@body)
283 283 `(progn
284 284 (tagbody-blocks ,funcs)
285 285 (loop
286 286 :for _nextblock
287 287 := :_nil
288 288 :then (await (funcall (getprop _labels _nextblock)))
289 289 :while _nextblock)))
290 290 ,@void))))
291 291
292 292 (defvar *current-label*)
293 293 (defvar *has-jump-back*)
294 294 (walker:deftransform optimize-jump jump (target)
295 295 (cond ((string= (string-upcase (second target)) *current-label*)
296 296 (setf *has-jump-back* t)
297 297 '(continue))
298 298 (t
299 299 (walker:walk-continue))))
300 300
301 301 (defpsmacro tagbody-blocks (funcs)
302 302 `(setf ,@(loop :for (label code . rest-labels) :on funcs :by #'cddr
303 303 :append `((@ _labels ,label)
304 304 (async-lambda ()
305 305 (block label-body
306 306 (tagbody-block-body ,label ,code
307 307 ,(first rest-labels))))))))
308 308
309 309 (defpsmacro tagbody-block-body (label code next-label)
310 310 (let ((*current-label* label)
311 311 (*has-jump-back* nil))
312 312 (let ((code (walker:walk 'optimize-jump code)))
313 313 (if *has-jump-back*
314 314 `(progn
315 315 (loop :do (progn
316 316 ,@code
317 317 (break)))
318 318 ,@(if next-label
319 319 (list next-label)
320 320 nil))
321 321 `(progn
322 322 ,@code
323 323 ,@(if next-label
324 324 (list next-label)
325 325 nil))))))
326 326
327 327 (defpsmacro exit ()
328 328 '(return-from nil (values)))
329 329
330 330 ;;; 10dynamic
331 331
332 332 (defpsmacro qspblock (&body body)
333 333 `(locals-block
334 334 ,@body))
335 335
336 336 (defpsmacro qsp-lambda (&body body)
337 337 `(async-lambda (args)
338 338 (label-block ()
339 339 ,@body)))
340 340
341 341 ;;; 11main
342 342
343 343 (defpsmacro act (name img &body body)
344 344 `(api-call add-act ,name ,img
345 345 (locals-block
346 346 ,@body)))
347 347
348 348 ;;; 12aux
349 349
350 350 ;;; 13diag
351 351
352 352 ;;; 14act
353 353
354 354 ;;; 15objs
355 355
356 356 ;;; 16menu
357 357
358 358 ;;; 17sound
359 359
360 360 ;;; 18img
361 361
362 362 ;;; 19input
363 363
364 364 ;;; 20time
365 365
366 366 ;;; 21local
367 367
368 368 ;;; 22for
369 369
370 370 ;; Transform because it creates a (set ...) hence it has to be processed
371 371 ;; before the apply-vars transform. And macros are processed after all
372 372 ;; the transforms
373 373 (walker:deftransform for-transform qspfor (var from to step &rest body)
374 374 `(loop :for i :from ,from :to ,to :by ,step
375 375 :do (set ,var i)
376 376 :do (block nil
377 377 ,@(walker:walk-continue body))))
@@ -1,9 +1,9 b''
1 1
2 (in-package sugar-qsp)
2 (in-package txt2web)
3 3
4 4 ;;; 1. Generates parenscript source to write to js
5 5 ;;; 2. Collects everything into complete file, leaving a neatly marked
6 6 ;;; place to customize page layout and styles.
7 7
8 8 (defun make-javascript (locations)
9 9 (mapcar #'ps:ps* locations))
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now