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 |
|
|
14 |
--entry |
|
|
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 |
|
|
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 |
|
|
|
37 |
|
|
|
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 |
` |
|
|
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 |
` |
|
|
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 |
` |
|
|
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 |
` |
|
|
33 | `txt2web game.txt`<br/> | |
|
34 | 34 | Создаст игру в game.html |
|
35 | 35 | |
|
36 | 36 | 2. **Я знаю что делаю**:<br/> |
|
37 |
` |
|
|
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 |
` |
|
|
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- |
|
|
28 |
'#.(read- |
|
|
29 |
'#.(read- |
|
|
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 |
|
|
2 | (in-package txt2web.main) | |
|
3 | 3 | |
|
4 | 4 | (defmacro+ps api-call (name &rest args) |
|
5 |
`(,(intern (string-upcase name) " |
|
|
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 |
|
|
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 : |
|
|
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 |
|
|
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 ' |
|
|
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 " |
|
|
84 |
(minify-package " |
|
|
85 |
(minify-package " |
|
|
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 " |
|
|
88 |
(minify-package " |
|
|
89 |
(minify-package " |
|
|
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 |
|
|
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" " |
|
|
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" " |
|
|
44 | (#.(intern "SET-TIMER" "TXT2WEB.API") | |
|
45 | 45 | *timer-interval) |
|
46 | 46 | ;; Start the first game |
|
47 |
(#.(intern "RUN-GAME" " |
|
|
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 : |
|
|
4 | (defpackage :txt2web.js) | |
|
5 | 5 | |
|
6 |
(defpackage : |
|
|
7 |
(:use :cl :ps : |
|
|
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 : |
|
|
29 |
(:use :cl :ps : |
|
|
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 : |
|
|
52 |
(:use :cl :ps : |
|
|
53 |
(:local-nicknames (#: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 " |
|
|
96 |
(setf (ps:ps-package-prefix " |
|
|
97 |
(setf (ps:ps-package-prefix " |
|
|
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 : |
|
|
100 | (defpackage :txt2web | |
|
101 | 101 | (:use :cl) |
|
102 | 102 | (:local-nicknames (#:p #:esrap) |
|
103 |
(#:lib : |
|
|
104 |
(#:api : |
|
|
105 |
(#: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 |
|
|
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)) " |
|
|
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) " |
|
|
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)) " |
|
|
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 |
|
|
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) " |
|
|
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 |
|
|
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 |
|
|
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