X-Git-Url: http://repo.macrolet.net/gitweb/?a=blobdiff_plain;f=src%2Fcode%2Flate-format.lisp;h=bd61b58af968212f8c00da0b43cef54935fb3c36;hb=602c9b1f15e2d96e4b79a3341a734b5eb8e02093;hp=d359fa30fcef2bde4782e345ecd024a96faa238f;hpb=a530bbe337109d898d5b4a001fc8f1afa3b5dc39;p=sbcl.git diff --git a/src/code/late-format.lisp b/src/code/late-format.lisp index d359fa3..bd61b58 100644 --- a/src/code/late-format.lisp +++ b/src/code/late-format.lisp @@ -8,13 +8,10 @@ ;;;; files for more information. (in-package "SB!FORMAT") - -(file-comment - "$Header$") (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*) @@ -30,15 +27,15 @@ ~?~@[~% ~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))) (def!struct format-directive - (string (required-argument) :type simple-string) - (start (required-argument) :type (and unsigned-byte fixnum)) - (end (required-argument) :type (and unsigned-byte fixnum)) - (character (required-argument) :type base-char) + (string (missing-arg) :type simple-string) + (start (missing-arg) :type (and unsigned-byte fixnum)) + (end (missing-arg) :type (and unsigned-byte fixnum)) + (character (missing-arg) :type base-char) (colonp nil :type (member t nil)) (atsignp nil :type (member t nil)) (params nil :type list)) @@ -76,10 +73,17 @@ :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) @@ -90,7 +94,9 @@ (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) @@ -100,6 +106,7 @@ (t (return)))) ((char= char #\#) + (check-ordering) (push (cons posn :remaining) params) (incf posn) (case (get-char) @@ -109,12 +116,14 @@ (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 @@ -132,6 +141,7 @@ (setf atsignp t))) (t (when (char= (schar string (1- posn)) #\,) + (check-ordering) (push (cons (1- posn) nil) params)) (return)))) (incf posn)) @@ -212,10 +222,12 @@ (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)))) @@ -266,8 +278,8 @@ (error 'format-error :complaint - "too many parameters, expected no more than ~D" - :arguments (list ,(length specs)) + "too many parameters, expected no more than ~W" + :args (list ,(length specs)) :offset (caar ,params))) ,,@body))) `(progn @@ -289,14 +301,10 @@ `(progn (defun ,defun-name (,directive ,directives) ,@(if lambda-list - `((let ,(mapcar #'(lambda (var) - `(,var - (,(intern (concatenate - 'string - "FORMAT-DIRECTIVE-" - (symbol-name var)) - (symbol-package 'foo)) - ,directive))) + `((let ,(mapcar (lambda (var) + `(,var + (,(symbolicate "FORMAT-DIRECTIVE-" var) + ,directive))) (butlast lambda-list)) ,@body)) `((declare (ignore ,directive ,directives)) @@ -319,7 +327,7 @@ (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) @@ -467,6 +475,7 @@ :complaint "no previous argument")) (caar *simple-args*)) (t + (/show0 "THROWing NEED-ORIG-ARGS from tilde-P") (throw 'need-orig-args nil))))) (if atsignp `(write-string (if (eql ,arg 1) "y" "ies") stream) @@ -618,17 +627,19 @@ "both colon and atsign modifiers used simultaneously") (expand-bind-defaults ((posn 0)) params (unless *orig-args-available* + (/show0 "THROWing NEED-ORIG-ARGS from tilde-@*") (throw 'need-orig-args nil)) `(if (<= 0 ,posn (length orig-args)) (setf args (nthcdr ,posn orig-args)) (error 'format-error - :complaint "Index ~D out of bounds. Should have been ~ - between 0 and ~D." - :arguments (list ,posn (length orig-args)) + :complaint "Index ~W out of bounds. Should have been ~ + between 0 and ~W." + :args (list ,posn (length orig-args)) :offset ,(1- end))))) (if colonp (expand-bind-defaults ((n 1)) params (unless *orig-args-available* + (/show0 "THROWing NEED-ORIG-ARGS from tilde-:*") (throw 'need-orig-args nil)) `(do ((cur-posn 0 (1+ cur-posn)) (arg-ptr orig-args (cdr arg-ptr))) @@ -638,10 +649,9 @@ (setf args (nthcdr new-posn orig-args)) (error 'format-error :complaint - "Index ~D is out of bounds; should have been ~ - between 0 and ~D." - :arguments - (list new-posn (length orig-args)) + "Index ~W is out of bounds; should have been ~ + between 0 and ~W." + :args (list new-posn (length orig-args)) :offset ,(1- end))))))) (if params (expand-bind-defaults ((n 1)) params @@ -659,14 +669,14 @@ (expand-bind-defaults () params `(handler-bind ((format-error - #'(lambda (condition) - (error 'format-error - :complaint - "~A~%while processing indirect format string:" - :arguments (list condition) - :print-banner nil - :control-string ,string - :offset ,(1- end))))) + (lambda (condition) + (error 'format-error + :complaint + "~A~%while processing indirect format string:" + :args (list condition) + :print-banner nil + :control-string ,string + :offset ,(1- end))))) ,(if atsignp (if *orig-args-available* `(setf args (%format stream ,(expand-next-arg) orig-args args)) @@ -871,14 +881,14 @@ (if *orig-args-available* `((handler-bind ((format-error - #'(lambda (condition) - (error 'format-error - :complaint - "~A~%while processing indirect format string:" - :arguments (list condition) - :print-banner nil - :control-string ,string - :offset ,(1- end))))) + (lambda (condition) + (error 'format-error + :complaint + "~A~%while processing indirect format string:" + :args (list condition) + :print-banner nil + :control-string ,string + :offset ,(1- end))))) (setf args (%format stream inside-string orig-args args)))) (throw 'need-orig-args nil)) @@ -940,6 +950,23 @@ ;;;; 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) @@ -950,8 +977,15 @@ 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 #\> () @@ -974,7 +1008,7 @@ :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) @@ -1083,7 +1117,8 @@ (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) @@ -1108,10 +1143,10 @@ (line-len '(or (sb!impl::line-length stream) 72))) (format-directive-params first-semi) `(setf extra-space ,extra line-len ,line-len)))) - ,@(mapcar #'(lambda (segment) - `(push (with-output-to-string (stream) - ,@(expand-directive-list segment)) - segments)) + ,@(mapcar (lambda (segment) + `(push (with-output-to-string (stream) + ,@(expand-directive-list segment)) + segments)) segments)) (format-justification stream ,@(if newline-segment-p @@ -1123,7 +1158,7 @@ ;;;; format directive and support function for user-defined method (def-format-directive #\/ (string start end colonp atsignp params) - (let ((symbol (extract-user-function-name string start end))) + (let ((symbol (extract-user-fun-name string start end))) (collect ((param-names) (bindings)) (dolist (param-and-offset params) (let ((param (cdr param-and-offset))) @@ -1138,7 +1173,7 @@ (,symbol stream ,(expand-next-arg) ,colonp ,atsignp ,@(param-names)))))) -(defun extract-user-function-name (string start end) +(defun extract-user-fun-name (string start end) (let ((slash (position #\/ string :start start :end (1- end) :from-end t))) (unless slash @@ -1150,8 +1185,8 @@ ;; 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))) @@ -1160,8 +1195,124 @@ ;; 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)))))))