0.6.12.7.flaky1.2:
[sbcl.git] / src / code / toplevel.lisp
index 0b5dce9..02504a2 100644 (file)
@@ -12,9 +12,6 @@
 ;;;; files for more information.
 
 (in-package "SB!IMPL")
-
-(file-comment
-  "$Header$")
 \f
 (defconstant most-positive-fixnum #.sb!vm:*target-most-positive-fixnum*
   #!+sb-doc
@@ -40,7 +37,7 @@
 (declaim
   #!-gengc
   (special *gc-inhibit* *already-maybe-gcing*
-          *need-to-collect-garbage* *gc-verbose*
+          *need-to-collect-garbage*
           *gc-notify-stream*
           *before-gc-hooks* *after-gc-hooks*
           #!+x86 *pseudo-atomic-atomic*
@@ -49,7 +46,7 @@
           sb!unix::*interrupt-pending*
           *type-system-initialized*)
   #!+gengc
-  (special *gc-verbose* *before-gc-hooks* *after-gc-hooks*
+  (special *before-gc-hooks* *after-gc-hooks*
           *gc-notify-stream*
           *type-system-initialized*))
 
             (* (floor initial-offset sb!vm:word-bytes) sb!vm:word-bytes)
             0))))
 \f
-;;;; the default TOPLEVEL function
+;;;; the default toplevel function
+
+;;; FIXME: Most stuff below here can probably be byte-compiled.
 
 (defvar / nil
   #!+sb-doc
   #!+sb-doc
   "The top-level prompt string. This also may be a function of no arguments
    that returns a simple-string.")
-(defvar *in-top-level-catcher* nil
-  #!+sb-doc
-  "Are we within the Top-Level-Catcher? This is used by interrupt
-   handlers to see whether it is OK to throw.")
 
 (defun interactive-eval (form)
   "Evaluate FORM, returning whatever it returns and adjusting ***, **, *,
   (values))
 
 ;;; the default system top-level function
-(defun toplevel ()
+(defun toplevel-init ()
 
-  (/show0 "entering TOPLEVEL")
+  (/show0 "entering TOPLEVEL-INIT")
   
-  (let ((sysinit nil)      ; value of --sysinit option
-       (userinit nil)     ; value of --userinit option
-       (evals nil)        ; values of --eval options (in reverse order)
-       (noprint nil)      ; Has a --noprint option been seen?
-       (noprogrammer nil) ; Has a --noprogammer option been seen?
+  (let ((sysinit nil)        ; value of --sysinit option
+       (userinit nil)       ; value of --userinit option
+       (reversed-evals nil) ; values of --eval options, in reverse order
+       (noprint nil)        ; Has a --noprint option been seen?
+       (noprogrammer nil)   ; Has a --noprogammer option been seen?
        (options (rest *posix-argv*))) ; skipping program name
 
-    (/show0 "done with outer LET in TOPLEVEL")
+    (/show0 "done with outer LET in TOPLEVEL-INIT")
   
-    ;; FIXME: There are lots of ways for errors to happen around here (e.g. bad
-    ;; command line syntax, or READ-ERROR while trying to READ an --eval
-    ;; string). Make sure that they're handled reasonably.
-
+    ;; FIXME: There are lots of ways for errors to happen around here
+    ;; (e.g. bad command line syntax, or READ-ERROR while trying to
+    ;; READ an --eval string). Make sure that they're handled
+    ;; reasonably. Also, perhaps all errors while parsing the command
+    ;; line should cause the system to QUIT, instead of trying to go
+    ;; into the Lisp debugger.
+    
     ;; Parse command line options.
     (loop while options do
-         (/show0 "at head of LOOP WHILE OPTIONS DO in TOPLEVEL")
+         (/show0 "at head of LOOP WHILE OPTIONS DO in TOPLEVEL-INIT")
          (let ((option (first options)))
            (flet ((pop-option ()
                     (if options
                                  (error "more than one expression in ~S"
                                         eval-as-string))
                                 (t
-                                 (push eval evals)))))))
+                                 (push eval reversed-evals)))))))
                    ((string= option "--noprint")
                     (pop-option)
                     (setf noprint t))
                     ;; because we didn't recognize an option as a
                     ;; toplevel option, then the option we gave up on
                     ;; must have been an error. (E.g. in
-                    ;;   sbcl --eval '(a)' --evl '(b)' --end-toplevel-options
-                    ;; this test will let us detect that "--evl" is
-                    ;; an error.)
+                    ;;  "sbcl --eval '(a)' --eval'(b)' --end-toplevel-options"
+                    ;; this test will let us detect that the string
+                    ;; "--eval(b)" is an error.)
                     (if (find "--end-toplevel-options" options
                               :test #'string=)
                         (error "bad toplevel option: ~S" (first options))
                         (return)))))))
-    (/show0 "done with LOOP WHILE OPTIONS DO in TOPLEVEL")
+    (/show0 "done with LOOP WHILE OPTIONS DO in TOPLEVEL-INIT")
 
-    ;; Excise all the options that we processed, so that only user-level
-    ;; options are left visible to user code.
+    ;; Excise all the options that we processed, so that only
+    ;; user-level options are left visible to user code.
     (setf (rest *posix-argv*) options)
 
+    ;; Handle --noprogrammer option. We intentionally do this
+    ;; early so that it will affect the handling of initialization
+    ;; files and --eval options.
+    (/show0 "handling --noprogrammer option in TOPLEVEL-INIT")
+    (when noprogrammer
+      (setf *debugger-hook* 'noprogrammer-debugger-hook-fun
+           *debug-io* *error-output*))
+
     ;; FIXME: Verify that errors in init files and/or --eval operations
     ;; lead to reasonable behavior.
 
     ;; Handle initialization files.
-    (/show0 "handling initialization files in TOPLEVEL")
+    (/show0 "handling initialization files in TOPLEVEL-INIT")
     (flet (;; If any of POSSIBLE-INIT-FILE-NAMES names a real file,
           ;; return its truename.
           (probe-init-files (&rest possible-init-file-names)
                          possible-init-file-names)
               (/show0 "leaving PROBE-INIT-FILES"))))
       (let* ((sbcl-home (posix-getenv "SBCL_HOME"))
-            #!+sb-show(ignore1 (progn
-                                 (/show0 "SBCL-HOME=..")
-                                 (if sbcl-home
-                                     (%primitive print sbcl-home)
-                                     (%primitive print "NIL"))))
             (sysinit-truename (if sbcl-home
                                   (probe-init-files sysinit
                                                     (concatenate
             (user-home (or (posix-getenv "HOME")
                            (error "The HOME environment variable is unbound, ~
                                    so user init file can't be found.")))
-            #!+sb-show(ignore2 (progn
-                                 (/show0 "USER-HOME=..")
-                                 (%primitive print user-home)))
             (userinit-truename (probe-init-files userinit
                                                  (concatenate
                                                   'string
                                                   user-home
                                                   "/.sbclrc"))))
        (/show0 "assigned SYSINIT-TRUENAME and USERINIT-TRUENAME")
-       (when sysinit-truename
-         (/show0 "SYSINIT-TRUENAME=..")
-         #!+sb-show (%primitive print sysinit-truename)
-         (unless (load sysinit-truename)
-           (error "~S was not successfully loaded." sysinit-truename))
-         (flush-standard-output-streams))
-       (/show0 "loaded SYSINIT-TRUENAME")
-       (when userinit-truename
-         (/show0 "USERINIT-TRUENAME=..")
-         #!+sb-show (%primitive print userinit-truename)
-         (unless (load userinit-truename)
-           (error "~S was not successfully loaded." userinit-truename))
-         (flush-standard-output-streams))
-       (/show0 "loaded USERINIT-TRUENAME"))
-
-      ;; Handle --eval options.
-      (/show0 "handling --eval options in TOPLEVEL")
-      (dolist (eval (reverse evals))
-       (/show0 "handling one --eval option in TOPLEVEL")
-       (eval eval)
-       (flush-standard-output-streams))
-
-      ;; Handle stream binding controlled by --noprogrammer option.
-      ;;
-      ;; FIXME: When we do actually implement this, shouldn't it go
-      ;; earlier in the sequence, so that its stream bindings will
-      ;; affect the behavior of init files and --eval options?
-      (/show0 "handling --noprogrammer option in TOPLEVEL")
-      (when noprogrammer
-       (warn "stub: --noprogrammer option unimplemented")) ; FIXME
-
-      (/show0 "falling into TOPLEVEL-REPL from TOPLEVEL")
+
+
+       ;; We wrap all the pre-REPL user/system customized startup code 
+       ;; in a restart.
+       ;;
+       ;; (Why not wrap everything, even the stuff above, in this
+       ;; restart? Errors above here are basically command line or
+       ;; Unix environment errors, e.g. a missing file or a typo on
+       ;; the Unix command line, and you don't need to get into Lisp
+       ;; to debug them, you should just start over and do it right
+       ;; at the Unix level. Errors below here are usually errors in
+       ;; user Lisp code, and it might be helpful to let the user
+       ;; reach the REPL in order to help figure out what's going on.)
+       (restart-case
+           (flet ((process-init-file (truename)
+                    (when truename
+                      (unless (load truename)
+                        (error "~S was not successfully loaded." truename))
+                      (flush-standard-output-streams))))
+             (process-init-file sysinit-truename)
+             (process-init-file userinit-truename)
+
+             ;; Process --eval options.
+             (/show0 "handling --eval options in TOPLEVEL-INIT")
+             (dolist (eval (reverse reversed-evals))
+               (/show0 "handling one --eval option in TOPLEVEL-INIT")
+               (eval eval)
+               (flush-standard-output-streams)))
+         (continue ()
+                   :report "Continue anyway (skipping to toplevel read/eval/print loop)."
+                   (values)) ; (no-op, just fall through)
+         (quit ()
+               :report "Quit SBCL (calling #'QUIT, killing the process)."
+               (quit))))
+
+      ;; one more time for good measure, in case we fell out of the
+      ;; RESTART-CASE above before one of the flushes in the ordinary
+      ;; flow of control had a chance to operate
+      (flush-standard-output-streams)
+
+      (/show0 "falling into TOPLEVEL-REPL from TOPLEVEL-INIT")
       (toplevel-repl noprint))))
 
 ;;; read-eval-print loop for the default system toplevel
        (/// nil) (// nil) (/ nil)
        (eof-marker (cons :eof nil)))
     (loop
-      ;; FIXME: This seems to be the source of one of the basic debugger
-      ;; choices in
-      ;;    Restarts:
-      ;;      0: [CONTINUE] Return from BREAK.
-      ;;      1: [ABORT   ] Return to toplevel.
-      ;; (The "Return from BREAK" choice is defined in BREAK.) I'd like to add
-      ;; another choice,
-      ;;      2: [TERMINATE] Terminate the current Lisp.
-      ;; That way, a user hitting ^C could get out of Lisp without knowing
-      ;; enough about the system to run (SB-EXT:QUIT).
-      ;;
-      ;; If I understand the documentation of WITH-SIMPLE-RESTART correctly,
-      ;; it shows how to replace this WITH-SIMPLE-RESTART with a RESTART-CASE
-      ;; with two choices (ABORT and QUIT). Or perhaps ABORT should be renamed
-      ;; TOPLEVEL?
-      ;;    Restarts:
-      ;;      0: [CONTINUE ] Return from BREAK, continuing calculation
-      ;;                    as though nothing happened.
-      ;;      1: [TOPLEVEL ] Transfer control to toplevel read/eval/print
-      ;;                    loop, aborting current calculation.
-      ;;      2: [TERMINATE] Terminate the current Lisp (equivalent to
-      ;;                    executing (SB-EXT:QUIT)).
       (/show0 "at head of outer LOOP in TOPLEVEL-REPL")
-      (with-simple-restart (abort "Return to toplevel.")
-       (catch 'top-level-catcher
-         (sb!unix:unix-sigsetmask 0) ; FIXME: What is this for?
-         (let ((*in-top-level-catcher* t))
-           (/show0 "about to enter inner LOOP in TOPLEVEL-REPL")
-           (loop                       ; FIXME: Do we need this inner LOOP?
-            ;; FIXME: It seems bad to have GC behavior depend on scrubbing
-            ;; the control stack before each interactive command. Isn't
-            ;; there some way we can convince the GC to just ignore
-            ;; dead areas of the control stack, so that we don't need to
-            ;; rely on this half-measure?
-            (scrub-control-stack)
-            (unless noprint
-              (fresh-line)
-              (princ (if (functionp *prompt*)
-                         (funcall *prompt*)
-                         *prompt*))
-              (flush-standard-output-streams))
-            (let ((form (read *standard-input* nil eof-marker)))
-              (if (eq form eof-marker)
-                  (quit)
-                  (let ((results
-                         (multiple-value-list (interactive-eval form))))
-                    (unless noprint
-                      (dolist (result results)
-                        (fresh-line)
-                        (prin1 result)))))))))))))
+      ;; There should only be one TOPLEVEL restart, and it's here, so
+      ;; restarting at TOPLEVEL always bounces you all the way out here.
+      (with-simple-restart (toplevel
+                           "Restart at toplevel READ/EVAL/PRINT loop.")
+       ;; We add a new ABORT restart for every debugger level, so 
+       ;; restarting at ABORT in a nested debugger gets you out to the
+        ;; innermost enclosing debugger, and only when you're in the
+        ;; outermost, unnested debugger level does restarting at ABORT 
+       ;; get you out to here.
+        (with-simple-restart (abort
+                             "Reduce debugger level (leaving debugger).")
+         (catch 'top-level-catcher
+         (sb!unix:unix-sigsetmask 0)   ; FIXME: What is this for?
+         (/show0 "about to enter inner LOOP in TOPLEVEL-REPL")
+         (loop                         ; FIXME: Do we need this inner LOOP?
+          ;; FIXME: It seems bad to have GC behavior depend on scrubbing
+          ;; the control stack before each interactive command. Isn't
+          ;; there some way we can convince the GC to just ignore
+          ;; dead areas of the control stack, so that we don't need to
+          ;; rely on this half-measure?
+          (scrub-control-stack)
+          (unless noprint
+            (fresh-line)
+            (princ (if (functionp *prompt*)
+                       (funcall *prompt*)
+                       *prompt*))
+            (flush-standard-output-streams))
+          (let ((form (read *standard-input* nil eof-marker)))
+            (if (eq form eof-marker)
+                (quit)
+                (let ((results
+                       (multiple-value-list (interactive-eval form))))
+                  (unless noprint
+                    (dolist (result results)
+                      (fresh-line)
+                      (prin1 result)))))))))))))
+
+(defun noprogrammer-debugger-hook-fun (condition old-debugger-hook)
+  (declare (ignore old-debugger-hook))
+  (flet ((failure-quit (&key recklessly-p)
+          (quit :unix-status 1 :recklessly-p recklessly-p)))
+    ;; This HANDLER-CASE is here mostly to stop output immediately
+    ;; (and fall through to QUIT) when there's an I/O error. Thus,
+    ;; when we're run under a shell script or something, we can die
+    ;; cleanly when the script dies (and our pipes are cut), instead
+    ;; of falling into ldb or something messy like that.
+    (handler-case
+       (progn
+         (format *error-output*
+                 "~@<unhandled condition (of type ~S): ~2I~_~A~:>~2%"
+                 (type-of condition)
+                 condition)
+         ;; Flush *ERROR-OUTPUT* even before the BACKTRACE, so that
+         ;; even if we hit an error within BACKTRACE we'll at least
+         ;; have the CONDITION printed out before we die.
+         (finish-output *error-output*)
+         ;; (Where to truncate the BACKTRACE is of course arbitrary, but
+         ;; it seems as though we should at least truncate it somewhere.)
+         (sb!debug:backtrace 128 *error-output*)
+         (format *error-output*
+                 "~%unhandled condition in --noprogrammer mode, quitting~%")
+         (finish-output *error-output*)
+         (failure-quit))
+      (condition ()
+       ;; We IGNORE-ERRORS here because even %PRIMITIVE PRINT can
+       ;; fail when our output streams are blown away, as e.g. when
+       ;; we're running under a Unix shell script and it dies somehow
+       ;; (e.g. because of a SIGINT). In that case, we might as well
+       ;; just give it up for a bad job, and stop trying to notify
+       ;; the user of anything.
+        ;;
+        ;; Actually, the only way I've run across to exercise the
+       ;; problem is to have more than one layer of shell script.
+       ;; I have a shell script which does
+       ;;   time nice -10 sh make.sh "$1" 2>&1 | tee make.tmp
+       ;; and the problem occurs when I interrupt this with Ctrl-C
+       ;; under Linux 2.2.14-5.0 and GNU bash, version 1.14.7(1).
+        ;; I haven't figured out whether it's bash, time, tee, Linux, or
+       ;; what that is responsible, but that it's possible at all
+       ;; means that we should IGNORE-ERRORS here. -- WHN 2001-04-24
+        (ignore-errors
+         (%primitive print "Argh! error within --noprogrammer error handling"))
+       (failure-quit :recklessly-p t)))))
 \f
 ;;; a convenient way to get into the assembly-level debugger
 (defun %halt ()