\f
(define-condition format-error (error)
((complaint :reader format-error-complaint :initarg :complaint)
- (arguments :reader format-error-arguments :initarg :arguments :initform nil)
+ (args :reader format-error-args :initarg :args :initform nil)
(control-string :reader format-error-control-string
:initarg :control-string
:initform *default-format-error-control-string*)
~?~@[~% ~A~% ~V@T^~]"
(format-error-print-banner condition)
(format-error-complaint condition)
- (format-error-arguments condition)
+ (format-error-args condition)
(format-error-control-string condition)
(format-error-offset condition)))
\f
:complaint "String ended before directive was found."
:control-string string
:offset start)
- (schar string posn))))
+ (schar string posn)))
+ (check-ordering ()
+ (when (or colonp atsignp)
+ (error 'format-error
+ :complaint "parameters found after #\\: or #\\@ modifier"
+ :control-string string
+ :offset posn))))
(loop
(let ((char (get-char)))
(cond ((or (char<= #\0 char #\9) (char= char #\+) (char= char #\-))
+ (check-ordering)
(multiple-value-bind (param new-posn)
(parse-integer string :start posn :junk-allowed t)
(push (cons posn param) params)
(decf posn))
(t
(return)))))
- ((or (char= char #\v) (char= char #\V))
+ ((or (char= char #\v)
+ (char= char #\V))
+ (check-ordering)
(push (cons posn :arg) params)
(incf posn)
(case (get-char)
(t
(return))))
((char= char #\#)
+ (check-ordering)
(push (cons posn :remaining) params)
(incf posn)
(case (get-char)
(t
(return))))
((char= char #\')
+ (check-ordering)
(incf posn)
(push (cons posn (get-char)) params)
(incf posn)
(unless (char= (get-char) #\,)
(decf posn)))
((char= char #\,)
+ (check-ordering)
(push (cons posn nil) params))
((char= char #\:)
(if colonp
(setf atsignp t)))
(t
(when (char= (schar string (1- posn)) #\,)
+ (check-ordering)
(push (cons (1- posn) nil) params))
(return))))
(incf posn))
(char-code (format-directive-character directive))))
(*default-format-error-offset*
(1- (format-directive-end directive))))
+ (declare (type (or null function) expander))
(if expander
(funcall expander directive more-directives)
(error 'format-error
- :complaint "unknown directive"))))
+ :complaint "unknown directive ~@[(character: ~A)~]"
+ :args (list (char-name (format-directive-character directive)))))))
(simple-string
(values `(write-string ,directive stream)
more-directives))))
'format-error
:complaint
"too many parameters, expected no more than ~W"
- :arguments (list ,(length specs))
+ :args (list ,(length specs))
:offset (caar ,params)))
,,@body)))
`(progn
(values (progn ,@body-without-decls)
,directives))))
-(eval-when (:compile-toplevel :load-toplevel :execute)
+(eval-when (#-sb-xc :compile-toplevel :load-toplevel :execute)
(defun %set-format-directive-expander (char fn)
(setf (aref *format-directive-expanders* (char-code (char-upcase char))) fn)
(error 'format-error
:complaint "Index ~W out of bounds. Should have been ~
between 0 and ~W."
- :arguments (list ,posn (length orig-args))
+ :args (list ,posn (length orig-args))
:offset ,(1- end)))))
(if colonp
(expand-bind-defaults ((n 1)) params
:complaint
"Index ~W is out of bounds; should have been ~
between 0 and ~W."
- :arguments
- (list new-posn (length orig-args))
+ :args (list new-posn (length orig-args))
:offset ,(1- end)))))))
(if params
(expand-bind-defaults ((n 1)) params
(error 'format-error
:complaint
"~A~%while processing indirect format string:"
- :arguments (list condition)
+ :args (list condition)
:print-banner nil
:control-string ,string
:offset ,(1- end)))))
(error 'format-error
:complaint
"~A~%while processing indirect format string:"
- :arguments (list condition)
+ :args (list condition)
:print-banner nil
:control-string ,string
:offset ,(1- end)))))
\f
;;;; format directives and support functions for justification
+(defparameter *illegal-inside-justification*
+ (mapcar (lambda (x) (parse-directive x 0))
+ '("~W" "~:W" "~@W" "~:@W"
+ "~_" "~:_" "~@_" "~:@_"
+ "~:>" "~:@>"
+ "~I" "~:I" "~@I" "~:@I"
+ "~:T" "~:@T")))
+
+(defun illegal-inside-justification-p (directive)
+ (member directive *illegal-inside-justification*
+ :test (lambda (x y)
+ (and (format-directive-p x)
+ (format-directive-p y)
+ (eql (format-directive-character x) (format-directive-character y))
+ (eql (format-directive-colonp x) (format-directive-colonp y))
+ (eql (format-directive-atsignp x) (format-directive-atsignp y))))))
+
(def-complex-format-directive #\< (colonp atsignp params string end directives)
(multiple-value-bind (segments first-semi close remaining)
(parse-format-justification directives)
close params string end)
(expand-format-logical-block prefix per-line-p insides
suffix atsignp))
- (expand-format-justification segments colonp atsignp
- first-semi params))
+ (let ((count (reduce #'+ (mapcar (lambda (x) (count-if #'illegal-inside-justification-p x)) segments))))
+ (when (> count 0)
+ ;; ANSI specifies that "an error is signalled" in this
+ ;; situation.
+ (error 'format-error
+ :complaint "~D illegal directive~:P found inside justification block"
+ :args (list count)))
+ (expand-format-justification segments colonp atsignp
+ first-semi params)))
remaining)))
(def-complex-format-directive #\> ()
:complaint
"cannot include format directives inside the ~
~:[suffix~;prefix~] segment of ~~<...~~:>"
- :arguments (list prefix-p)
+ :args (list prefix-p)
:offset (1- (format-directive-end directive)))
(apply #'concatenate 'string list)))))
(case (length segments)
(block nil
,@(let ((*expander-next-arg-macro* 'expander-pprint-next-arg)
(*only-simple-args* nil)
- (*orig-args-available* t))
+ (*orig-args-available*
+ (if atsignp *orig-args-available* t)))
(expand-directive-list insides)))))))
(defun expand-format-justification (segments colonp atsignp first-semi params)
;; subseq expansion.
(subseq foo (1+ slash) (1- end)))))
(first-colon (position #\: name))
- (last-colon (if first-colon (position #\: name :from-end t)))
- (package-name (if last-colon
+ (second-colon (if first-colon (position #\: name :start (1+ first-colon))))
+ (package-name (if first-colon
(subseq name 0 first-colon)
"COMMON-LISP-USER"))
(package (find-package package-name)))
;; FIND-UNDELETED-PACKAGE-OR-LOSE?
(error 'format-error
:complaint "no package named ~S"
- :arguments (list package-name)))
- (intern (if first-colon
- (subseq name (1+ first-colon))
- name)
+ :args (list package-name)))
+ (intern (cond
+ ((and second-colon (= second-colon (1+ first-colon)))
+ (subseq name (1+ second-colon)))
+ (first-colon
+ (subseq name (1+ first-colon)))
+ (t name))
package))))
+
+;;; compile-time checking for argument mismatch. This code is
+;;; inspired by that of Gerd Moellmann, and comes decorated with
+;;; FIXMEs:
+(defun %compiler-walk-format-string (string args)
+ (declare (type simple-string string))
+ (let ((*default-format-error-control-string* string))
+ (macrolet ((incf-both (&optional (increment 1))
+ `(progn
+ (incf min ,increment)
+ (incf max ,increment)))
+ (walk-complex-directive (function)
+ `(multiple-value-bind (min-inc max-inc remaining)
+ (,function directive directives args)
+ (incf min min-inc)
+ (incf max max-inc)
+ (setq directives remaining))))
+ ;; FIXME: these functions take a list of arguments as well as
+ ;; the directive stream. This is to enable possibly some
+ ;; limited type checking on FORMAT's arguments, as well as
+ ;; simple argument count mismatch checking: when the minimum and
+ ;; maximum argument counts are the same at a given point, we
+ ;; know which argument is going to be used for a given
+ ;; directive, and some (annotated below) require arguments of
+ ;; particular types.
+ (labels
+ ((walk-justification (justification directives args)
+ (declare (ignore args))
+ (let ((*default-format-error-offset*
+ (1- (format-directive-end justification))))
+ (multiple-value-bind (segments first-semi close remaining)
+ (parse-format-justification directives)
+ (declare (ignore segments first-semi))
+ (cond
+ ((not (format-directive-colonp close))
+ (values 0 0 directives))
+ ((format-directive-atsignp justification)
+ (values 0 sb!xc:call-arguments-limit directives))
+ ;; FIXME: here we could assert that the
+ ;; corresponding argument was a list.
+ (t (values 1 1 remaining))))))
+ (walk-conditional (conditional directives args)
+ (let ((*default-format-error-offset*
+ (1- (format-directive-end conditional))))
+ (multiple-value-bind (sublists last-semi-with-colon-p remaining)
+ (parse-conditional-directive directives)
+ (declare (ignore last-semi-with-colon-p))
+ (let ((sub-max
+ (loop for s in sublists
+ maximize (nth-value
+ 1 (walk-directive-list s args)))))
+ (cond
+ ((format-directive-atsignp conditional)
+ (values 1 (max 1 sub-max) remaining))
+ ((loop for p in (format-directive-params conditional)
+ thereis (or (integerp (cdr p))
+ (memq (cdr p) '(:remaining :arg))))
+ (values 0 sub-max remaining))
+ ;; FIXME: if not COLONP, then the next argument
+ ;; must be a number.
+ (t (values 1 (1+ sub-max) remaining)))))))
+ (walk-iteration (iteration directives args)
+ (declare (ignore args))
+ (let ((*default-format-error-offset*
+ (1- (format-directive-end iteration))))
+ (let* ((close (find-directive directives #\} nil))
+ (posn (position close directives))
+ (remaining (nthcdr (1+ posn) directives)))
+ ;; FIXME: if POSN is zero, the next argument must be
+ ;; a format control (either a function or a string).
+ (if (format-directive-atsignp iteration)
+ (values (if (zerop posn) 1 0)
+ sb!xc:call-arguments-limit
+ remaining)
+ ;; FIXME: the argument corresponding to this
+ ;; directive must be a list.
+ (let ((nreq (if (zerop posn) 2 1)))
+ (values nreq nreq remaining))))))
+ (walk-directive-list (directives args)
+ (let ((min 0) (max 0))
+ (loop
+ (let ((directive (pop directives)))
+ (when (null directive)
+ (return (values min (min max sb!xc:call-arguments-limit))))
+ (when (format-directive-p directive)
+ (incf-both (count :arg (format-directive-params directive)
+ :key #'cdr))
+ (let ((c (format-directive-character directive)))
+ (cond
+ ((find c "ABCDEFGORSWX$/")
+ (incf-both))
+ ((char= c #\P)
+ (unless (format-directive-colonp directive)
+ (incf-both)))
+ ((or (find c "IT%&|_();>") (char= c #\Newline)))
+ ((char= c #\<)
+ (walk-complex-directive walk-justification))
+ ((char= c #\[)
+ (walk-complex-directive walk-conditional))
+ ((char= c #\{)
+ (walk-complex-directive walk-iteration))
+ ((char= c #\?)
+ ;; FIXME: the argument corresponding to this
+ ;; directive must be a format control.
+ (cond
+ ((format-directive-atsignp directive)
+ (incf min)
+ (setq max sb!xc:call-arguments-limit))
+ (t (incf-both 2))))
+ (t (throw 'give-up-format-string-walk nil))))))))))
+ (catch 'give-up-format-string-walk
+ (let ((directives (tokenize-control-string string)))
+ (walk-directive-list directives args)))))))