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

;;;FILTER-FILE
;;;
;;;Filter selected characters in a file to a new output file.  Pass
;;;FILTER-FILE a function to compare to each character; characters that
;;;pass the "test" are written to the output.  The default function is
;;;STANDARD-CHAR-P which has the effect of keeping only printable
;;;characters.

(defvar *last-filtered-chars*)

(defun filter-file (file &key outfile (test 'standard-char-p))
  "Filter characters from FILE that pass the TEST predicate into
output file OUTFILE."
  (declare (values outfile *last-filtered-chars*))
  (assert (functionp test) (test) "~s is not suitable as a function" test)
  (setq file (merge-pathnames file
			      (make-pathname :host si:associated-machine
					     :type "LISP"
					     :version :HIGHEST)))
  (check-type (pathname-name file) string)
  (check-type (pathname-type file) string)
  ;;
  ;;Should use MERGE-PATHNAMES here, but it's broken - it doesn't preserve
  ;;NIL components from both pathnames!
  ;;
  (let ((outfile-defaults (make-pathname :host (or (pathname-host file) si:associated-machine)
					 :type "FILTRD" :version :HIGHEST)))
    (setq outfile
	  (merge-pathnames 
	    (if outfile (merge-pathnames outfile outfile-defaults) outfile-defaults)
	    file)))
  (check-type (pathname-name outfile) string)
  (check-type (pathname-type outfile) string)
  ;;
  (format t "~&Filter input from ~a, output to ~a, testing for ~s"
	  file outfile test)
  ;;
  (setq *last-filtered-chars* nil)
  (with-open-file (in file)
    (with-open-file (out outfile :direction :output)
      (do ((ch (send in :tyi) (send in :tyi)))
	  ((null ch) (values outfile *last-filtered-chars*))
	(setq ch (character ch))
	(if (funcall test ch)
	    (send out :tyo ch)
	  (pushnew ch *last-filtered-chars* :test #'char=))))))
