diff --git a/.gitignore b/.gitignore index 8ce2733..d00d861 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ truth-table -truth-table-webserver \ No newline at end of file +truth-table-web-server \ No newline at end of file diff --git a/Makefile b/Makefile index 5227158..6f436de 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ SBCL ?= sbcl BASE_FILES=base-packages.lisp parse.lisp table.lisp typeset.lisp eval.lisp \ arguments.lisp CLI_FILES=cli.lisp +WEB_FILES=web.lisp all: cli @@ -10,6 +11,10 @@ cli: truth-table truth-table: build.lisp truth-table.asd $(BASE_FILES) $(CLI_FILES) $(SBCL) --load build.lisp --eval '(cli)' +web: truth-table-web-server +truth-table-web-server: build.lisp truth-table.asd $(BASE_FILES) $(WEB_FILES) + $(SBCL) --load build.lisp --eval '(web)' + clean: rm -f truth-table diff --git a/build.lisp b/build.lisp index 199d6f5..5354b9f 100644 --- a/build.lisp +++ b/build.lisp @@ -16,3 +16,14 @@ :executable t :save-runtime-options t :toplevel (intern "TOPLEVEL" :truth-table/cli))) + +(defun web () + "Build web CLI application executable." + (sb-ext:disable-debugger) + (asdf:load-system :truth-table/web) + (require :truth-table/web) + (sb-ext:save-lisp-and-die + "truth-table-web-server" + :executable t + :save-runtime-options t + :toplevel (intern "TOPLEVEL" :truth-table/web))) diff --git a/truth-table-web-wrapper b/truth-table-web-wrapper deleted file mode 100755 index 2cefdf4..0000000 --- a/truth-table-web-wrapper +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -SBCL=${SBCL:-sbcl} - -# Use Quicklisp instead of ASDF to silence the output -exec ${SBCL} --noinform \ - --eval '(format *error-output* "Loading systems...~%")' \ - --eval '(ql:quickload :truth-table/web :silent t)' \ - --eval '(truth-table/web:toplevel)' - "${@}" diff --git a/truth-table.asd b/truth-table.asd index c9e0357..08a689e 100644 --- a/truth-table.asd +++ b/truth-table.asd @@ -36,6 +36,7 @@ #:reblocks #:reblocks-ui #:reblocks-lass + #:clack-handler-hunchentoot #:truth-table/base #:truth-table/args) :serial t diff --git a/web.lisp b/web.lisp index 6b2b201..41b249c 100644 --- a/web.lisp +++ b/web.lisp @@ -37,45 +37,47 @@ (defparameter *default-port* 8000) (defparameter *default-address* "127.0.0.1") (defparameter *default-prefix* "/") +(defparameter *max-input-length* 128 + "The maximum length of any input proposition.") (defwidget help-overlay () () (:documentation "Simple class to handle holding the help overlay.")) (defmethod render ((overlay help-overlay)) - (with-html + (with-html () (:div :id "help-table-wrapper" - (:div :id "help-header-wrapper" - (:span :id "help-header" "Help") - (:span :id "help-close-button" - :onclick "document.querySelector(\".help-overlay\").style.display = \"none\"" - "Close")) - (:table ;:style "margin-bottom: 10px;" - (:tr (:th "Operator") (:th "Syntax")) - (loop for ((sym (name . nics) desc (examples)) . rest-desc) - = *operator-descriptions* then rest-desc - for ((_sym . syntax) . rest-st) - = *operator-symbol-table* then rest-st - while sym - do - (:tr - (:td name) - (:td (format nil "~{~a~^, ~}" (sort (copy-list syntax) - 'string<))))) - (:tr (:th "Operand") (:th "Syntax")) - (loop for (sym . syntax) in *operand-symbol-table* do - (:tr - (:td (string-downcase (symbol-name sym))) - (:td (format nil "~{~a~^, ~}" (sort (copy-list syntax) - 'string<)))))) - (:p "You can input multiple propositions by separating them with" - "commas (,):" - (:br) - (:code "ab,cd")) - (:p "Two operands next to each other is treated as an 'implicit and'" - "(unless this feature is disabled):" - (:br) - (:code (:raw "abc|d = a ∧ b ∧ c ∨ d")))))) + (:div :id "help-header-wrapper" + (:span :id "help-header" "Help") + (:span :id "help-close-button" + :onclick "document.querySelector(\".help-overlay\").style.display = \"none\"" + "Close")) + (:table ;:style "margin-bottom: 10px;" + (:tr (:th "Operator") (:th "Syntax")) + (loop for ((sym (name . nics) desc (examples)) . rest-desc) + = *operator-descriptions* then rest-desc + for ((_sym . syntax) . rest-st) + = *operator-symbol-table* then rest-st + while sym + do + (:tr + (:td name) + (:td (format nil "~{~a~^, ~}" (sort (copy-list syntax) + 'string<))))) + (:tr (:th "Operand") (:th "Syntax")) + (loop for (sym . syntax) in *operand-symbol-table* do + (:tr + (:td (string-downcase (symbol-name sym))) + (:td (format nil "~{~a~^, ~}" (sort (copy-list syntax) + 'string<)))))) + (:p "You can input multiple propositions by separating them with" + "commas (,):" + (:br) + (:code "ab,cd")) + (:p "Two operands next to each other is treated as an 'implicit and'" + "(unless this feature is disabled):" + (:br) + (:code (:raw "abc|d = a ∧ b ∧ c ∨ d")))))) (defmethod get-dependencies ((overlay help-overlay)) (append @@ -168,27 +170,27 @@ reblocks bug.") :pretty-print pretty-print :latin-truths latin-truths))))) (when data - (with-html + (with-html () (:div :class "label" "Output:") (:span :id "output-span" - ;; there seems to be a bug in reblocks that means you have to pass - ;; the second argument to `make-js-action' - (:button :onclick (make-js-action - (lambda (&key &allow-other-keys) - (truth-table-toggle-output table) - (update table)) - :args *blank-hash-table*) - :id "output-expander-button" - (if output-visible - "⏷" - "⏵")) - (if (or pretty-print - (equal format "ascii") - (equal format "unicode")) - (:pre :id "output-area" :hidden (not output-visible) - other-text) - (:code :id "output-area" :hidden (not output-visible) - other-text))) + ;; there seems to be a bug in reblocks that means you have to pass + ;; the second argument to `make-js-action' + (:button :onclick (make-js-action + (lambda (&key &allow-other-keys) + (truth-table-toggle-output table) + (update table)) + :args *blank-hash-table*) + :id "output-expander-button" + (if output-visible + "⏷" + "⏵")) + (if (or pretty-print + (equal format "ascii") + (equal format "unicode")) + (:pre :id "output-area" :hidden (not output-visible) + other-text) + (:code :id "output-area" :hidden (not output-visible) + other-text))) (:div :class "label" "HTML Preview:")) (princ html-text reblocks/html:*stream*)))) nil) @@ -240,11 +242,11 @@ reblocks bug.") (defmethod render ((box error-box)) "Render BOX." - (with-html + (with-html () (with-slots (message) box - (when message - (:div - (:pre message)))))) + (when message + (:div + (:pre message)))))) (defmethod get-dependencies ((box error-box)) (append @@ -340,9 +342,17 @@ reblocks bug.") (update table) (update error-box))) +(declaim (inline handle-too-long-error)) +(defun handle-too-long-error (page length) + "Handle requests that were too long by showing the user an error message." + (setf (error-box-message (page-error-box page)) + (format nil "Input of length ~S is longer than maximum of ~S" + length *max-input-length*)) + (update (page-error-box page))) + (defmethod render ((page page)) "Render PAGE." - (with-html + (with-html () (:body (with-slots (table error-box help-overlay) page (render help-overlay) @@ -352,15 +362,17 @@ reblocks bug.") include-vars subexps latin pretty &allow-other-keys) - (handle-generate-request - page :prop-str prop-str - :implicit-and implicit-and - :multi-char-names multi-char-names - :format format - :include-vars include-vars - :subexps subexps - :pretty pretty - :latin latin))) + (if (> (length prop-str) *max-input-length*) + (handle-too-long-error page (length prop-str)) + (handle-generate-request + page :prop-str prop-str + :implicit-and implicit-and + :multi-char-names multi-char-names + :format format + :include-vars include-vars + :subexps subexps + :pretty pretty + :latin latin)))) (:div :id "main-controls-wrapper" (:input :id "prop-input-field" :type "text" @@ -487,6 +499,14 @@ was not specified." *default-port*)) *default-port*))) +(defun wait-for-hunchentoot () + "Wait for the hunchentoot webserver to exit." + (bordeaux-threads:join-thread + (find-if (lambda (thread) + (equal "clack-handler-hunchentoot" + (bordeaux-threads:thread-name thread))) + (bordeaux-threads:all-threads)))) + (defun main (argv) "The main entry point to the program. ARGV is the list of command line arguments." @@ -497,7 +517,7 @@ arguments." (format *error-output* "~a~%" c) (setq cmdline-error t) (continue)))) - (destructuring-bind ((&rest norm-args) &rest opts) + (destructuring-bind (norm-args &rest opts) (parse-command-line *command-line-spec* argv) ;; parse the options here so that continue still exits properly (let ((port (determine-port opts)) @@ -517,9 +537,11 @@ arguments." :port port :interface address :debug (option-value 'debug opts) - :disable-welcome-app t)))))) + :disable-welcome-app t + :server-type :hunchentoot)))))) (defun toplevel () "Top-level function to be passed to `save-lisp-and-die'." #+sbcl (sb-ext:disable-debugger) - (main (uiop:command-line-arguments))) + (main (uiop:command-line-arguments)) + (wait-for-hunchentoot))