Bug fix: simplode on a singleton list could return a non-string
[maxima.git] / src / merror.lisp
blob1cc95fd12c1ab4c5e50f84bf4ec7a2f2268e496c
1 ;;; -*- Mode: Lisp; Package: Maxima; Syntax: Common-Lisp; Base: 10 -*- ;;;;
2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;;; The data in this file contains enhancments. ;;;;;
4 ;;; ;;;;;
5 ;;; Copyright (c) 1984,1987 by William Schelter,University of Texas ;;;;;
6 ;;; All rights reserved ;;;;;
7 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
8 ;;; (c) Copyright 1982 Massachusetts Institute of Technology ;;;
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
11 (in-package :maxima)
13 (macsyma-module merror)
15 ;;; Macsyma error signalling.
16 ;;; 2:08pm Tuesday, 30 June 1981 George Carrette.
18 (defmvar $error `((mlist simp) "No error.")
19 "During an MAXIMA-ERROR break this is bound to a list
20 of the arguments to the call to MAXIMA-ERROR, with the message
21 text in a compact format.")
23 (defmvar $errormsg 't
24 "If `false' then no maxima-error message is printed!")
26 (defmfun $error (&rest l)
27 "Signals a Maxima user error."
28 (apply #'merror (fstringc l)))
30 (defmfun $warning (&rest l)
31 "Signals a Maxima warning."
32 (apply #'mwarning l))
34 (defmvar $error_size 60.
35 "Expressions greater in SOME size measure over this value
36 are replaced by symbols {ERREXP1, ERREXP2,...} in the MAXIMA-ERROR
37 display, the symbols being set to the expressions, so that one can
38 look at them with expression editing tools. The default value of
39 this variable may be determined by factors of terminal speed and type.")
41 (defun error-size (exp)
42 ; RATDISREP the argument in case it's a CRE. Ugh.
43 ; But RATDISREP simplifies its argument, which is a no-no if we got here
44 ; because some simplification code is complaining, so inhibit simplification. Double ugh.
45 (let (($simp nil))
46 (declare (special $simp))
47 (setq exp (ratdisrep exp)))
49 (if (atom exp)
51 (do ((l (cdr exp) (cdr l))
52 (n 1 (1+ (+ n (error-size (car l))))))
53 ((or (atom l)
54 ;; no need to go any further, and this will save us
55 ;; from circular structures. (Which the display
56 ;; package would have a hell of a time with too.)
57 (> n $error_size))
58 n))))
60 ;;; Problem: Most macsyma users do not take advantage of break-points
61 ;;; for debugging. Therefore they need to have the error variables
62 ;;; SET (as the old ERREXP was), and not PROGV bound. The problem with
63 ;;; this is that recursive errors will bash the old value of the
64 ;;; error variables. However, since we do bind the value of the
65 ;;; variable $ERROR, calling the function $ERRORMSG will always
66 ;;; set things back. It would be better to bind these variables,
67 ;;; for, amoung other things, then the values could get garbage
68 ;;; collected.
70 (define-condition maxima-$error (error)
71 ((message :initform $error :reader the-$error))
72 (:documentation "Muser error, to be signalled by MERROR, usually.")
73 (:report (lambda (c stream)
74 (declare (ignore c))
75 (let ((*standard-output* stream))
76 ($errormsg)))))
78 (defun merror (sstring &rest l)
79 (declare (special errcatch *mdebug*))
80 (setq $error `((mlist simp) ,sstring ,@ l))
81 (and $errormsg ($errormsg))
82 (cond (*mdebug*
83 (let ((dispflag t) ret)
84 (declare (special dispflag))
85 (format t (intl:gettext " -- an error. Entering the Maxima debugger.~%~
86 Enter ':h' for help.~%"))
87 (progn
88 (setq ret (break-dbm-loop nil))
89 (cond ((eql ret :resume)
90 (break-quit))))))
91 (errcatch (error 'maxima-$error))
93 (fresh-line *standard-output*)
94 ($backtrace 3)
95 (format t (intl:gettext "~& -- an error. To debug this try: debugmode(true);~%"))
96 (force-output)
97 (throw 'macsyma-quit 'maxima-error))))
99 (defun mwarning (&rest l)
100 (format t "Warning: ~{~a~^ ~}~%" (mapcar #'$sconcat l)))
102 (defmacro with-$error (&body body)
103 "Let MERROR signal a MAXIMA-$ERROR condition."
104 `(let ((errcatch t)
105 *mdebug* ;let merror signal a lisp error
106 $errormsg) ;don't print $error
107 (declare (special errcatch *mdebug* $errormsg))
108 ,@body))
110 ;; Sample:
111 ;; (defun h (he)
112 ;; (merror "hi there ~:M and ~:M" he he))
113 ;; This will signal a MAXIMA-$ERROR condition:
114 ;; (with-$error (h '$you))
116 (defmvar $error_syms '((mlist) $errexp1 $errexp2 $errexp3)
117 "Symbols to bind the too-large `maxima-error' expresssions to")
119 (defun-prop ($error_syms assign) (var val)
120 (if (not (and ($listp val)
121 (do ((l (cdr val) (cdr l)))
122 ((null l) (return t))
123 (if (not (symbolp (car l))) (return nil)))))
124 (merror (intl:gettext "assignment: assignment to ~M must be a list of symbols; found: ~M")
125 var val)))
127 (defun process-error-argl (l)
128 ;; This returns things so that we could set or bind.
129 (do ((error-symbols nil)
130 (error-values nil)
131 (new-argl nil)
132 (symbol-number 0))
133 ((null l)
134 (list (nreverse error-symbols)
135 (nreverse error-values)
136 (nreverse new-argl)))
137 (let ((form (pop l)))
138 (cond ((> (error-size form) $error_size)
139 (incf symbol-number)
140 (let ((sym (nthcdr symbol-number $error_syms)))
141 (cond (sym
142 (setq sym (car sym)))
144 (setq sym (intern (format nil "~A~D" '$errexp
145 symbol-number)))
146 (tuchus $error_syms sym)))
147 (push sym error-symbols)
148 (push form error-values)
149 (push sym new-argl)))
151 (push form new-argl))))))
153 (defmfun $errormsg ()
154 "errormsg() redisplays the maxima-error message while in a `maxima-error' break."
155 ;; Don't optimize out call to PROCESS-ERROR-ARGL in case of
156 ;; multiple calls to $ERRORMSG, because the user may have changed
157 ;; the values of the special variables controlling its behavior.
158 ;; The real expense here is when MFORMAT calls the DISPLA package.
159 (let ((the-jig (process-error-argl (cddr $error))))
160 (mapc #'(lambda (v x) (setf (symbol-value v) x)) (car the-jig) (cadr the-jig))
161 (fresh-line)
162 (let ((errset nil))
163 (if (null (errset
164 (apply #'mformat nil
165 (cadr $error) (caddr the-jig))))
166 (mtell (intl:gettext "~%** error while printing error message **~%~A~%")
167 (cadr $error)
169 (fresh-line))
170 '$done)
172 (defmfun read-only-assign (var val)
173 (if munbindp
174 'munbindp
175 (merror (intl:gettext "assignment: attempting to assign read-only variable ~:M the value ~M") var val)))
178 (defprop $error read-only-assign assign)
180 ;; RAT-ERROR (function)
182 ;; Throw to the nearest enclosing RAT-ERR tag (set by IGNORE-RAT-ERROR or
183 ;; RAT-ERROR-TO-MERROR). If ERROR-ARGS is nonzero, they are thrown. The
184 ;; RAT-ERROR-TO-MERROR form applies the MERROR function to them.
186 ;; The obvious way to make RAT-ERROR work is to raise a condition. On the lisp
187 ;; implementations we support other than CMUCL, this runs perfectly
188 ;; fast. Unfortunately, on CMUCL there's a performance bug which turns out to be
189 ;; very costly when you raise lots of the condition. There are lots and lots of
190 ;; rat-error calls running the test suite (10s of thousands), and this turns out
191 ;; to be hilariously slow.
193 ;; Thus we do the (catch .... (throw .... )) thing instead. Other error handling
194 ;; should be able to use conditions with impunity: the only reason that the
195 ;; performance was so critical with rat error is the sheer number of them that
196 ;; are thrown.
197 (defun rat-error (&rest error-args)
198 (throw 'rat-err error-args))
200 ;; IGNORE-RAT-ERR
202 ;; Evaluate BODY with the RAT-ERR tag set. If something in BODY throws to
203 ;; RAT-ERR (happens upon calling the RAT-ERROR function), this form evaluates to
204 ;; NIL.
205 (defmacro ignore-rat-err (&body body)
206 (let ((result (gensym)) (error-p (gensym)))
207 `(let ((,result) (,error-p t))
208 (catch 'rat-err
209 (setf ,result (progn ,@body))
210 (setf ,error-p nil))
211 (unless ,error-p ,result))))
213 (defmacro rat-error-to-merror (&body body)
214 (let ((result (gensym)) (error-args (gensym)) (error-p (gensym)))
215 `(let ((,result) (,error-p t))
216 (let ((,error-args
217 (catch 'rat-err
218 (setf ,result (progn ,@body))
219 (setf ,error-p nil))))
220 (when ,error-p
221 (apply #'merror ,error-args)))
222 ,result)))
224 ;;; The user-error function is called on "strings" and expressions.
225 ;;; Cons up a format string so that $ERROR can be bound.
226 ;;; This might also be done at code translation time.
227 ;;; This is a bit crude.
229 (defmfun fstringc (l)
230 (do ((sl nil) (s) (sb)
231 (se nil))
232 ((null l)
233 (setq sl (maknam sl))
234 (cons sl (nreverse se)))
235 (setq s (pop l))
236 (cond ((stringp s)
237 (setq sb (mapcan #'(lambda (x)
238 (if (char= x #\~)
239 (list x x)
240 (list x)))
241 (coerce s 'list))))
243 (push s se)
244 (setq sb (list #\~ #\M))))
245 (setq sl (nconc sl sb (if (null l) nil (list #\space))))))