Make web server

This commit is contained in:
Alexander Rosenberg 2024-09-05 14:46:05 -07:00
parent 9e35fed164
commit c6cadc3123
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
11 changed files with 525 additions and 142 deletions

View File

@ -1,20 +1,16 @@
LISP ?= sbcl SBCL ?= sbcl
BASE_FILES=packages.lisp parse.lisp table.lisp typeset.lisp eval.lisp BASE_FILES=base-packages.lisp parse.lisp table.lisp typeset.lisp eval.lisp \
arguments.lisp
CLI_FILES=cli.lisp CLI_FILES=cli.lisp
WEB_FILES=web.lisp
all: cli web all: cli
cli: truth-table cli: truth-table
truth-table: build.lisp truth-table.asd $(BASE_FILES) $(CLI_FILES) truth-table: build.lisp truth-table.asd $(BASE_FILES) $(CLI_FILES)
$(LISP) --load build.lisp --eval '(cli)' $(SBCL) --load build.lisp --eval '(cli)'
web: truth-table-webserver
truth-table-webserver: build.lisp truth-table.asd $(BASE_FILES) $(WEB_FILES)
$(LISP) --load build.lisp --eval '(web)'
clean: clean:
rm -f truth-table truth-table-webserver rm -f truth-table
.PHONY: all cli clean .PHONY: all cli clean

View File

@ -130,7 +130,8 @@ arguments will be placed in a list at the beginning of the alist."
finally (setf output-other (nconc (nreverse rest) output-other))) finally (setf output-other (nconc (nreverse rest) output-other)))
(cons (nreverse output-other) output-opts))) (cons (nreverse output-other) output-opts)))
(defun print-usage (stream spec exec-name &optional general-args) (defun print-usage (stream spec exec-name &key general-args
(print-astrisk t))
"Print the command line usage corresponding to SPEC to STREAM." "Print the command line usage corresponding to SPEC to STREAM."
(format stream "usage: ~a [options]~@[ ~a~]~%~%" exec-name general-args) (format stream "usage: ~a [options]~@[ ~a~]~%~%" exec-name general-args)
(loop with longest-option (loop with longest-option
@ -149,8 +150,9 @@ arguments will be placed in a list at the beginning of the alist."
(format stream " ~v@<~@[-~c~]~@[, ~*~]~@[--~a~]~@[=<arg>~*~]~> ~a~%" (format stream " ~v@<~@[-~c~]~@[, ~*~]~@[--~a~]~@[=<arg>~*~]~> ~a~%"
longest-option longest-option
short (or short long) long has-arg-p desc)) short (or short long) long has-arg-p desc))
(format stream "~%The choice surrounded by '*' is the default. Arguments to long (format stream
options are also required for their short variant.~%")) "~%~@[The choice surrounded by '*' is the default. ~]Arguments to long~:[ ~;~%~]~
options are also required for their short variant.~%" print-astrisk print-astrisk))
(defun option-value (opt opts) (defun option-value (opt opts)
"Get the value of command line option OPT from OTPS, which is an alist as "Get the value of command line option OPT from OTPS, which is an alist as

View File

@ -41,6 +41,7 @@
#:latex-var-name-transform #:latex-var-name-transform
#:typeset-proposition #:typeset-proposition
#:convert-truth-table-to-latex #:convert-truth-table-to-latex
#:convert-truth-table-to-html
#:*table-border-ascii-alist* #:*table-border-ascii-alist*
#:*table-border-unicode-alist* #:*table-border-unicode-alist*
#:typeset-truth-table #:typeset-truth-table
@ -56,16 +57,6 @@
#:unknown-option-error #:unknown-option-error
#:option-no-arg-error #:option-no-arg-error
#:no-input-error #:no-input-error
#:parse-command-line
#:print-usage #:print-usage
#:option-value)) #:option-value
#:parse-command-line))
(defpackage #:truth-table/cli
(:use #:common-lisp #:truth-table/base
#:truth-table/args)
(:export #:toplevel #:main))
(defpackage #:truth-table/web
(:use #:common-lisp #:truth-table/base
#:truth-table/args)
(:export #:toplevel #:main))

View File

@ -1,15 +1,14 @@
;; This file should be run as follows: ;; This file should be run as follows:
;; sbcl --load build.lisp --eval '(<BUILD FUNCTION>)' ;; sbcl --load build.lisp --eval '(cli)'
;; where <BUILD FUNCTION> is either `cli' or `web' ;; where <BUILD FUNCTION> is either `cli' or `web'
#-sbcl (error "Only SBCL is supported right now") #-sbcl (error "Only SBCL is supported right now")
(sb-ext:disable-debugger)
(require :asdf) (require :asdf)
(defun cli () (defun cli ()
"Build the CLI application executable." "Build the CLI application executable."
(sb-ext:disable-debugger)
(asdf:load-system :truth-table/cli) (asdf:load-system :truth-table/cli)
(require :truth-table/cli) (require :truth-table/cli)
(sb-ext:save-lisp-and-die (sb-ext:save-lisp-and-die
@ -17,13 +16,3 @@
:executable t :executable t
:save-runtime-options t :save-runtime-options t
:toplevel (intern "TOPLEVEL" :truth-table/cli))) :toplevel (intern "TOPLEVEL" :truth-table/cli)))
(defun web ()
"Build the web server executable."
(asdf:load-system :truth-table/web)
(require :truth-table/web)
(sb-ext:save-lisp-and-die
"truth-table-webserver"
:executable t
:save-runtime-options t
:toplevel (intern "TOPLEVEL" :truth-table/web)))

View File

@ -13,6 +13,11 @@
;; ;;
;; You should have received a copy of the GNU General Public License ;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
(defpackage #:truth-table/cli
(:use #:common-lisp #:truth-table/base
#:truth-table/args)
(:export #:toplevel #:main))
(in-package :truth-table/cli) (in-package :truth-table/cli)
(defun eval-and-typeset-propositions (prop-strs &key (format "unicode") (defun eval-and-typeset-propositions (prop-strs &key (format "unicode")
@ -43,7 +48,8 @@ functions involved in evaluating and typesetting."
(defparameter *command-line-spec* (defparameter *command-line-spec*
'((#\h "help" help nil "print this message, then exit") '((#\h "help" help nil "print this message, then exit")
(#\f "format" format t "specify the output format (*unicode*, ascii, or latex)") (#\f "format" format t
"specify the output format (*unicode*, ascii, latex, or html)")
(#\s "subexps" subexps nil "include sub-expressions in the output table") (#\s "subexps" subexps nil "include sub-expressions in the output table")
(#\n "no-vars" no-vars nil "do not include variables in the output table") (#\n "no-vars" no-vars nil "do not include variables in the output table")
(#\m "multi-char" multi-char nil "allow multi-character variable names") (#\m "multi-char" multi-char nil "allow multi-character variable names")
@ -70,8 +76,8 @@ arguments."
(parse-command-line *command-line-spec* argv) (parse-command-line *command-line-spec* argv)
(cond (cond
((option-value 'help opts) ((option-value 'help opts)
(print-usage t *command-line-spec* (print-usage t *command-line-spec* "truth-table"
"truth-table" "<propositions...>") :general-args "<propositions...>")
(uiop:quit (if cmdline-error 1 0))) (uiop:quit (if cmdline-error 1 0)))
((null prop-strs) ((null prop-strs)
(cerror *cli-parse-continue-string* 'no-input-error)) (cerror *cli-parse-continue-string* 'no-input-error))

View File

@ -258,79 +258,83 @@ found variables."
(operands '()) (operands '())
(oper-poses '()) (oper-poses '())
(last-was-operand nil)) (last-was-operand nil))
(dotokens (token token-pos str) (flet ((push-operator (value pos)
(:multi-char-names multi-char-names)
(destructuring-bind (type value) (interpret-token token)
(cond
;; unknown type
((not type)
(error 'proposition-parse-error
:position token-pos
:proposition str
:message "unknown token"))
;; operand
((eq type 'operand)
(when last-was-operand
;; two operands next to each other often means "and" implicitly
(unless implicit-and
(error 'proposition-parse-error
:position token-pos
:proposition str
:message "expected operator, found operand"))
(multiple-value-bind (new-oper new-opan pop-count) (multiple-value-bind (new-oper new-opan pop-count)
(apply-lower-precedent (operator-precedence 'and) (apply-lower-precedent (operator-precedence value)
operators operands str token-pos) operators operands str pos)
(setq operators new-oper (setq operators new-oper
operands new-opan) operands new-opan)
(dotimes (i pop-count) (dotimes (i pop-count)
(pop oper-poses))) (pop oper-poses)))
(push 'and operators) (push value operators)
(push pos oper-poses)))
(dotokens (token token-pos str)
(:multi-char-names multi-char-names)
(destructuring-bind (type value) (interpret-token token)
(cond
;; unknown type
((not type)
(error 'proposition-parse-error
:position token-pos
:proposition str
:message "unknown token"))
;; operand
((eq type 'operand)
(when last-was-operand
;; two operands next to each other often means "and" implicitly
(unless implicit-and
(error 'proposition-parse-error
:position token-pos
:proposition str
:message "expected operator, found operand"))
(push-operator 'and token-pos))
(unless (member value '(true false))
(pushnew value found-vars :test 'equal))
(push value operands)
(setq last-was-operand t))
((eq value 'open-paren)
(when last-was-operand
;; an open parenthesis directly following an operator is also a
;; signal of an implicit "and"
(unless implicit-and
(error 'proposition-parse-error
:position token-pos
:proposition str
:message "expected operator, found open parenthesis"))
(push-operator 'and token-pos)
(setq last-was-operand nil))
(push value operators)
(push token-pos oper-poses)) (push token-pos oper-poses))
(unless (member value '(true false)) ;; close paren doesn't touch `last-was-operand'
(pushnew value found-vars :test 'equal)) ((eq value 'close-paren)
(push value operands) (loop while (not (eq (car operators) 'open-paren))
(setq last-was-operand t)) when (null operators) do
;; open and close paren don't touch `last-was-operand' (error 'proposition-parse-error
((eq value 'open-paren) :position token-pos
(push value operators) :proposition str
(push token-pos oper-poses)) :message "no matching open parenthesis")
((eq value 'close-paren) do
(loop while (not (eq (car operators) 'open-paren)) (setf (values operators operands)
when (null operators) do (apply-one-operator operators operands
(error 'proposition-parse-error str token-pos))
:position token-pos (pop oper-poses))
:proposition str ;; remove the open-paren
:message "no matching open parenthesis") (pop operators)
do (pop oper-poses))
(setf (values operators operands) ;; operator
(apply-one-operator operators operands (t
str token-pos)) (push-operator value token-pos)
(pop oper-poses)) (setq last-was-operand nil)))))
;; remove the open-paren (loop while operators
(pop operators) for oper-pos = (pop oper-poses)
(pop oper-poses)) when (eq (car operators) 'open-paren) do
;; operator (error 'proposition-parse-error
(t :message "no matching closing parenthesis"
(multiple-value-bind (new-oper new-opan pop-count) :proposition str
(apply-lower-precedent (operator-precedence value) :position oper-pos)
operators operands str token-pos) do
(setq operators new-oper (setf (values operators operands)
operands new-opan) (apply-one-operator operators operands
(dotimes (i pop-count) str oper-pos)))
(pop oper-poses))) ;; return variables in the order we found them
(push value operators) (values (car operands) (nreverse found-vars)))))
(push token-pos oper-poses)
(setq last-was-operand nil)))))
(loop while operators
for oper-pos = (pop oper-poses)
when (eq (car operators) 'open-paren) do
(error 'proposition-parse-error
:message "no matching closing parenthesis"
:proposition str
:position oper-pos)
do
(setf (values operators operands)
(apply-one-operator operators operands
str oper-pos)))
;; return variables in the order we found them
(values (car operands) (nreverse found-vars))))

View File

@ -15,13 +15,18 @@
;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
(in-package :truth-table/base) (in-package :truth-table/base)
(defun flatten-tree (tree)
"Flatten TREE into a single list."
(if (atom tree)
(list tree)
(apply 'append (mapcar 'flatten-tree tree))))
(defun discover-variables (prop) (defun discover-variables (prop)
"Return a list of all the variables in PROP, in left to right order." "Return a list of all the variables in PROP, in left to right order."
(cond (let ((vars))
((stringp prop) (dolist (item (flatten-tree prop) (nreverse vars))
(list prop)) (when (stringp item)
((listp prop) (pushnew item vars :test 'equal)))))
(mapcan 'discover-variables (cdr prop)))))
(defun permute-variables (vars) (defun permute-variables (vars)
"Return a list of alists, each with a different permutation of VARS." "Return a list of alists, each with a different permutation of VARS."
@ -38,17 +43,21 @@
"Evaluate PROP with all possible combinations of truth values for its "Evaluate PROP with all possible combinations of truth values for its
variables. If supplied VARS should be a list of all the know variables in PORP, variables. If supplied VARS should be a list of all the know variables in PORP,
if it is excluded, `discover-variables' will be used to generate it." if it is excluded, `discover-variables' will be used to generate it."
(if (null vars) (cond
(list (list (cons prop (eval-proposition prop '())))) ((null prop)
(loop for perm in (permute-variables vars) (list (list (cons nil nil))))
for (value sub-map) = (multiple-value-list ((null vars)
(eval-proposition prop perm)) (list (list (cons prop (eval-proposition prop '())))))
collect (t
(append (when include-vars perm) (loop for perm in (permute-variables vars)
(when include-intermediate for (value sub-map) = (multiple-value-list
(delete-if (lambda (item) (equal prop (car item))) (eval-proposition prop perm))
sub-map)) collect
(list (cons prop value)))))) (append (when include-vars perm)
(when include-intermediate
(reverse (delete-if (lambda (item) (equal prop (car item)))
sub-map)))
(list (cons prop value)))))))
(defun extract-truth-table-expressions (table) (defun extract-truth-table-expressions (table)
"Extract each expression from TABLE and return them as a list. "Extract each expression from TABLE and return them as a list.

5
truth-table-web-wrapper Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
exec sbcl --noinform \
--eval '(asdf:load-system :truth-table/web)' \
--eval '(truth-table/web:toplevel)' "${@}"

View File

@ -9,7 +9,7 @@
:depends-on () :depends-on ()
:serial t :serial t
:components :components
((:file "packages") ((:file "base-packages")
(:file "parse") (:file "parse")
(:file "eval") (:file "eval")
(:file "table") (:file "table")
@ -19,7 +19,7 @@
:depends-on (#:uiop) :depends-on (#:uiop)
:serial t :serial t
:components :components
((:file "packages") ((:file "base-packages")
(:file "arguments"))) (:file "arguments")))
(defsystem #:truth-table/cli (defsystem #:truth-table/cli
@ -29,17 +29,15 @@
#:truth-table/args) #:truth-table/args)
:serial t :serial t
:components :components
((:file "packages") ((:file "cli")))
(:file "cli")))
(defsystem #:truth-table/web (defsystem #:truth-table/web
:depends-on (#:uiop :depends-on (#:uiop
#:with-user-abort
#:reblocks #:reblocks
#:reblocks-ui #:reblocks-ui
#:reblocks-lass
#:truth-table/base #:truth-table/base
#:truth-table/args) #:truth-table/args)
:serial t :serial t
:components :components
((:file "packages") ((:file "web")))
(:file "web")))

View File

@ -71,6 +71,22 @@
(false . "\\bot")) (false . "\\bot"))
"Lookup table mapping operators to their LaTeX representation.") "Lookup table mapping operators to their LaTeX representation.")
(defparameter *operator-html-lookup-alist*
'((and . "&and;")
(nand . "&uarr;")
(or . "&or;")
(nor . "&darr;")
(xor . "&oplus;")
(not . "&not;")
(implies . "&rarr;")
(converse . "&larr;")
(iff . "&harr;")
(open-paren . "(")
(close-paren . ")")
(true . "&top;")
(false . "&perp;"))
"Lookup table mapping operators to their HTML representation.")
(defun latex-var-name-transform (name) (defun latex-var-name-transform (name)
"Transform NAME so that it is escaped for use in LaTeX." "Transform NAME so that it is escaped for use in LaTeX."
(format nil "{~{~A~}}" (loop for char across name (format nil "{~{~A~}}" (loop for char across name
@ -83,6 +99,16 @@
else else
collect char))) collect char)))
(defun html-var-name-transform (name)
"Transform NAME so that it is escaped for use in HTML."
(format nil "~{~A~}" (loop for char across name
if (eq char #\<)
collect "&lt;"
else if (eq char #\>)
collect "&gt;"
else
collect char)))
(defun typeset-proposition (expr &optional (defun typeset-proposition (expr &optional
(lookup-table *operator-ascii-lookup-alist*) (lookup-table *operator-ascii-lookup-alist*)
var-name-transform var-name-transform
@ -93,6 +119,9 @@ be a table mapping operators to their textual representation. VAR-NAME-TRANSFORM
escape it for use in the target typesetting system. PARENT-PERC is for internal escape it for use in the target typesetting system. PARENT-PERC is for internal
use (it controls when parentheses are applied.)" use (it controls when parentheses are applied.)"
(cond (cond
;; expr is empty
((null expr)
"")
;; expr is a variable name ;; expr is a variable name
((stringp expr) ((stringp expr)
(if var-name-transform (if var-name-transform
@ -154,6 +183,35 @@ between each row."
typeset-exprs typeset-exprs
(extract-truth-table-values table)))) (extract-truth-table-values table))))
(defun format-html-properties-alist (props)
"Format PROPS, a list of conses, as a list of HTML properties."
(loop for (name . value) in props
when (eq value t)
collect (format nil "~A=\"\"" name)
else when value
collect (format nil "~A=~S" name (princ-to-string value))))
(defun convert-truth-table-to-html (table &key class id more-props)
"Convert TABLE, which should be a truth table as returned by
`create-truth-table' to HTML. CLASS and ID are their respective HTML
properties. MORE-PROPS is an alist mapping properties to values.
NOTE: though the overall order does not matter, the order must be the same
between each row."
(let ((typeset-exprs (mapcar (lambda (expr)
(typeset-proposition
expr *operator-html-lookup-alist*
'html-var-name-transform))
(extract-truth-table-expressions table))))
(format nil "~
<table~@[ class=~s~]~@[ id=~s~]~{ ~A~}>~
<tr>~{<th>~A</th>~}</tr>~
~{<tr>~{<td>~:[&perp;~;&top;~]</td>~}</tr>~}~
</table>"
class id (format-html-properties-alist more-props)
typeset-exprs
(extract-truth-table-values table))))
(defparameter *table-border-ascii-alist* (defparameter *table-border-ascii-alist*
'((vertical . #\|) '((vertical . #\|)
(horizontal . #\-) (horizontal . #\-)
@ -255,7 +313,7 @@ between each row."
(cdr (assoc 'bottom-right box-lookup-table)))))) (cdr (assoc 'bottom-right box-lookup-table))))))
(defparameter *known-formats* (defparameter *known-formats*
'("unicode" "ascii" "latex") '("unicode" "ascii" "latex" "html")
"The known formats that `typeset-table-to-format' can take.") "The known formats that `typeset-table-to-format' can take.")
(defun typeset-table-to-format (table format) (defun typeset-table-to-format (table format)
@ -269,4 +327,6 @@ between each row."
*table-border-ascii-alist*)) *table-border-ascii-alist*))
((equal format "latex") ((equal format "latex")
(convert-truth-table-to-latex table)) (convert-truth-table-to-latex table))
((equal format "html")
(convert-truth-table-to-html table))
(t (error 'table-format-error :format format)))) (t (error 'table-format-error :format format))))

337
web.lisp
View File

@ -13,18 +13,341 @@
;; ;;
;; You should have received a copy of the GNU General Public License ;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
(defpackage #:truth-table/web
(:use #:common-lisp #:truth-table/base
#:truth-table/args)
(:export #:toplevel #:main)
(:import-from #:reblocks/app
#:defapp)
(:import-from #:reblocks/html
#:with-html)
(:import-from #:reblocks/widget
#:defwidget
#:update
#:render)
(:import-from #:reblocks-ui/form
#:with-html-form)
(:import-from #:reblocks/actions
#:make-js-action)
(:import-from #:reblocks/dependencies
#:get-dependencies))
(in-package :truth-table/web) (in-package :truth-table/web)
(defparameter *default-port* 8000)
(defparameter *default-address* "127.0.0.1")
(defwidget truth-table ()
((data :initform nil
:accessor truth-table-data)
(format :initform "html"
:accessor truth-table-format)
(output-visible :initform t
:accessor truth-table-output-visible))
(:documentation "Class to hold the generated table."))
(defmethod truth-table-toggle-output ((table truth-table))
"Toggle the visibility of the output box of TABLE."
(with-slots (output-visible) table
(setf output-visible (not output-visible))))
(defparameter *blank-hash-table* (make-hash-table)
"Blank hash table to pass to make-js-action because of what seems to be a
reblocks bug.")
(defmethod render ((table truth-table))
"Render TABLE."
(with-slots (data format output-visible) table
(let* ((html-text (convert-truth-table-to-html data))
(other-text
(when output-visible
(if (equal format "html")
html-text
(typeset-table-to-format data format)))))
(when data
(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 (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)
(defmethod get-dependencies ((widget truth-table))
(append
(list
(reblocks-lass:make-dependency
'(.truth-table
(.label
:font-size "large"
:font-weight "bold"
:margin-top "5px"
:margin-bottom "5px")
(|#output-span|
:display "flex"
(button :margin-right "10px"))
(|#output-expander-button|
:margin-bottom "auto"
:font-size "xx-large"
:background "none"
:border "none"
:cursor "pointer")
(|#output-area|
:background "lightgrey"
:flex-grow "1"
:padding "5px"
:border-style "solid"
:border-color "black"
:border-width "1px"
:max-height "25vh"
:overflow-y "scroll")
(table
:margin "auto"
:border-collapse "collapse"
:border-spacing "0px"
((:or th td)
:padding "3px"
:text-align "center"
:border-style "solid"
:border-width "1px"
:border-color "black")))))
(call-next-method)))
(defwidget error-box ()
((message :initform nil
:accessor error-box-message))
(:documentation "Class to hold various error messages."))
(defmethod render ((box error-box))
"Render BOX."
(with-html
(with-slots (message) box
(when message
(:div
(:pre message))))))
(defmethod get-dependencies ((box error-box))
(append
(list
(reblocks-lass:make-dependency
`(.error-box
(div
:border-width "1px"
:border-style "solid"
:border-color "black"
(pre
:margin "0px"
:padding-top "5px"
:font-size "large"
:border-left-style "solid"
:border-left-color "red"
:border-left-width "10px"
:padding-left "5px")))))
(call-next-method)))
(defwidget page ()
((table :initform (make-instance 'truth-table)
:accessor page-table)
(error-box :initform (make-instance 'error-box)
:accessor page-error-box))
(:documentation "The root of the whole page"))
(defmethod handle-generate-request ((page page)
&key prop-str implicit-and multi-char-names
format include-vars subexps)
"Handler for requests to generate truth tables."
(with-slots (table error-box) page
(setf (truth-table-format table) format
(error-box-message error-box) nil)
(if (not (zerop (length prop-str)))
(handler-case
(multiple-value-bind (parsed-exp vars)
(parse-proposition-string
prop-str
:implicit-and implicit-and
:multi-char-names multi-char-names)
(setf (truth-table-data table) (create-truth-table parsed-exp
:vars vars
:include-vars include-vars
:include-intermediate subexps)))
((or proposition-parse-error proposition-eval-error) (e)
(setf (error-box-message error-box) (princ-to-string e))))
(setf (truth-table-data table) nil))
(update table)
(update error-box)))
(defmethod render ((page page))
"Render PAGE."
(with-html
(with-slots (table error-box) page
(:h1 "Truth Table Generator")
(with-html-form (:POST (lambda (&key prop-str implicit-and
multi-char-names format
include-vars subexps
&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)))
(:div :id "main-controls-wrapper"
(:input :id "prop-input-field"
:type "text"
:name "prop-str"
:placeholder "Proposition string...")
(:input :id "submit-button"
:type "submit"
:value "Generate"))
(:div :id "extra-controls-wrapper"
(:input :type "checkbox"
:name "implicit-and"
:checked t)
(:label :for "implicit-and" "Implicit And")
(:input :type "checkbox"
:name "multi-char-names"
:style "margin-left: 10px;")
(:label :for "multi-char-names" "Multi-character Variables")
(:input :type "checkbox"
:name "include-vars"
:checked t
:style "margin-left: 10px;")
(:label :for "include-vars" "Include Variables")
(:input :type "checkbox"
:name "subexps"
:checked t
:style "margin-left: 10px;")
(:label :for "subexps" "Include Sub-expressions")
(:select :name "format" :style "margin-left: 10px;"
(:option :value "html" "HTML")
(:option :value "latex" "LaTeX")
(:option :value "ascii" "ASCII")
(:option :value "unicode" "Unicode"))))
(render error-box)
(render table)
(:div :id "info-text"
(:span
"This website is free software under the terms of the AGPL"
"license version 3. You can find a copy of the license ")
(:a :href "https://www.gnu.org/licenses/agpl-3.0.html"
"here")
(:span ". You can find the source of this website ")
(:a :href "https://git.zander.im/Zander671/truth-table"
"here")
(:span ".")))))
(defmethod get-dependencies ((page page))
(append
(list
(reblocks-lass:make-dependency
'(.page
:width "70%"
:margin "auto"
(h1 :text-align "center")
(form
:margin-bottom "5px"
(|#main-controls-wrapper|
:display flex
:margin-bottom "5px"
(|#prop-input-field|
:flex-grow "1"
:margin-right "5px"
:font-size "large")
(|#submit-button|
:font-size "large"))
(|#extra-controls-wrapper|
:display "flex"
:justify-content "center"
:align-items "center"))
(|#info-text|
:text-align "center"
:margin-top "10px"
:font-size "small"))))
(call-next-method)))
(defapp truth-table-app
:prefix "/")
(defmethod reblocks/page:init-page ((app truth-table-app) (url-path string)
expire-at)
"Main entry point for webpage."
(declare (ignorable app url-path expire-at))
(make-instance 'page))
(defparameter *command-line-spec*
`((#\h "help" help nil "print this message, then exit")
(#\d "debug" debug nil "enable debug output")
(#\p "port" port t
,(format nil "specify port to use (default: ~d)" *default-port*))
(#\a "address" address t
,(format nil "specify address to bind to (default: ~a)" *default-address*)))
"Spec for use in `parse-command-line.")
(defun determine-port (opts)
"Get port from the command line option array OPTS, or use a default if port
was not specified."
(let ((raw-value (option-value 'port opts)))
(if raw-value
(handler-case
(let ((value (parse-integer raw-value :junk-allowed nil)))
(if (< value 1)
(error 'parse-error)
value))
(parse-error ()
(cerror "Use *default-port*" 'command-line-error
:message (format nil "invalid port: ~a" raw-value))
*default-port*))
*default-port*)))
(defun main (argv) (defun main (argv)
"The main entry point to the program. ARGV is the list of command line "The main entry point to the program. ARGV is the list of command line
arguments." arguments."
(format t "Hello World~%")) (let ((cmdline-error nil))
(handler-bind
((command-line-error
(lambda (c)
(format *error-output* "~a~%" c)
(setq cmdline-error t)
(continue))))
(destructuring-bind ((&rest 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))
(address (or (option-value 'address opts) *default-address*)))
(when norm-args
(cerror "Ignore the extra arguments." 'command-line-error
:message "extra non-option arguments"))
(when (option-value 'help opts)
(print-usage t *command-line-spec* "truth-table-webserver"
:print-astrisk nil)
(if cmdline-error
(uiop:quit 1)
(uiop:quit 0)))
(when cmdline-error
(uiop:quit 1))
(reblocks/server:start :apps '(truth-table-app)
:port port
:interface address
:debug (option-value 'debug opts)))))))
(defun toplevel () (defun toplevel ()
"Top-level function to be passed to `save-lisp-and-die'." "Top-level function to be passed to `save-lisp-and-die'."
(handler-case #+sbcl (sb-ext:disable-debugger)
(with-user-abort:with-user-abort (main (uiop:command-line-arguments)))
(main (uiop:command-line-arguments)))
(with-user-abort:user-abort ()
(format *error-output* "Keyboard interrupt~%")
(uiop:quit 1))))