On 16.06.23 02:14, Waldek Hebisch wrote:
Well, I wrote "binary".  The question is if sbcl version I use for
creation of binaries has all features needed by jfricas.  It should,
but testing is better than bling faith.

OK, but building SBCL 1.1.1 from the git repo does not work on my computer. Certainly someprerequisite missing. I am building with the default SBCL 2.1.11.debian on Ubuntu 22.04.2 LTS.

Is there an easier way to get the sbcl version you wil be using?


As I wrote my current thinking is that hunchentoot is dependence
like other dependencies: it is responsibility of person doing build
to make sure that it is available.  For convenience we can add
hsbcl tarball to the release area.  And of course instruction
in INSTALL.

If we go that way, then it seems as simple as asking the user to provide an sbcl image with hunchentoot included such that

(require :asdf)(require :hunchentoot)

can be called and we do not have to change anything in FriCAS. Oh wait, there was this thing with stdout.

https://github.com/fricas/fricas/commit/7c1d3e8ea7ced544ecb57156415e7b8af64e098b#diff-953a91d70c9e61cba8f0e8f2f97d7141601a66d5d12985e181c9abbb4b8da7d2

(see into the vmlisp.lisp part of the attached patch).

When I jfricas starts webspad.lisp, then *OBEY-STDOUT* will be set to T and output goes the way it should go for jfricas in order to capture output that would normally go to stdout and otherwise be invisible in the notebook.

In fact, may I ask you a favour? If you do changes that affects quite a big
piece of code and could potentially break things, like the "$ --> %" change,
please open a pull request at github so that others have the chance to check
things they care about, before the patches become officially committed to
the repo.

Well, long time ago I tried to provide patches so that people can
test various changes.  Feedback I received was almost empty,
so I rarely do this.

Well, at least for me it feels troublesome to extract the patch from the mail, call patch and record it in my local git tree so that I know where I am. It would be much easier, if I could simply "git fetch hebisch" and look at your branches. Also for you it would be a simple "git push" to your github repository.

But true, for some things I do not have to say much, but the FriCAS-Aldor interface is something I care about.

Ralf

=========================================================
; wrote /home/hemmecke/v/git/sbcl/obj/from-host/src/code/early-type.fasl-tmp
; compilation finished in 0:00:00.236
Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
                                    {1001834363}>:
  FAILURE-P was set when creating "obj/from-host/src/code/early-type.fasl".

Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {1001834363}>
0: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<SIMPLE-ERROR "FAILURE-P was set when creating ~S." {10040CEC63}> #<unused argument> :QUIT T) 1: (SB-DEBUG::RUN-HOOK SB-EXT:*INVOKE-DEBUGGER-HOOK* #<SIMPLE-ERROR "FAILURE-P was set when creating ~S." {10040CEC63}>) 2: (INVOKE-DEBUGGER #<SIMPLE-ERROR "FAILURE-P was set when creating ~S." {10040CEC63}>) 3: (ERROR #<SIMPLE-ERROR "FAILURE-P was set when creating ~S." {10040CEC63}>) 4: (SB-KERNEL:WITH-SIMPLE-CONDITION-RESTARTS ERROR NIL "FAILURE-P was set when creating ~S." "obj/from-host/src/code/early-type.fasl")
5: (COMPILE-STEM "src/code/early-type" NIL :HOST-COMPILE)
6: (IN-HOST-COMPILATION-MODE #<FUNCTION (LAMBDA NIL :IN HOST-CLOAD-STEM) {10054D5F2B}>)
7: (HOST-CLOAD-STEM "src/code/early-type" NIL)
8: (LOAD-OR-CLOAD-XCOMPILER #<FUNCTION HOST-CLOAD-STEM>)
9: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LOAD-OR-CLOAD-XCOMPILER (FUNCTION HOST-CLOAD-STEM)) #<NULL-LEXENV>)
10: (EVAL (LOAD-OR-CLOAD-XCOMPILER (FUNCTION HOST-CLOAD-STEM)))
11: (SB-EXT:INTERACTIVE-EVAL (LOAD-OR-CLOAD-XCOMPILER (FUNCTION HOST-CLOAD-STEM)) :EVAL NIL)
12: (SB-IMPL::REPL-FUN NIL)
13: ((LAMBDA NIL :IN SB-IMPL::TOPLEVEL-REPL))
14: (SB-IMPL::%WITH-REBOUND-IO-SYNTAX #<FUNCTION (LAMBDA NIL :IN SB-IMPL::TOPLEVEL-REPL) {10009BC53B}>)
15: (SB-IMPL::TOPLEVEL-REPL NIL)
16: (SB-IMPL::TOPLEVEL-INIT)
17: ((FLET SB-UNIX::BODY :IN SB-IMPL::START-LISP))
18: ((FLET "WITHOUT-INTERRUPTS-BODY-3" :IN SB-IMPL::START-LISP))
19: (SB-IMPL::START-LISP)

unhandled condition in --disable-debugger mode, quitting
deleted #P"/home/hemmecke/v/git/sbcl/obj/from-host/src/code/early-type.fasl-tmp"
Command exited with non-zero status 1
4.02user 0.24system 0:06.83elapsed 62%CPU (0avgtext+0avgdata 118296maxresident)k
67240inputs+4336outputs (370major+69633minor)pagefaults 0swaps


--
You received this message because you are subscribed to the Google Groups "FriCAS - 
computer algebra system" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to fricas-devel+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/fricas-devel/44993d81-51e9-db00-3063-186e8996a79a%40hemmecke.org.
From 7a33c9bdee3d9d575bb9bc5dc745b20f3799a8c1 Mon Sep 17 00:00:00 2001
From: Ralf Hemmecke <r...@hemmecke.org>
Date: Sat, 4 Jun 2022 18:04:37 +0200
Subject: put hunchentoot into FRICASsys

Works only with sbcl.
---
 configure                    | 185 +++++++++++++++++++++++++--
 configure.ac                 | 130 +++++++++++++++++++
 contrib/webspad/webspad.lisp | 233 +++++++++++++++++++++++++++++++++++
 src/interp/Makefile.in       |   3 +
 src/interp/vmlisp.lisp       |  12 +-
 5 files changed, 547 insertions(+), 16 deletions(-)
 create mode 100644 contrib/webspad/webspad.lisp

diff --git a/configure b/configure
index bb72db73..771936ae 100755
--- a/configure
+++ b/configure
@@ -631,6 +631,8 @@ ALDOR_foreignStyle
 ALDOR
 ALDOR_datadir
 sys_ALDOR
+WEBSPAD_REQUIREMENTS
+SBCL_HOME
 LDF
 CCF
 FRICAS
@@ -725,7 +727,6 @@ infodir
 docdir
 oldincludedir
 includedir
-runstatedir
 localstatedir
 sharedstatedir
 sysconfdir
@@ -759,6 +760,9 @@ enable_gmp
 enable_debug_compiler
 enable_algebra_optimization
 with_x
+with_quicklisp
+enable_webspad
+with_sbcl_home
 enable_aldor
 with_aldor_binary
 '
@@ -810,7 +814,6 @@ datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
-runstatedir='${localstatedir}/run'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
 docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1063,15 +1066,6 @@ do
   | -silent | --silent | --silen | --sile | --sil)
     silent=yes ;;
 
-  -runstatedir | --runstatedir | --runstatedi | --runstated \
-  | --runstate | --runstat | --runsta | --runst | --runs \
-  | --run | --ru | --r)
-    ac_prev=runstatedir ;;
-  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
-  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
-  | --run=* | --ru=* | --r=*)
-    runstatedir=$ac_optarg ;;
-
   -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
     ac_prev=sbindir ;;
   -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1209,7 +1203,7 @@ fi
 for ac_var in	exec_prefix prefix bindir sbindir libexecdir datarootdir \
 		datadir sysconfdir sharedstatedir localstatedir includedir \
 		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
-		libdir localedir mandir runstatedir
+		libdir localedir mandir
 do
   eval ac_val=\$$ac_var
   # Remove trailing slashes.
@@ -1362,7 +1356,6 @@ Fine tuning of the installation directories:
   --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
-  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --oldincludedir=DIR     C header files for non-gcc [/usr/include]
@@ -1410,6 +1403,7 @@ Optional Features:
   --enable-algebra-optimization=S
                           use S as Lisp optimize declaration for compiling the
                           algebra
+  --enable-webspad        include a webserver into FriCASsys, (yes/no)
   --enable-aldor          build an interface to the Aldor compiler, (yes/no)
 
 Optional Packages:
@@ -1426,6 +1420,9 @@ Optional Packages:
   --with-gmp-include=PATH specify directory for installed GMP include files
   --with-gmp-lib=PATH     specify directory for the installed GMP library
   --with-x                use the X Window System
+  --with-quicklisp=PATH   use quicklisp installation path (default
+                          "~/quicklisp")
+  --with-sbcl-home=PATH   Value for SBCL_HOME.
   --with-aldor-binary=PATH
                           use the aldor binary given
 
@@ -6440,6 +6437,168 @@ esac
 
 
 
+###################################################################
+## The following section deals with the inclusion of the hunchentoot
+## webserver which can be used for the jupyter interface for FriCAS.
+## It is currently only supported on Linux for SBCL.
+
+## There are, in fact, two switches.
+## --with-quicklisp="/absolute/path-to-quicklisp"
+## and
+## --enable-webspad.
+## Whereas --enable-webspad deals with the actual inclusion of
+## hunchentoot into FriCASsys, --with-quicklisp specifies that
+## hunchentoot should be loaded via quicklisp.
+
+## Since by default hunchentoot (apt install cl-hunchentoot) comes
+## with ssl support, which might cause problems loading the right
+## version of libssl.so, namely
+##: Error opening shared object "libssl.so.1.0.0":
+##: libssl.so.1.0.0: cannot open shared object file: No such file or directory.
+## and the local FriCAS-Jupyter connection does not need ssl,
+## we prefer that hunchentoot is installed via quicklisp into
+## a distinguished path via
+##   sbcl --load quicklisp.lisp
+##   (quicklisp-quickstart:install :path "/absolute/path-to-quicklisp/")
+##   (push :hunchentoot-no-ssl *features*)
+##   (ql:quickload "hunchentoot")
+
+# Check whether --with-quicklisp was given.
+if test "${with_quicklisp+set}" = set; then :
+  withval=$with_quicklisp; USE_QUICKLISP=yes
+     if test -d $withval; then :
+  QUICKLISPPATH=$withval
+else
+  if test -d ~/quicklisp; then :
+  QUICKLISPPATH=~/quicklisp
+else
+  as_fn_error $? "quicklisp path not available" "$LINENO" 5
+fi
+fi
+else
+  USE_QUICKLISP=no
+fi
+
+
+###################################################################
+## Default is --enable-webspad=no which means hunchentoot will
+## not be included into FRICASsys and thus jfricas will not work.
+## With --enable-webspad, configure checks whether hunchentoot is
+## available for inclusion into FRICASsys.
+
+## If hunchentoot is included into FRICASsys in the binary
+## distribution, then the target system does not need to have
+## hunchentoot installed, but must provide jupyter so that the jfricas
+## interface can work.
+INCLUDE_WEBSPAD=no
+# Check whether --enable-webspad was given.
+if test "${enable_webspad+set}" = set; then :
+  enableval=$enable_webspad; case $enableval in #(
+  yes) :
+    if test "$fricas_lisp_flavor" = "sbcl"; then :
+  INCLUDE_WEBSPAD=yes
+else
+  INCLUDE_WEBSPAD=no
+        as_fn_error $? "webspad only available for SBCL" "$LINENO" 5
+fi ;; #(
+  no) :
+    INCLUDE_WEBSPAD=no ;; #(
+  *) :
+    as_fn_error $? "only yes/no allowed as value for --enable-webspad" "$LINENO" 5
+   ;;
+esac
+
+fi
+
+
+# Now Check that Hunchentoot is available.
+WEBSPAD_REQUIREMENTS=
+if test x"$USE_QUICKLISP" = xyes; then :
+  WEBSPAD_REQUIREMENTS="(load \"$QUICKLISPPATH/setup.lisp\")(require :hunchentoot)"
+else
+  WEBSPAD_REQUIREMENTS="(require :asdf)(require :hunchentoot)"
+fi
+
+if test x"$INCLUDE_WEBSPAD" = xyes; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for asdf and hunchentoot" >&5
+$as_echo_n "checking for asdf and hunchentoot... " >&6; }
+  if $fricas_lisp --non-interactive --eval "(progn $WEBSPAD_REQUIREMENTS)"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+     as_fn_error $? "asdf and hunchentoot are required for --enable-webspad" "$LINENO" 5
+     INCLUDE_WEBSPAD=no
+fi
+
+fi
+
+if test x"$INCLUDE_WEBSPAD" = xyes; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: Webserver hunchentoot will be included into FRICASsys." >&5
+$as_echo "$as_me: Webserver hunchentoot will be included into FRICASsys." >&6;}
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: No webserver will be included into FRICASsys." >&5
+$as_echo "$as_me: WARNING: No webserver will be included into FRICASsys." >&2;}
+   WEBSPAD_REQUIREMENTS=""
+
+fi
+
+
+# The reason why we need SBCL_HOME is that FRICASsys is generated by
+# using the bootsys image and not by the sbcl itself. The bootsys
+# image, however, lost the strategy to find sbcl.core if SBCL_HOME is
+# not set. (See below.)
+
+# Check whether --with-sbcl-home was given.
+if test "${with_sbcl_home+set}" = set; then :
+  withval=$with_sbcl_home; # Action if --with-sbcl-home is given.
+    if test -d "$withval"; then :
+  SBCL_HOME=$withval
+else
+  as_fn_error $? "--with-sbcl-home directory does not exist." "$LINENO" 5
+fi
+else
+  # Action if --with-sbcl-home is not given.
+    # If the path of sbcl is P, then SBCL_HOME will be P/../lib/sbcl.
+    # This is in line with https://github.com/sbcl/sbcl/blob/master/INSTALL.
+    #
+    ## The SBCL runtime needs to be able to find the ancillary files
+    ## associated with it: the "sbcl.core" file, and the contrib modules.
+    ## Finding sbcl.core can happen in the following ways:
+    ##
+    ##   * By default, in a location configured when the system was built.
+    ##     For binary distributions this is in "/usr/local/lib/sbcl".
+    ##
+    ##   * In the directory ../lib/sbcl relative to the executable.
+    ##
+    ##   * In the current directory.
+    ##
+    ##   * By environment variable, in the directory named by the
+    ##     environment variable "SBCL_HOME". Example:
+    ##
+    ##        $ export SBCL_HOME=/foo/bar/lib/sbcl
+    ##        $ sbcl
+    ##
+    ##     If your "INSTALL_ROOT" was FOO, then your "SBCL_HOME" is
+    ##     "FOO/lib/sbcl".
+    ##
+    ##   * By command line option:
+    ##
+    ##         $ sbcl --core /foo/bar/sbcl.core
+
+    lisp_prog=`echo $fricas_lisp | sed 's/ .*//'` # remove parameters
+     lisp_path=`which $lisp_prog` # cannot be empty
+     SBCL_HOME="${lisp_path%sbcl}../lib/sbcl"
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: SBCL_HOME=$SBCL_HOME" >&5
+$as_echo "$as_me: SBCL_HOME=$SBCL_HOME" >&6;}
+
+
+
 ac_config_files="$ac_config_files Makefile:config/var-def.mk:Makefile.in:config/setup-dep.mk"
 
 ac_config_files="$ac_config_files src/Makefile:config/var-def.mk:src/Makefile.in:config/setup-dep.mk"
diff --git a/configure.ac b/configure.ac
index 1d35bab9..9c43bfd8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -793,6 +793,136 @@ esac
 AC_SUBST(CCF)
 AC_SUBST(LDF)
 
+###################################################################
+## The following section deals with the inclusion of the hunchentoot
+## webserver which can be used for the jupyter interface for FriCAS.
+## It is currently only supported on Linux for SBCL.
+
+## There are, in fact, two switches.
+## --with-quicklisp="/absolute/path-to-quicklisp"
+## and
+## --enable-webspad.
+## Whereas --enable-webspad deals with the actual inclusion of
+## hunchentoot into FriCASsys, --with-quicklisp specifies that
+## hunchentoot should be loaded via quicklisp.
+
+## Since by default hunchentoot (apt install cl-hunchentoot) comes
+## with ssl support, which might cause problems loading the right
+## version of libssl.so, namely
+##: Error opening shared object "libssl.so.1.0.0":
+##: libssl.so.1.0.0: cannot open shared object file: No such file or directory.
+## and the local FriCAS-Jupyter connection does not need ssl,
+## we prefer that hunchentoot is installed via quicklisp into
+## a distinguished path via
+##   sbcl --load quicklisp.lisp
+##   (quicklisp-quickstart:install :path "/absolute/path-to-quicklisp/")
+##   (push :hunchentoot-no-ssl *features*)
+##   (ql:quickload "hunchentoot")
+AC_ARG_WITH([quicklisp],
+    [AS_HELP_STRING([--with-quicklisp=PATH],
+        [use quicklisp installation path (default "~/quicklisp")])],
+    [USE_QUICKLISP=yes
+     AS_IF([test -d $withval],
+       [QUICKLISPPATH=$withval],
+       [AS_IF([test -d ~/quicklisp],
+          [QUICKLISPPATH=~/quicklisp],
+          [AC_MSG_ERROR([quicklisp path not available])])])],
+    [USE_QUICKLISP=no])
+
+###################################################################
+## Default is --enable-webspad=no which means hunchentoot will
+## not be included into FRICASsys and thus jfricas will not work.
+## With --enable-webspad, configure checks whether hunchentoot is
+## available for inclusion into FRICASsys.
+
+## If hunchentoot is included into FRICASsys in the binary
+## distribution, then the target system does not need to have
+## hunchentoot installed, but must provide jupyter so that the jfricas
+## interface can work.
+INCLUDE_WEBSPAD=no
+AC_ARG_ENABLE(webspad,
+  [AS_HELP_STRING([--enable-webspad],
+                  [include a webserver into FriCASsys, (yes/no)])],
+  [AS_CASE([$enableval],
+    [yes], [AS_IF([test "$fricas_lisp_flavor" = "sbcl"],
+       [INCLUDE_WEBSPAD=yes],
+       [INCLUDE_WEBSPAD=no
+        AC_MSG_ERROR([webspad only available for SBCL])])],
+    [no], [INCLUDE_WEBSPAD=no],
+    [AC_MSG_ERROR([only yes/no allowed as value for --enable-webspad])]
+  )]
+)
+
+# Now Check that Hunchentoot is available.
+WEBSPAD_REQUIREMENTS=
+AS_IF([test x"$USE_QUICKLISP" = xyes],
+       [WEBSPAD_REQUIREMENTS="(load \"$QUICKLISPPATH/setup.lisp\")(require :hunchentoot)"],
+       [WEBSPAD_REQUIREMENTS="(require :asdf)(require :hunchentoot)"])
+
+AS_IF([test x"$INCLUDE_WEBSPAD" = xyes],
+  [AC_MSG_CHECKING([for asdf and hunchentoot])
+  AS_IF([$fricas_lisp --non-interactive --eval "(progn $WEBSPAD_REQUIREMENTS)"],
+    [AC_MSG_RESULT([yes])],
+    [AC_MSG_RESULT([no])
+     AC_MSG_ERROR([asdf and hunchentoot are required for --enable-webspad])
+     INCLUDE_WEBSPAD=no])]
+)
+
+AS_IF([test x"$INCLUDE_WEBSPAD" = xyes],
+  [AC_MSG_NOTICE([Webserver hunchentoot will be included into FRICASsys.])],
+  [AC_MSG_WARN([No webserver will be included into FRICASsys.])
+   WEBSPAD_REQUIREMENTS=""]
+)
+
+
+# The reason why we need SBCL_HOME is that FRICASsys is generated by
+# using the bootsys image and not by the sbcl itself. The bootsys
+# image, however, lost the strategy to find sbcl.core if SBCL_HOME is
+# not set. (See below.)
+AC_ARG_WITH([sbcl-home],
+    [AS_HELP_STRING([--with-sbcl-home=PATH],
+                    [Value for SBCL_HOME.])],
+    # Action if --with-sbcl-home is given.
+    [AS_IF([test -d "$withval"],
+           [SBCL_HOME=$withval],
+           [AC_MSG_ERROR([--with-sbcl-home directory does not exist.])])],
+    # Action if --with-sbcl-home is not given.
+    # If the path of sbcl is P, then SBCL_HOME will be P/../lib/sbcl.
+    # This is in line with https://github.com/sbcl/sbcl/blob/master/INSTALL.
+    #
+    ## The SBCL runtime needs to be able to find the ancillary files
+    ## associated with it: the "sbcl.core" file, and the contrib modules.
+    ## Finding sbcl.core can happen in the following ways:
+    ##
+    ##   * By default, in a location configured when the system was built.
+    ##     For binary distributions this is in "/usr/local/lib/sbcl".
+    ##
+    ##   * In the directory ../lib/sbcl relative to the executable.
+    ##
+    ##   * In the current directory.
+    ##
+    ##   * By environment variable, in the directory named by the
+    ##     environment variable "SBCL_HOME". Example:
+    ##
+    ##        $ export SBCL_HOME=/foo/bar/lib/sbcl
+    ##        $ sbcl
+    ##
+    ##     If your "INSTALL_ROOT" was FOO, then your "SBCL_HOME" is
+    ##     "FOO/lib/sbcl".
+    ##
+    ##   * By command line option:
+    ##
+    ##         $ sbcl --core /foo/bar/sbcl.core
+
+    [lisp_prog=`echo $fricas_lisp | sed 's/ .*//'` # remove parameters
+     lisp_path=`which $lisp_prog` # cannot be empty
+     SBCL_HOME="${lisp_path%sbcl}../lib/sbcl"]
+)
+
+AC_MSG_NOTICE([SBCL_HOME=$SBCL_HOME])
+AC_SUBST(SBCL_HOME)
+AC_SUBST(WEBSPAD_REQUIREMENTS)
+
 FRICAS_MAKEFILE([Makefile])
 FRICAS_MAKEFILE([src/Makefile])
 FRICAS_MAKEFILE([src/lib/Makefile])
diff --git a/contrib/webspad/webspad.lisp b/contrib/webspad/webspad.lisp
new file mode 100644
index 00000000..4b96fd17
--- /dev/null
+++ b/contrib/webspad/webspad.lisp
@@ -0,0 +1,233 @@
+;;; https://jfricas.readthedocs.io
+;;; https://github.com/fricas/jfricas.
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; This file is loaded into FriCAS at initialization time.
+;;; It serves the following purposes
+;;; (a) makes a webserver (Hunchentoot) available,
+;;; (b) extends/modifies some FriCAS (package "BOOT") behavior,
+;;; (c) provides functions (package "webspad") that take a multiline
+;;;     (FriCAS) code string, send it to FriCAS and catch the
+;;;     FriCAS output.
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; BOOT is a Package in FriCAS.
+;;  This part adds a few functions to the BOOT package of FriCAS
+;;; to make the FriCAS output contain a number of markers (via the
+;;; |$ioHook| facility) that make parsing of the output easier.
+;;; Furthermore, it contains a function, interpret-block, which
+;;; interprets a (potentially multiline) block of code.
+
+;;; The function "interpret-block" might be a candidate for inclusion
+;;; in FriCAS.
+
+;;; The output formats TexFormat, TexmacsFormat, MathMLFormat can
+;;; easily be recognized in the output stream and thus need no extra
+;;; markers. However, the algebra output, which is switched on via
+;;; ")set output algebra on" in a FriCAS session, does not have any
+;;; begin/end markers. They are added here. We also add markers for
+;;; the output of the 'error' function in FriCAS and the messages that
+;;; are used in FriCAS to inform the user (KeyedMsg). Also type, time,
+;;; and storage information come on the KeyedMsg channel. They are,
+;;; however, so important to us, that we catch them here by their
+;;; message tags and mark them in a special way.
+
+;;; In particular, the storage message is important here, since its
+;;; markers are used in fricaskernel.py to extract the step number of
+;;; FriCAS and synchronize the Out[n] number in the Jupyter notebook
+;;; with it.
+
+;;; Note that storage output is normally not switched on. Since it is
+;;; vital for us, we turn it on before we send any code to FriCAS.
+
+;;; We explicitly ignore (turn off) formats that do not seem to be
+;;; relevant in a Jupyter notebook and are, in fact, because of
+;;; missing begin/end markers hard to detect in the output stream.
+;;; Yes, it was a design decision to turn off the FriCAS formats
+;;; FortranFormat, HtmlFormat, and OpenMathFormat.
+
+;;; All markers that we introduce here look like
+;;;   --FORMAT:BEG:formatname:stepnumber
+;;;   --FORMAT:END:formatname:stepnumber
+;;; where the stepnumber is given by boot::|$IOindex| of FriCAS and
+;;; formatname is given below.
+
+(in-package "BOOT")
+
+(defun ws-fmt (marker)
+  (|sayMSG| (format nil "--FORMAT:~A:~D" marker boot::|$IOindex|)))
+
+(setf |$ioHook|
+      (lambda (x &optional args)
+        (cond
+;         ((eq x '|startAlgebraOutput|) (ws-fmt "BEG:Algebra"))
+;         ((eq x '|endOfAlgebraOutput|) (ws-fmt "END:Algebra"))
+         ((eq x '|startPatternMsg|)    (ws-fmt "BEG:ERROR"))
+         ((eq x '|endPatternMsg|)      (ws-fmt "END:ERROR"))
+         ((eq x '|startKeyedMsg|)
+          (cond
+           ((eq (car args) 'S2GL0012) (ws-fmt "BEG:Type"))
+           ((eq (car args) 'S2GL0013) (ws-fmt "BEG:Time"))
+           ((eq (car args) 'S2GL0014) (ws-fmt "BEG:TypeTime"))
+           ((eq (car args) 'S2GL0016) (ws-fmt "BEG:Storage"))
+           ('T                        (ws-fmt "BEG:KeyedMsg"))))
+         ((eq x '|endOfKeyedMsg|)
+          (cond
+           ((eq (car args) 'S2GL0012) (ws-fmt "END:Type"))
+           ((eq (car args) 'S2GL0013) (ws-fmt "END:Time"))
+           ((eq (car args) 'S2GL0014) (ws-fmt "END:TypeTime"))
+           ((eq (car args) 'S2GL0016) (ws-fmt "END:Storage"))
+           ('T                        (ws-fmt "END:KeyedMsg"))))
+        )))
+
+;;; Override a FriCAS function in order to remove the equation number.
+(DEFUN |mathprintWithNumber| (|x|)
+  (PROG ()
+    (RETURN
+     (PROGN
+      ;(|ioHook| '|startAlgebraOutput|)
+      (|saySpadMsg| '|--FORMAT:BEG:Algebra|)
+      (|maprin| (|outputTran2| |x|))
+      (|saySpadMsg| '|--FORMAT:END:Algebra|)
+      ;(|ioHook| '|endOfAlgebraOutput|)
+      ))))
+
+;;; interpret-block takes a code string that is interpreted as if it
+;;; comes from a .input file.
+(DEFUN |interpret-block| (|code|)
+  (PROG (|$newcompErrorCount| |$inclAssertions| |$ncMsgList|
+         |$erMsgToss| |$lastPos| |$EchoLines| |st|)
+        (DECLARE (SPECIAL |$newcompErrorCount| |$inclAssertions| |$ncMsgList|
+                          |$erMsgToss| |$lastPos| |$EchoLines|))
+    (RETURN
+     (PROGN
+      (SETQ |$newcompErrorCount| 0)
+      (SETQ |$inclAssertions| NIL)
+      (SETQ |$EchoLines| NIL)
+      (SETQ |$lastPos| |$nopos|)
+      (SETQ |$erMsgToss| NIL)
+      (SETQ |$ncMsgList| NIL)
+      (SETQ |st| (MAKE-STRING-INPUT-STREAM |code|))
+      (|intloopInclude0| |st| '|webspad| 0)))))
+
+;;; Following function calls FriCAS for evaluation of code and returns
+;;; true if ther is an error and nil otherwise.
+(defun |webspad-parseAndEvalStr| (code)
+;  (setf |$printTypeIfTrue| T)    ; Make sure we get "Type:" line.
+;  (setf |$printTimeIfTrue| T)    ; Make sure we get "Time:" line.
+  (setf |$printStorageIfTrue| T) ; Make sure we get "Storage:" line.
+  (setf |$fortranFormat| NIL)    ; we don't want Fortran output
+  (setf |$htmlFormat| NIL)       ; we don't want Html output
+  (setf |$openMathFormat| NIL)   ; we don't want OpenMath output
+  (setf |$MARGIN| 0)             ; we don't want indentation
+
+  ;;; code might consist of multiple lines (i.e. contain newline
+  ;;; characters)
+  (eq (catch 'SPAD_READER (catch '|top_level| (|interpret-block| code)))
+      '|restart|))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defpackage webspad
+    (:use common-lisp)
+    (:documentation "see source code"))
+
+(in-package :webspad)
+
+;;; The function webspad-eval is called from the Jupyter notebook via
+;;; fricaskernel.py and the Hunchentoot webserver. It receives the
+;;; code of a cell as input. Before this code is sent to FriCAS, all
+;;; relevant streams that FriCAS writes to are stored and replaced by
+;;; a new and empty string output stream. After evaluation by FriCAs,
+;;; the output is fetched and the saved stream are restored.
+
+(defstruct r
+  (stdout    "" :type string)
+  (error?    "" :type string)
+  (input     "" :type string))
+
+;;; One stream that catches all output.
+(defvar webspad-stream (make-string-output-stream))
+
+(defun webspad-eval (input)
+  (let* (
+        ;;; store original input argument
+         (data (make-r :input input))
+         ;;; Because we want to read from the fricas streams via
+         ;;; get-output-stream-string, we must make sure that the
+         ;;; streams are created via make-string-output-stream.
+         ;;; Therefore, we first save the original streams.
+         ;;; If |$formattedOutputStream| is not in the FriCAS code base,
+         ;;; then that stream is not considered.
+         (formattedp (boundp 'boot::|$formattedOutputStream|))
+         (s-formatted (if formattedp boot::|$formattedOutputStream|))
+         (s-stdout    boot::*standard-output*)
+         (s-stderr    boot::*error-output*)
+         (s-algebra   boot::|$algebraOutputStream|)
+         (s-tex       boot::|$texOutputStream|)
+         (s-mathml    boot::|$mathmlOutputStream|)
+         (s-texmacs   boot::|$texmacsOutputStream|)
+         (*OBEY-STDOUT* T)
+         (*standard-output* webspad-stream)
+         (*error-output* *standard-output*)
+         (s           (boot::|mkOutputConsoleStream|))) ; use *standard-output*
+
+    ;;; create empty streams
+    (setf boot::|$algebraOutputStream|   s)
+    (setf boot::|$texOutputStream|       s)
+    (setf boot::|$mathmlOutputStream|    s)
+    (setf boot::|$texmacsOutputStream|   s)
+    (if formattedp (setf boot::|$formattedOutputStream| s))
+
+    ;;; eval and return true if there was an error
+    (setf (r-error? data) (if (boot::|webspad-parseAndEvalStr| input) "T" "F"))
+
+    (setf (r-stdout data) (get-output-stream-string boot::*standard-output*))
+    (if formattedp (setf boot::|$formattedOutputStream| s-formatted))
+
+    (setf boot::|$algebraOutputStream|   s-algebra)
+    (setf boot::|$texOutputStream|       s-tex)
+    (setf boot::|$mathmlOutputStream|    s-mathml)
+    (setf boot::|$texmacsOutputStream|   s-texmacs)
+
+    ;;; return the data record
+    data))
+
+;;; The function spad-eval is only intended to call FriCAS and return
+;;; its result with one-line code.
+(defun spad-eval (code)
+  (format nil "~{~A~%~}" (boot::|parseAndEvalToString| code)))
+
+
+;;; fricaskernel.py expects the output in form of a json record.
+(defun encode-json (data)
+  (format nil "{ \"stdout\":~S,~
+                 \"error?\":~S,~
+                 \"input\":~S~
+               }"
+          (r-stdout data)
+          (r-error? data)
+          (r-input data)))
+
+
+;;; Make the url http://localhost:PORT/eval?code=sin(x) available.
+(hunchentoot:define-easy-handler (fricas-eval :uri "/eval") (code)
+  (setf (hunchentoot:content-type*) "text/plain")
+  (format nil "~A~%" (spad-eval code)))
+
+;;; Make the url
+;;; http://localhost:PORT/raw?code=integrate(sin(x),x) available.
+(hunchentoot:define-easy-handler (fricas-raw :uri "/raw") (code)
+  (setf (hunchentoot:content-type*) "text/plain")
+  (format nil "~A~%" (webspad-eval code)))
+
+;;; Make the url
+;;; http://localhost:PORT/json?code=D(sin(x),x) available.
+(hunchentoot:define-easy-handler (fricas-json :uri "/json") (code)
+  (setf (hunchentoot:content-type*) "text/plain")
+  (format nil "~A~%" (encode-json (webspad-eval code))))
+
+;;; Start the Hunchentoot webserver.
+(defun start (port address)
+  (hunchentoot:start
+    (make-instance 'hunchentoot:easy-acceptor :port port :address address)))
diff --git a/src/interp/Makefile.in b/src/interp/Makefile.in
index d5861b63..c85aaef6 100644
--- a/src/interp/Makefile.in
+++ b/src/interp/Makefile.in
@@ -1,3 +1,5 @@
+WEBSPAD_REQUIREMENTS = @WEBSPAD_REQUIREMENTS@
+SBCL_HOME = @SBCL_HOME@
 
 subdir = src/interp/
 
@@ -158,6 +160,7 @@ ${FRICASSYS}: ../etc/stamp-databases
 	   '#+:cmu (setq *compile-verbose* nil)' \
 	   '#+:cmu (setq *compile-print* nil)' \
 	   '#+:cmu (declaim (optimize (ext:inhibit-warnings 3)))' \
+	   '${WEBSPAD_REQUIREMENTS}'\
 	   '#+:ecl(FRICAS-LISP::make-program "$(BASE)$@" nil)' \
 	   '#-:ecl(BOOT::spad-save "$(BASE)$@" t)' \
              | DAASE="$(BASE)$(fricas_targetdir)" ${BOOTSYS}
diff --git a/src/interp/vmlisp.lisp b/src/interp/vmlisp.lisp
index df009e49..19a6e54b 100644
--- a/src/interp/vmlisp.lisp
+++ b/src/interp/vmlisp.lisp
@@ -718,11 +718,17 @@
 #+:allegro
 (defun OBEY (S) (excl::run-shell-command s))
 
+(defvar *OBEY-STDOUT* nil "if T use *standard output*")
 #+:sbcl
 (defun OBEY (S)
-   #-:win32 (sb-ext::process-exit-code
-             (sb-ext::run-program "/bin/sh"
-                    (list "-c" S) :input t :output t :error t))
+   #-:win32 (if *OBEY-STDOUT*
+               (sb-ext::process-exit-code
+                (sb-ext::run-program "/bin/sh" (list "-c" S) :input t
+                     :output *standard-output* :error *standard-output*))
+
+               (sb-ext::process-exit-code
+                (sb-ext::run-program "/bin/sh"
+                    (list "-c" S) :input t :output t :error t)))
    #+:win32 (sb-ext::process-exit-code
              (sb-ext::run-program "sh"
                     (list "-c" S) :input t :output t :error t :search t)))
-- 
2.34.1

Reply via email to