diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -1,18 +1,21 @@ -BIN = sugar-qsp +BIN = txt2web +PKG = $(BIN) +DIST = txt2web.tar.xz LISP = sbcl all: $(BIN) +dist: $(DIST) + graphs: diagrams.png -$(BIN): src/*.lisp src/*.ps +$(BIN): *.asd src/*.lisp src/*.ps strings/*.sexp buildapp.$(LISP) --asdf-path .\ --asdf-tree .qlot/dists\ - --load-system sugar-qsp\ - --entry sugar-qsp:entry-point\ - --compress-core\ + --load-system $(PKG)\ + --entry $(PKG):entry-point\ --output $(BIN) install-deps: @@ -24,16 +27,20 @@ update-deps: %.png: %.dot dot $< -T png -o $@ -dist: $(BIN) - tar cfvJ sugar-qsp.tar.xz $(BIN) extras +$(DIST): $(BIN) extras/* + tar cfvJ $@ $< extras + +upload: $(DIST) + curl --upload-file $(DIST) https://transfer.sh/$(DIST) + @echo distclean: clean clean-deps clean: - -rm sugar-qsp + rm -f $(BIN) $(DIST) clean-deps: - -rm qlfile.lock - -rm -rf .qlot + rm qlfile.lock + rm -rf .qlot -.PHONY: all graphs install-deps update-deps clean +.PHONY: all graphs install-deps update-deps clean upload diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ (инструкции на Русском - внизу) -# sugar-qsp +# txt2web Compiler for QSP games which creates monolithic HTML pages. ## Usage @@ -8,21 +8,21 @@ Compiler for QSP games which creates mon There are three mastery levels 1. Just build me the game:
-`sugar-qsp game.txt`
+`txt2web game.txt`
And it will create the game in game.html 2. I know what I'm doing:
-`sugar-qsp game.txt -o game.html --body body.html --js jquery.js my-js-library.js --css styles/*.css`
+`txt2web game.txt -o game.html --body body.html --js jquery.js my-js-library.js --css styles/*.css`
All options are self-explanatory. The result is a monolithic html specified with the `-o` option. Default `body.html` (used by the first mastery level) can be found in `extas` directory. 3. I'm a frontend developer!
-`sugar-qsp game.txt -c -o game.js`
+`txt2web game.txt -c -o game.js`
It just builds the game script into a js you can put on your website. To run the game execute `SugarQSP.start()` -# sugar-qsp +# txt2web Компилятор для игр на QSP создающий монолитные страницы на HTML. ## Инструкции @@ -30,15 +30,15 @@ the game execute `SugarQSP.start()` Есть три уровня мастерства. 1. **Просто собери мне игру**:
-`sugar-qsp game.txt`
+`txt2web game.txt`
Создаст игру в game.html 2. **Я знаю что делаю**:
-`sugar-qsp game.txt -o game.html --body body.html --js jquery.js my-js-library.js --css styles/*.css`
+`txt2web game.txt -o game.html --body body.html --js jquery.js my-js-library.js --css styles/*.css`
Если вы знаете что делаете, то для вас смысл опций очевиден. `body.html` и `default.css` лежат в каталоге `extras`. 3. **Я - фронтендер!**
-`sugar-qsp game.txt -c -o game.js`
+`txt2web game.txt -c -o game.js`
Просто соберёт игру в Javascript файл который вы можете разместить на своём сайте как вам угодно. diff --git a/TODO b/TODO --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ +* Localization * Save-load game in slots * CLI build for Windows @@ -7,6 +8,7 @@ * Report duplicate label (in the parser) * reporting error lines at runtime (by storing them in every form in the parser * Report JUMP with missing label (in tagbody) +* Localizing parser errors... * Build Istreblenie * Build Цветохимия diff --git a/qlfile b/qlfile --- a/qlfile +++ b/qlfile @@ -1,4 +1,5 @@ ql alexandria +ql system-locale ql esrap ql parenscript ql flute @@ -8,3 +9,5 @@ ql anaphora ql named-readtables ql assoc-utils ql let-over-lambda +ql documentation-utils +ql trivial-indent diff --git a/qlfile.lock b/qlfile.lock --- a/qlfile.lock +++ b/qlfile.lock @@ -6,6 +6,10 @@ (:class qlot/source/ql:source-ql :initargs (:%version :latest) :version "ql-2020-04-27")) +("system-locale" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2020-04-27")) ("esrap" . (:class qlot/source/ql:source-ql :initargs (:%version :latest) @@ -38,3 +42,11 @@ (:class qlot/source/ql:source-ql :initargs (:%version :latest) :version "ql-2020-04-27")) +("documentation-utils" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2020-04-27")) +("trivial-indent" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2020-04-27")) diff --git a/qsp-txt2web.asd b/qsp-txt2web.asd new file mode 100644 --- /dev/null +++ b/qsp-txt2web.asd @@ -0,0 +1,24 @@ + +(defsystem qsp-txt2web + :description "QSP compiler to monolithic HTML page" + :depends-on (:alexandria :system-locale ;; General + :esrap ;; Parsing + :parenscript :flute ;; Codegening + ) + :pathname "src/" + :serial t + :components ((:file "package") + (:file "utils") + (:file "l10n") + (:file "walker") + + (:file "patches") + (:file "js-syms") + (:file "main-macros") + (:file "ps-macros") + (:file "api-macros") + (:file "intrinsic-macros") + + (:file "class") + (:file "main") + (:file "parser"))) diff --git a/src/class.lisp b/src/class.lisp --- a/src/class.lisp +++ b/src/class.lisp @@ -1,32 +1,14 @@ (in-package sugar-qsp) -(eval-when (:compile-toplevel :load-toplevel :execute) - (defun src-file (filename) - (uiop/pathname:merge-pathnames* - filename - (asdf:system-source-directory :sugar-qsp))) - (defun read-code-from-string (string) - (with-input-from-string (in string) - (let ((*package* *package*)) - `(progn - ,@(loop :for form := (read in nil :eof) - :until (eq form :eof) - :when (eq (first form) 'cl:in-package) - :do (setf *package* (find-package (second form))) - :else - :collect form))))) - (defun load-src (filename) - (alexandria:read-file-into-string (src-file filename)))) - (defclass compiler () ((body :accessor body :initform #.(load-src "extras/body.html")) (css :accessor css :initform (list #.(load-src "extras/default.css"))) (js :accessor js :initform (reverse (list - '#.(read-code-from-string (load-src "src/main.ps")) - '#.(read-code-from-string (load-src "src/api.ps")) - '#.(read-code-from-string (load-src "src/intrinsics.ps"))))) + '#.(read-progn-from-string (load-src "src/main.ps")) + '#.(read-progn-from-string (load-src "src/api.ps")) + '#.(read-progn-from-string (load-src "src/intrinsics.ps"))))) (compile :accessor compile-only :initarg :compile) (target :accessor target :initarg :target) (beautify :accessor beautify :initarg :beautify))) diff --git a/src/l10n.lisp b/src/l10n.lisp new file mode 100644 --- /dev/null +++ b/src/l10n.lisp @@ -0,0 +1,19 @@ + +(in-package txt2web) + +(defparameter *languages* (list "en" "ru")) + +(defparameter *l10n-strings* + (mapcan (lambda (lang) + (cons (intern (string-upcase lang) :keyword) + (read-code-from-string + (load-src + (concatenate 'string "strings/" lang ".sexp"))))) + *languages*)) + +(defun lformat (target key &rest args) + (let* ((lang (intern (string-upcase (first (system-locale:languages))) + :keyword)) + (strings (or (getf *l10n-strings* lang) + (getf *l10n-strings* :en)))) + (apply #'format target (getf strings key) args))) diff --git a/src/main-macros.lisp b/src/main-macros.lisp --- a/src/main-macros.lisp +++ b/src/main-macros.lisp @@ -1,8 +1,8 @@ -(in-package sugar-qsp.main) +(in-package txt2web.main) (defmacro+ps api-call (name &rest args) - `(,(intern (string-upcase name) "SUGAR-QSP.API") ,@args)) + `(,(intern (string-upcase name) "TXT2WEB.API") ,@args)) (defpsmacro has (key obj) `(chain ,obj (has-own-property ,key))) diff --git a/src/main.lisp b/src/main.lisp --- a/src/main.lisp +++ b/src/main.lisp @@ -1,5 +1,5 @@ -(in-package sugar-qsp) +(in-package txt2web) (defvar *app-name* "") @@ -8,7 +8,7 @@ (defun entry-point (args) (setf *app-name* (first args)) - (let ((*package* (find-package :sugar-qsp))) + (let ((*package* (find-package :txt2web))) (catch :terminate (let ((compiler (apply #'make-instance 'compiler (parse-opts (rest args))))) (write-compiled-file compiler)))) @@ -48,20 +48,10 @@ :beautify (getf data :beautify)))) (defun print-usage () - (format t "Usage: ~A [options]~%" *app-name*) - (format t "Options:~%") - (format t " -o - Output filename~%") - (format t " --js - List of extra .js files to include in the game~%") - (format t " --css - List of .css files to include in the game. Default is in extras/default.css~%") - (format t " --body - Alternative page body. Default is in extras/body.html~%") - (format t "~%") - (format t " -c - Just compile the game to a .js file without making it a full web page~%") - (format t " --beautify - Make the JS content pretty. By default it gets minified.~%") - (format t "~%") - (format t "Note that the files in extras/ are not actually used. They're just there for the reference")) + (lformat t :usage *app-name*)) (defun parse-file (filename) - (p:parse 'sugar-qsp-grammar + (p:parse 'txt2web-grammar (alexandria:read-file-into-string filename))) (defun report-error (fmt &rest args) @@ -80,13 +70,13 @@ (defmethod js-sources ((compiler compiler)) (let ((ps:*ps-print-pretty* (beautify compiler))) (cond ((beautify compiler) - (minify-package "SUGAR-QSP.MAIN" nil "qsp_") - (minify-package "SUGAR-QSP.API" nil "qsp_api_") - (minify-package "SUGAR-QSP.LIB" nil "qsp_lib_")) + (minify-package "TXT2WEB.MAIN" nil "qsp_") + (minify-package "TXT2WEB.API" nil "qsp_api_") + (minify-package "TXT2WEB.LIB" nil "qsp_lib_")) (t - (minify-package "SUGAR-QSP.MAIN" t "_") - (minify-package "SUGAR-QSP.API" t "a_") - (minify-package "SUGAR-QSP.LIB" t "l_"))) + (minify-package "TXT2WEB.MAIN" t "_") + (minify-package "TXT2WEB.API" t "a_") + (minify-package "TXT2WEB.LIB" t "l_"))) (format nil "~{~A~^~%~%~}" (mapcar #'ps:ps* (reverse (js compiler)))))) ;;; CSS diff --git a/src/main.ps b/src/main.ps --- a/src/main.ps +++ b/src/main.ps @@ -1,5 +1,5 @@ -(in-package sugar-qsp.main) +(in-package txt2web.main) ;;; Game session state (saved in savegames) ;; Variables @@ -37,14 +37,14 @@ (setf (@ window onload) (lambda () - (#.(intern "INIT-DOM" "SUGAR-QSP.API")) + (#.(intern "INIT-DOM" "TXT2WEB.API")) ;; For MSECCOUNT (setf *started-at (chain *date (now))) ;; For $COUNTER and SETTIMER - (#.(intern "SET-TIMER" "SUGAR-QSP.API") + (#.(intern "SET-TIMER" "TXT2WEB.API") *timer-interval) ;; Start the first game - (#.(intern "RUN-GAME" "SUGAR-QSP.API") + (#.(intern "RUN-GAME" "TXT2WEB.API") (chain *object (keys *games) 0)) (values))) diff --git a/src/model.lisp b/src/model.lisp deleted file mode 100644 --- a/src/model.lisp +++ /dev/null @@ -1,2 +0,0 @@ - -(in-package sugar-qsp) diff --git a/src/package.lisp b/src/package.lisp --- a/src/package.lisp +++ b/src/package.lisp @@ -1,10 +1,10 @@ (in-package cl-user) -(defpackage :sugar-qsp.js) +(defpackage :txt2web.js) -(defpackage :sugar-qsp.main - (:use :cl :ps :sugar-qsp.js) +(defpackage :txt2web.main + (:use :cl :ps :txt2web.js) (:export #:api-call #:by-id #:has @@ -25,8 +25,8 @@ #:walk-continue)) ;;; API functions -(defpackage :sugar-qsp.api - (:use :cl :ps :sugar-qsp.main :sugar-qsp.js) +(defpackage :txt2web.api + (:use :cl :ps :txt2web.main :txt2web.js) (:export #:with-frame #:with-call-args #:stash-state @@ -48,9 +48,9 @@ )) ;;; QSP library functions and macros -(defpackage :sugar-qsp.lib - (:use :cl :ps :sugar-qsp.main :sugar-qsp.js) - (:local-nicknames (#:api :sugar-qsp.api) +(defpackage :txt2web.lib + (:use :cl :ps :txt2web.main :txt2web.js) + (:local-nicknames (#:api :txt2web.api) (#:walker :code-walker)) (:export #:str #:exec #:qspblock #:qspfor #:game #:location #:qspcond #:qspvar #:set #:local #:jump @@ -92,17 +92,17 @@ #:openqst #:addqst #:killqst )) -(setf (ps:ps-package-prefix "SUGAR-QSP.MAIN") "qsp_") -(setf (ps:ps-package-prefix "SUGAR-QSP.API") "qsp_api_") -(setf (ps:ps-package-prefix "SUGAR-QSP.LIB") "qsp_lib_") +(setf (ps:ps-package-prefix "TXT2WEB.MAIN") "qsp_") +(setf (ps:ps-package-prefix "TXT2WEB.API") "qsp_api_") +(setf (ps:ps-package-prefix "TXT2WEB.LIB") "qsp_lib_") ;;; The compiler -(defpackage :sugar-qsp +(defpackage :txt2web (:use :cl) (:local-nicknames (#:p #:esrap) - (#:lib :sugar-qsp.lib) - (#:api :sugar-qsp.api) - (#:main :sugar-qsp.main) + (#:lib :txt2web.lib) + (#:api :txt2web.api) + (#:main :txt2web.main) (#:walker :code-walker)) (:export #:parse-file #:entry-point)) diff --git a/src/parser.lisp b/src/parser.lisp --- a/src/parser.lisp +++ b/src/parser.lisp @@ -1,5 +1,5 @@ -(in-package sugar-qsp) +(in-package txt2web) ;;;; Parses TXT source to an intermediate representation @@ -35,7 +35,7 @@ (not (find char " !:&=<>+-*/,'\"()[]{}")))) (defun intern-first (list) - (list* (intern (string-upcase (first list)) "SUGAR-QSP.LIB") + (list* (intern (string-upcase (first list)) "TXT2WEB.LIB") (rest list))) (eval-when (:compile-toplevel :load-toplevel :execute) @@ -46,7 +46,7 @@ (destructuring-bind (ws1 operator ws2 operand2) list (declare (ignore ws1 ws2)) - (list (intern (string-upcase operator) "SUGAR-QSP.LIB") operand2))) + (list (intern (string-upcase operator) "TXT2WEB.LIB") operand2))) (defun do-binop% (left-op other-ops) (if (null other-ops) @@ -129,7 +129,7 @@ (digit-char-p character))) (p:defrule identifier-raw (and id-first (* id-next)) (:lambda (list) - (intern (string-upcase (p:text list)) "SUGAR-QSP.LIB"))) + (intern (string-upcase (p:text list)) "TXT2WEB.LIB"))) (p:defrule identifier (not-qsp-keyword-p identifier-raw)) @@ -176,7 +176,7 @@ ;;; Location -(p:defrule sugar-qsp-grammar (and (* (or spaces #\newline)) +(p:defrule txt2web-grammar (and (* (or spaces #\newline)) (* location)) (:lambda (list) `(lib:game ,@(second list)))) @@ -431,7 +431,7 @@ (unless (<= ,min-arity (length arguments) ,max-arity) (error "Intrinsic ~A expects between ~A and ~A arguments but ~A were provided:~%~S" name ,min-arity ,max-arity (length arguments) arguments)) - (list* ',(intern (string sym) "SUGAR-QSP.LIB") arguments)))) + (list* ',(intern (string sym) "TXT2WEB.LIB") arguments)))) (defintrinsics (intrinsic returning-intrinsic non-returning-intrinsic) ;; Transitions diff --git a/src/ps-macros.lisp b/src/ps-macros.lisp --- a/src/ps-macros.lisp +++ b/src/ps-macros.lisp @@ -1,5 +1,5 @@ -(in-package sugar-qsp.lib) +(in-package txt2web.lib) ;;;; Parenscript macros which make the parser's intermediate ;;;; representation directly compilable by Parenscript diff --git a/src/utils.lisp b/src/utils.lisp new file mode 100644 --- /dev/null +++ b/src/utils.lisp @@ -0,0 +1,24 @@ + +(in-package txt2web) + +(defun src-file (filename) + (uiop/pathname:merge-pathnames* + filename + (asdf:system-source-directory :txt2web))) + +(defun read-progn-from-string (string) + `(progn + ,@(read-code-from-string string))) + +(defun read-code-from-string (string) + (with-input-from-string (in string) + (let ((*package* *package*)) + (loop :for form := (read in nil :eof) + :until (eq form :eof) + :when (eq (first form) 'cl:in-package) + :do (setf *package* (find-package (second form))) + :else + :collect form)))) + +(defun load-src (filename) + (alexandria:read-file-into-string (src-file filename))) diff --git a/src/writer.lisp b/src/writer.lisp --- a/src/writer.lisp +++ b/src/writer.lisp @@ -1,5 +1,5 @@ -(in-package sugar-qsp) +(in-package txt2web) ;;; 1. Generates parenscript source to write to js ;;; 2. Collects everything into complete file, leaving a neatly marked diff --git a/strings/en.sexp b/strings/en.sexp new file mode 100644 --- /dev/null +++ b/strings/en.sexp @@ -0,0 +1,12 @@ +(:usage "Usage: ~A [options] +Options: + -o - Output filename + --js - List of extra .js files to include in the game + --css - List of .css files to include in the game. Default is in extras/default.css + --body - Alternative page body. Default is in extras/body.html + + -c - Just compile the game to a .js file without making it a full web page + --beautify - Make the JS content pretty. By default it gets minified. + +Note that the files in extras/ are not actually used. They're just there for the reference~%") + diff --git a/strings/ru.sexp b/strings/ru.sexp new file mode 100644 --- /dev/null +++ b/strings/ru.sexp @@ -0,0 +1,12 @@ +(:usage "Использование: ~A [options] +Опции: + -o <имя файла> - Имя .html файла для записи скомпилированной игры + --js <имена файлов...> - Список дополнительных .js файлов + --css <имена файлов...> - Список .css файлов. Стиль по-умолчанию - в файле extras/default.css + --body <имя файла> - Альтернативное тело страницы. Тело по-умолчанию - в файле extras/body.html + + -c - Просто скомпилировать игру в .js файл, не компонуя полную .html страницу + --beautify - Не минифицировать .js скрипты + +Файлы в extras на самом деле компилятором не используются. Используйте только как образец.~%") + diff --git a/sugar-qsp.asd b/sugar-qsp.asd deleted file mode 100644 --- a/sugar-qsp.asd +++ /dev/null @@ -1,20 +0,0 @@ - -(defsystem sugar-qsp - :description "QSP compiler to monolithic HTML page" - :depends-on (:alexandria ;; General - :esrap ;; Parsing - :parenscript :flute ;; Codegening - ) - :pathname "src/" - :serial t - :components ((:file "package") - (:file "walker") - (:file "patches") - (:file "js-syms") - (:file "main-macros") - (:file "ps-macros") - (:file "api-macros") - (:file "intrinsic-macros") - (:file "class") - (:file "main") - (:file "parser")))