commit 67e5c7284c0364ca89ccdbe31598881f0d316329
parent 3b600f7f2c52789075c0ad6f422d76ce020c3228
Author: Tomas Hlavaty <tom@logand.com>
Date:   Sun, 16 Oct 2016 23:53:08 +0200
try to store state in url instead on server in pool
Diffstat:
| A | demo-counter2.lisp |  |  | 295 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | demo-counter3.lisp |  |  | 345 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
2 files changed, 640 insertions(+), 0 deletions(-)
diff --git a/demo-counter2.lisp b/demo-counter2.lisp
@@ -0,0 +1,295 @@
+;;; Copyright (C) 2013, 2014, 2015, 2016 Tomas Hlavaty <tom@logand.com>
+;;;
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use, copy,
+;;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;;; of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+;;;
+;;; The above copyright notice and this permission notice shall be
+;;; included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+;;; DEALINGS IN THE SOFTWARE.
+
+;;; TODO post forms
+
+(defpackage :rw.demo.counter2
+  (:use :cl))
+
+(in-package :rw.demo.counter2)
+
+(defvar *query-parameters*)
+(defvar *state-alist*)
+(defvar *action*)
+(defvar *widget-path*)
+(defvar *widget-child*)
+(defvar *action-index*)
+(defvar *var-index*)
+(defvar *getters*)
+
+(defun encode-var (value stream)
+  (etypecase value
+    (null)
+    (integer
+     (unless (minusp value)
+       (write-char #\+ stream))
+     (princ value stream))))
+
+(defun decode-var (x)
+  (when x
+    (ecase (char x 0)
+      ((#\+ #\-) (parse-integer x)))))
+
+(defun encode-url (state-alist action)
+  (with-output-to-string (s)
+    (write-string "?" s)
+    (loop
+       for x in state-alist
+       for i from 0
+       do (progn
+            (when (plusp i)
+              (write-char #\& s))
+            (write-string (car x) s)
+            (write-char #\= s))
+         (encode-var (cdr x) s))
+    (when (and state-alist action)
+      (write-string "&" s))
+    (when action
+      (write-string "a=" s)
+      (write-string action s))))
+
+(defun decode-url ()
+  (loop
+     for x in *query-parameters*
+     if (equal "a" (car x))
+     collect (cdr x) into action
+     else collect (cons (car x) (decode-var (cdr x))) into state-alist
+     finally (return (values state-alist (car action)))))
+
+(defun encode-path (path i)
+  (with-output-to-string (s)
+    (when path
+      (princ (car path) s)
+      (dolist (x (cdr path))
+        (write-char #\: s)
+        (princ x s)))
+    (write-char #\; s)
+    (princ i s)))
+
+(defun lookup-var (path)
+  (cdr (assoc path *state-alist* :test #'equal)))
+
+(defun widget-var (default get set &optional id)
+  (let ((path (encode-path *widget-path* (incf *var-index*))))
+    (funcall set (or (and id (lookup-var id))
+                     (lookup-var path)
+                     default))
+    (when *action*
+      (push (lambda () (cons (or id path) (funcall get))) *getters*))))
+
+(defun widget (thunk)
+  (lambda ()
+    (let ((*widget-path* (cons (incf *widget-child*) *widget-path*))
+          (*widget-child* 0)
+          (*action-index* 0)
+          (*var-index* 0))
+      (funcall thunk))))
+
+(defun widget-action (thunk)
+  (let ((path (encode-path *widget-path* (incf *action-index*))))
+    (lambda ()
+      (if *action*
+          (when (equal *action* path)
+            (funcall thunk)
+            nil)
+          (encode-url *state-alist* path)))))
+
+(defun draw (thunk)
+  (multiple-value-bind (*state-alist* *action*) (ignore-errors (decode-url))
+    (let (*widget-path*
+          (*widget-child* 0)
+          *getters*
+          (rw.ui::*click-link* (lambda (x) (funcall x)))
+          (rw.ui::*click-form* (lambda (x) (funcall x))) ;;;;;;;;;;;
+          (w (funcall thunk)))
+      (if *action*
+          (progn
+            (funcall w)
+            (rw.ui::http-redirect
+             (encode-url (mapcar #'funcall *getters*) nil)))
+          `(:http-1.0
+            :code 200
+            :headers (("Content-Type" . "text/html;charset=utf-8")
+                      ("cache-control" . "no-cache,no-store")
+                      ("pragma" . "no-cache")
+                      ("expires" . "-1"))
+            :body ,(funcall w))))))
+
+;;; example
+
+(defun counter-widget (i)
+  (let (n)
+    (widget
+     (lambda ()
+       (widget-var 0 (lambda () n) (lambda (x) (setq n x)))
+       `(:p ,i ": "
+            " " ,(rw.ui:link "up" (widget-action (lambda () (incf n))))
+            " " ,(rw.ui:link "down" (widget-action (lambda () (decf n))))
+            " " (:b ,n))))))
+
+(defun calendar-widget (year0 month0 &key (first-weekday 0) (show-weeks t))
+  (let (year month)
+    (widget
+     (lambda ()
+       (widget-var year0 (lambda () year) (lambda (x) (setq year x)))
+       (widget-var month0 (lambda () month) (lambda (x) (setq month x)))
+       (let ((weeks (when show-weeks (rw.calendar::week-generator year month))))
+         `((:table :style "font-family:monospace")
+           (:tr
+            ,@(when weeks '((:td "")))
+            ((:td :colspan 3 :align "center")
+             ,(rw.ui:link "<" (widget-action
+                               (lambda ()
+                                 (decf month)
+                                 (when (< month 1)
+                                   (decf year)
+                                   (setq month 12)))))
+             " " ,(rw.calendar::pretty-month month) " "
+             ,(rw.ui:link ">" (widget-action
+                               (lambda ()
+                                 (incf month)
+                                 (when (< 12 month)
+                                   (incf year)
+                                   (setq month 1))))))
+            ((:td :align "center")
+             ,(rw.ui:link "@" (widget-action
+                               (lambda () (setq year year0 month month0)))))
+            ((:td :colspan 3 :align "center")
+             ,(rw.ui:link "<" (widget-action
+                               (lambda () (decf year))))
+             " " ,year " "
+             ,(rw.ui:link ">" (widget-action
+                               (lambda () (incf year))))))
+           (:tr
+            ,@(when weeks '((:td "  ")))
+            ,@(loop
+                 with g = (rw.calendar::weekday-generator first-weekday)
+                 for i from 0 below 7
+                 for n = (funcall g)
+                 collect `((:td :style
+                                (:style :color ,(when (rw.calendar::weekend n) "red")))
+                           ,(rw.calendar::pretty-day n))))
+           ,@(loop
+                with g = (rw.calendar::day-generator year month first-weekday)
+                for i from 0 below 6
+                collect `(:tr
+                          ,@(when weeks `(((:td :align "right") ,(funcall weeks))))
+                          ,@(loop
+                               for j from 0 below 7
+                               for d = (funcall g)
+                               collect `((:td :align "right")
+                                         ,(if d
+                                              (rw.ui:link d (widget-action
+                                                             (lambda ())))
+                                              "")))))))))))
+
+(defun form (draw)
+  `((:form
+     :action ,(funcall rw.ui::*click-link* (widget-action 'rw.ui::no))
+     :method "post"
+     :enctype "multipart/form-data"
+     :style "padding:0;margin:0;border:0")
+    ((:div :style "width:0;height:0;overflow:hidden")
+     ,(rw.ui:submit nil (widget-action 'rw.ui::no1)))
+    ,(if (functionp draw) (funcall draw) draw)))
+
+(defun var-widget (id)
+  (let (v)
+    (widget
+     (lambda ()
+       (widget-var 0 (lambda () v) (lambda (x) (setq v x)) id)
+       (progn ;;form
+        `(:p ,id "=" ,v " "
+             #+nil ,(rw.ui:entry (lambda (x)) (princ-to-string v))
+             #+nil ,(rw.ui:submit "ok" (lambda (x) (print (list :@@@ x))))))))))
+
+(defun toplevel-widget ()
+  (let ((w (nconc
+            (mapcar 'counter-widget '(1 2 3 4))
+            (list
+             (calendar-widget 2012 7)
+             (calendar-widget 2013 8)
+             (var-widget "v1")
+             (var-widget "v2")))))
+    (lambda ()
+      `(:html
+        (:head
+         ((:meta :http-equiv "content-type"
+                 :content "text/html;charset=utf-8"))
+         ((:meta :http-equiv "cache-control" :content "no-cache,no-store"))
+         ((:meta :http-equiv "pragma" :content "no-cache"))
+         ((:meta :http-equiv "expires" :content -1))
+         (:title "counter"))
+        (:body ,@(mapcar #'funcall w))))))
+
+(defun draw-counter ()
+  (draw (lambda () (toplevel-widget))))
+
+(defun counter-handler (msg stream method query protocol headers &optional body)
+  (declare (ignore protocol headers))
+  (ecase msg
+    (:read (rw:till (rw:peek-reader stream)))
+    (:write
+     (let ((rw.ui:*http-server*
+            (let ((pp (rw.http::post-parameters method body)))
+              (lambda (msg &rest args)
+                (declare (ignore args))
+                (ecase msg
+                  (:method method)
+                  (:post-parameters pp)))))
+           (*query-parameters* (nth-value 1 (rw.uri:parse-query-string query))))
+       (draw-counter)))))
+
+(defun start ()
+  (rw.http:server (rw.socket:make-ipv4-address "0.0.0.0")
+                  2349
+                  'counter-handler
+                  :quit (lambda () nil)
+                  :allowed-methods '(:get :post)
+                  :ignore-errors-p t))
+
+;;(start)
+
+(defun save-image ()
+  #-(or ccl sbcl)
+  (error "TODO RW.DEMO.COUNTER::SAVE-IMAGE")
+  #+ccl ;; TODO no debug on ^C
+  (ccl:save-application "cl-rw-demo-counter"
+                        :prepend-kernel t
+                        :error-handler :quit-quietly
+                        :toplevel-function (lambda ()
+                                             (handler-case
+                                                 (progn
+                                                   (start)
+                                                   (loop (sleep 1)))
+                                               (condition ()
+                                                 (ccl:quit 1)))))
+  #+sbcl
+  (sb-ext:save-lisp-and-die "cl-rw-demo-counter"
+                            :executable t
+                            :toplevel (lambda ()
+                                        (handler-case
+                                            (progn
+                                              (start)
+                                              (loop (sleep 1)))
+                                          (condition ()
+                                            (sb-ext:exit :code 1 :abort t))))))
diff --git a/demo-counter3.lisp b/demo-counter3.lisp
@@ -0,0 +1,345 @@
+;;; Copyright (C) 2013, 2014, 2015, 2016 Tomas Hlavaty <tom@logand.com>
+;;;
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use, copy,
+;;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;;; of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+;;;
+;;; The above copyright notice and this permission notice shall be
+;;; included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+;;; DEALINGS IN THE SOFTWARE.
+
+(defpackage :rw.demo.counter2
+  (:use :cl))
+
+(in-package :rw.demo.counter2)
+
+(defvar *slet-state*)
+(defvar *slet-state-decoded*)
+(defvar *sflet-action*)
+(defvar *slet-path*)
+(defvar *slet-child*)
+(defvar *sflet-path*)
+(defvar *sflet-child*)
+(defvar *widget-path*)
+(defvar *widget-child*)
+(defvar *action-index*)
+(defvar *var-index*)
+(defvar *slet-getters*)
+(defvar *mode*)
+
+(defun encode-url (state action)
+  (with-output-to-string (s)
+    (write-string "?" s)
+    (when state
+      (write-string "s=" s)
+      (write-string state s))
+    (when (and state action)
+      (write-string "&" s))
+    (when action
+      (write-string "a=" s)
+      (write-string action s))))
+
+(defun encode-path (path i)
+  (with-output-to-string (s)
+    (when path
+      (princ (car path) s)
+      (dolist (x (cdr path))
+        (write-char #\: s)
+        (princ x s)))
+    (write-char #\; s)
+    (princ i s)))
+
+(defun var-reader (r)
+  (lambda ()
+    (when (rw:peek r)
+      (assert (eql #\! (rw:next r)))
+      (cons (let ((x (rw:till r '(#\$))))
+              (when x
+                (coerce x 'string)))
+            (progn
+              (assert (eql #\$ (rw:next r)))
+              (assert (member (rw:peek r) '(nil #\! #\+ #\-)))
+              (let ((x (rw:till r '(#\!))))
+                (when x
+                  (parse-integer (coerce x 'string)))))))))
+
+(defun decode-state (state)
+  (rw:till (rw:peek-reader (var-reader (rw:peek-reader (rw:reader state))))))
+
+;;(decode-state "!1;1$!1;2$+3!2;1$-1")
+
+(defun lookup-var (path)
+  (cdr (assoc path *slet-state-decoded* :test #'equal)))
+
+(defun widget-var (default get set)
+  (let ((path (encode-path *widget-path* (incf *var-index*))))
+    (funcall set (or (lookup-var path) default))
+    (when (eq :step *mode*)
+      (push (lambda () (values path (funcall get))) *slet-getters*))))
+
+(defmacro slet (vars &body body)
+  `(let ((*slet-path* (cons (incf *slet-child*) *slet-path*))
+         (*slet-child* 0))
+     (let ((slet-path *slet-path*))
+       (let ,(loop
+                for x in vars
+                for i from 1
+                collect (destructuring-bind (name value)
+                            (if (atom x) `(,x nil) x)
+                          `(,name
+                            (or (lookup-var (encode-path slet-path ,i))
+                                ,value))))
+         (when (eq :step *mode*)
+           ,@(loop
+                for x in vars
+                for i from 1
+                collect (destructuring-bind (name value)
+                            (if (atom x) `(,x nil) x)
+                          (declare (ignore value))
+                          `(push (lambda ()
+                                   (values (encode-path slet-path ,i) ,name))
+                                 *slet-getters*))))
+         ,@body))))
+
+(defun widget (thunk)
+  (lambda ()
+    (let ((*widget-path* (cons (incf *widget-child*) *widget-path*))
+          (*widget-child* 0)
+          (*action-index* 0)
+          (*var-index* 0))
+      (funcall thunk))))
+
+(defun widget-action (thunk)
+  (let ((path (encode-path *widget-path* (incf *action-index*))))
+    (lambda ()
+      (ecase *mode*
+        (:draw
+         (encode-url *slet-state* path))
+        (:step
+         (when (equal *sflet-action* path)
+           (funcall thunk))
+         nil)))))
+
+(defmacro sflet (funs &body body)
+  `(let ((*sflet-path* (cons (incf *sflet-child*) *sflet-path*))
+         (*sflet-child* 0))
+     (let ((sflet-path *sflet-path*))
+       (flet ,(loop
+                 for x in funs
+                 for i from 1
+                 collect (destructuring-bind (name args &body body) x
+                           `(,name
+                             ,args
+                             (ecase *mode*
+                               (:draw
+                                (encode-url *slet-state*
+                                            (encode-path sflet-path ,i)))
+                               (:step
+                                (when (equal *sflet-action*
+                                             (encode-path sflet-path ,i))
+                                  ,@body)
+                                nil)))))
+         ,@body))))
+
+(defun counter-widget (i)
+  (let ((n 0))
+    (flet ((up () (incf n) (print (list :@@@ :up i n)))
+           (down () (decf n) (print (list :@@@ :down i n))))
+      (widget
+       (lambda ()
+         (widget-var 0 (lambda () n) (lambda (x) (setq n x)))
+         `(:p ,i ": "
+              " " ,(rw.ui:link "up" (widget-action #'up))
+              " " ,(rw.ui:link "down" (widget-action #'down))
+              " " (:b ,n)))))))
+
+(defun calendar-widget (year0 month0 &key (first-weekday 0) (show-weeks t))
+  (let ((year year0)
+        (month month0))
+    (flet ((nop ()) ;; problem, need link for each day
+           (previous-month ()
+             (decf month)
+             (when (< month 1)
+               (decf year)
+               (setq month 12)))
+           (next-month ()
+             (incf month)
+             (when (< 12 month)
+               (incf year)
+               (setq month 1)))
+           (reset () (setq year year0 month month0))
+           (previous-year () (decf year))
+           (next-year () (incf year)))
+      (widget
+       (lambda ()
+         (widget-var year0 (lambda () year) (lambda (x) (setq year x)))
+         (widget-var month0 (lambda () month) (lambda (x) (setq month x)))
+         (let ((weeks (when show-weeks (rw.calendar::week-generator year month))))
+           `((:table :style "font-family:monospace")
+             (:tr
+              ,@(when weeks '((:td "")))
+              ((:td :colspan 3 :align "center")
+               ,(rw.ui:link "<" (widget-action #'previous-month))
+               " " ,(rw.calendar::pretty-month month) " "
+               ,(rw.ui:link ">" (widget-action #'next-month)))
+              ((:td :align "center") ,(rw.ui:link "@" (widget-action #'reset)))
+              ((:td :colspan 3 :align "center")
+               ,(rw.ui:link "<" (widget-action #'previous-year))
+               " " ,year " "
+               ,(rw.ui:link ">" (widget-action #'next-year))))
+             (:tr
+              ,@(when weeks '((:td "  ")))
+              ,@(loop
+                   with g = (rw.calendar::weekday-generator first-weekday)
+                   for i from 0 below 7
+                   for n = (funcall g)
+                   collect `((:td :style
+                                  (:style :color ,(when (rw.calendar::weekend n) "red")))
+                             ,(rw.calendar::pretty-day n))))
+             ,@(loop
+                  with g = (rw.calendar::day-generator year month first-weekday)
+                  for i from 0 below 6
+                  collect `(:tr
+                            ,@(when weeks `(((:td :align "right") ,(funcall weeks))))
+                            ,@(loop
+                                 for j from 0 below 7
+                                 for d = (funcall g)
+                                 collect `((:td :align "right")
+                                           ,(if d
+                                                (rw.ui:link d (widget-action #'nop))
+                                                ""))))))))))))
+
+(defun toplevel-widget ()
+  (let ((w (mapcar 'counter-widget '(1 2 3 4)))
+        (w2 (calendar-widget 2012 7)))
+    (lambda ()
+      `(:html
+        (:head
+         ((:meta :http-equiv "content-type"
+                 :content "text/html;charset=utf-8"))
+         ((:meta :http-equiv "cache-control" :content "no-cache,no-store"))
+         ((:meta :http-equiv "pragma" :content "no-cache"))
+         ((:meta :http-equiv "expires" :content -1))
+         (:title "counter"))
+        (:body ,@(mapcar #'funcall w) ,(funcall w2))))))
+
+(defvar *query-parameters*)
+
+(defun query-parameter (key)
+  (cdr (assoc key *query-parameters* :test #'equal)))
+
+(defun draw-counter ()
+  (let* ((*slet-state* (query-parameter "s"))
+         (*slet-state-decoded* (ignore-errors (decode-state *slet-state*)))
+         (*sflet-action* (query-parameter "a"))
+         *slet-path*
+         (*slet-child* 0)
+         *sflet-path*
+         (*sflet-child* 0)
+         *widget-path*
+         (*widget-child* 0)
+         *slet-getters*
+         (*mode* (if *sflet-action* :step :draw))
+         (rw.ui::*click-link* (lambda (click) (funcall click)))
+         #+nil(rw.ui::*click-form* (lambda (set) "TODO")))
+    (let ((w (toplevel-widget)))
+      (ecase *mode*
+        (:step
+         (funcall w)
+         (rw.ui::http-redirect
+          (encode-url (with-output-to-string (s)
+                        (dolist (x *slet-getters*)
+                          (multiple-value-bind (k v) (funcall x)
+                            (write-char #\! s)
+                            (write-string k s)
+                            (write-char #\$ s)
+                            (etypecase v
+                              (null)
+                              (integer
+                               (unless (minusp v)
+                                 (write-char #\+ s))
+                               (princ v s))))))
+                      nil)))
+        (:draw
+         `(:http-1.0
+           :code 200
+           :headers (("Content-Type" . "text/html;charset=utf-8")
+                     ("cache-control" . "no-cache,no-store")
+                     ("pragma" . "no-cache")
+                     ("expires" . "-1"))
+           :body ,(funcall w))))))
+  #+nil
+  (rw.ui:draw (lambda ()
+                (let ((w (toplevel-widget)))
+                  (lambda ()
+                    `(:http-1.0
+                      :code 200
+                      :headers (("Content-Type" . "text/html;charset=utf-8")
+                                ("cache-control" . "no-cache,no-store")
+                                ("pragma" . "no-cache")
+                                ("expires" . "-1"))
+                      :body ,(funcall w)))))
+              'construct
+              'deconstruct))
+
+(defun counter-handler (msg stream method query protocol headers &optional body)
+  (declare (ignore protocol headers))
+  (ecase msg
+    (:read (rw:till (rw:peek-reader stream)))
+    (:write
+     (let ((rw.ui:*http-server*
+            (let ((pp (rw.http::post-parameters method body)))
+              (lambda (msg &rest args)
+                (declare (ignore args))
+                (ecase msg
+                  (:method method)
+                  (:post-parameters pp)))))
+           (*query-parameters* (nth-value 1 (rw.uri:parse-query-string query))))
+       (draw-counter)))))
+
+(defun start ()
+  (rw.http:server (rw.socket:make-ipv4-address "0.0.0.0")
+                  2349
+                  'counter-handler
+                  :quit (lambda () nil)
+                  :allowed-methods '(:get :post)
+                  :ignore-errors-p t))
+
+;;(start)
+
+(defun save-image ()
+  #-(or ccl sbcl)
+  (error "TODO RW.DEMO.COUNTER::SAVE-IMAGE")
+  #+ccl ;; TODO no debug on ^C
+  (ccl:save-application "cl-rw-demo-counter"
+                        :prepend-kernel t
+                        :error-handler :quit-quietly
+                        :toplevel-function (lambda ()
+                                             (handler-case
+                                                 (progn
+                                                   (start)
+                                                   (loop (sleep 1)))
+                                               (condition ()
+                                                 (ccl:quit 1)))))
+  #+sbcl
+  (sb-ext:save-lisp-and-die "cl-rw-demo-counter"
+                            :executable t
+                            :toplevel (lambda ()
+                                        (handler-case
+                                            (progn
+                                              (start)
+                                              (loop (sleep 1)))
+                                          (condition ()
+                                            (sb-ext:exit :code 1 :abort t))))))