;;; -*- Mode:LISP; Package:COMPILER; Base:10; Readtable:CL -*-

;;;The style police:

(defun check-byte-spec (form &aux flag)
  (let* ((fcn (car form))
	 (argpos (or (get fcn 'check-byte-spec-arg) 0))
	 (barg (nth (1+ argpos) form))
	 (definitely-not-portable t))
    (when
      (setq flag
	    ;;Return to FLAG the warning, if any
	    (macrolet ((middle (prefix)
			       (format nil "~A ~~S as a BYTE specifier" prefix))
		       (at-end (prefix)
			       (format nil "~A BYTE specifier \"~~S\"" prefix))
		       (simple (prefix)
			       (format nil "~A BYTE specifier~~*" prefix)))
	      (typecase barg
		(keyword (middle "the keyword"))	;Keywords will never be right
		((member t nil)				; T, NIL other selv-evaling vars
		 (if barg (simple "T as a")
		   (simple "a null")))
		(symbol nil)				;Variables? We can't be sure.
		(list					;"Random" function calls are not portable.
		 (setq barg (car barg))
		 (cond
		   ((eq barg 'byte) nil)	;The ideal: calls to BYTE
		   (t (setq definitely-not-portable nil)	;Can't be sure
		      (middle "a call to"))))
		(number (middle "the numeric constant"))	;Numbers
		(t (at-end "an invalid")))))
      (warn 'invalid-byte-spec :not-portable
	"~S called with ~A; this ~:[may not be~;is not~] portable"
	fcn
	(format nil flag barg)
	definitely-not-portable))))

;;;How to turn on checker for one function:

(defmacro make-byte-spec-checker (sym &optional (argpos 0))
  (check-type sym symbol)
  (check-type argpos (integer 0))
  `(eval-when (eval compile load)
     (progn
       (unless (fboundp ',sym)
	 (warn 'function-not-valid :implausible
	   "Establishing byte-spec checker for undefined function ~S" ',sym))
       (putprop ',sym 'check-byte-spec 'compiler:style-checker)
       (putprop ',sym ,argpos 'check-byte-spec-arg))))

;;;Now turn on checkers selectively:

(check-byte-specs ldb)
(check-byte-specs dpb 1)

;;;Test code:

(check-byte-specs fsksks)

(defun tester-ok (ppss wrd)
  (ldb ppss wrd)
  (ldb (byte 1 0) #o77)
  (dpb 0. ppss wrd)
  (dpb #o77 (byte 1 0) wrd))

(defstruct (bword (:type :array-leader))
  ((word)
   (byte-1 (byte 8 0.))
   (byte-2 (byte 8 8.))
   (byte-3 (byte 8 16.))
   (byte-4 (byte 8 24.))))

(untrace)

(let ((*trace-output* #'ignore))
  (untrace)
  (trace byte-expand check-byte-spec
	 si:defstruct-parse-items si:defstruct-parse-one-field
	 si:defstruct-define-ref-macros si:defstruct-expand-ref-macro
	 compiler-macroexpand-1 macroexpand-1 invoke-style-checker compiler-optimize))

(defun tester-ng (ppss wrd)
  (compiler-let ((compiler:*just-once-for-style-checkers-per-inner-form* nil))
    (ldb #(1 2 3) wrd)
    (ldb nil wrd)
    (ldb t wrd)
    (ldb :keyword wrd)
    (ldb (* ppss 3) wrd)
    (dpb #b1010 261. wrd)
    (dpb #o17 (max 3 5) 77.)
    (ldb (times ppss 3) wrd)))

<< While compiling TESTER-NG >>
 LDB called with an invalid BYTE specifier "#(1 2 3)"; this is not portable
 LDB called with a null BYTE specifier; this is not portable
 LDB called with T as a BYTE specifier; this is not portable
 LDB called with the keyword :KEYWORD as a BYTE specifier; this is not portable
 LDB called with a call to * as a BYTE specifier; this may not be portable
 DPB called with the numeric constant 261 as a BYTE specifier; this is not portable
 DPB called with a call to MAX as a BYTE specifier; this may not be portable
 LDB called with a call to TIMES as a BYTE specifier; this may not be portable

;;;Need a way to handle clause-matcher like DISPATCH:  
;  (dispatch ppss wrd
;    (0 (print 'ok))
;    (((byte 1 0)) (print 0))
;    (((byte 1 1)) (print 1))
;    (((byte 1 2)) (print 2))
;    (((byte 1 3)) (print 3))
;    (((byte 1 4)) (print 4))
;    (((byte 1 5)) (print 5))
;    (((byte 1 6)) (print 6))
;    (((byte 1 7)) (print 7))
;    (otherwise (print '???))))

