X-Git-Url: http://git.kpe.io/?a=blobdiff_plain;f=genutils.lisp;h=cee438bcbf53ff4f5235015a45455aedf8efbc5d;hb=6007424292e8d78977bc90bcc29a20d4451cfa41;hp=e35f23d51c9f73c7447ba47440a9319e1942c191;hpb=5e5cc3c20a925d8af5de153a118fdaf0792dd7e2;p=kmrcl.git diff --git a/genutils.lisp b/genutils.lisp index e35f23d..cee438b 100644 --- a/genutils.lisp +++ b/genutils.lisp @@ -2,35 +2,31 @@ ;;;; ************************************************************************* ;;;; FILE IDENTIFICATION ;;;; -;;;; Name: genutils.lisp -;;;; Purpose: Main general utility functions for GENUTILS package +;;;; Name: gentils.lisp +;;;; Purpose: Main general utility functions for KMRCL package ;;;; Programmer: Kevin M. Rosenberg ;;;; Date Started: Apr 2000 ;;;; -;;;; $Id: genutils.lisp,v 1.1 2002/10/06 13:21:47 kevin Exp $ +;;;; $Id: genutils.lisp,v 1.15 2003/02/07 14:21:55 kevin Exp $ ;;;; -;;;; This file, part of Genutils, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; This file, part of KMRCL, is Copyright (c) 2002 by Kevin M. Rosenberg ;;;; -;;;; Genutils users are granted the rights to distribute and use this software -;;;; as governed by the terms of the GNU General Public License. +;;;; KMRCL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. ;;;; ************************************************************************* -(in-package :genutils) +(in-package :kmrcl) +(declaim (optimize (speed 3) (safety 1) (compilation-speed 0) (debug 3))) -(declaim (optimize (speed 3) (safety 1))) - -(defmacro bind-when ((bind-var boundForm) &body body) - `(let ((,bind-var ,boundForm)) - (declare (ignore-if-unused ,bind-var)) - (when ,bind-var - ,@body))) +(defmacro let-when ((var test-form) &body body) + `(let ((,var ,test-form)) + (when ,var ,@body))) -(defmacro bind-if ((bind-var boundForm) yup &optional nope) - `(let ((,bind-var ,boundForm)) - (if ,bind-var - ,yup - ,nope))) +(defmacro let-if ((var test-form) if-true &optional if-false) + `(let ((,var ,test-form)) + (if ,var ,if-true ,if-false))) ;; Anaphoric macros @@ -160,6 +156,12 @@ (if val (push val acc)))) (nreverse acc))) +(defun appendnew (l1 l2) + "Append two lists, filtering out elem from second list that are already in first list" + (dolist (elem l2) + (unless (find elem l1) + (setq l1 (append l1 (list elem))))) + l1) ;; Functions @@ -216,7 +218,23 @@ ((> ,var ,gstop)) ,@body))) - +(defmacro with-each-stream-line ((var stream) &body body) + (let ((eof (gensym)) + (eof-value (gensym)) + (strm (gensym))) + `(let ((,strm ,stream) + (,eof ',eof-value)) + (do ((,var (read-line ,strm nil ,eof) (read-line ,strm nil ,eof))) + ((eql ,var ,eof)) + ,@body)))) + +(defmacro with-each-file-line ((var file) &body body) + (let ((stream (gensym))) + `(with-open-file (,stream ,file :direction :input) + (with-each-stream-line (,var ,stream) + ,@body)))) + + ;;; Keyword functions (defun remove-keyword (key arglist) @@ -252,7 +270,7 @@ (defun mapcar-append-string-nontailrec (func v) -"Concatenate results of mapcar lambda calls" + "Concatenate results of mapcar lambda calls" (aif (car v) (concatenate 'string (funcall func it) (mapcar-append-string-nontailrec func (cdr v))) @@ -260,7 +278,7 @@ (defun mapcar-append-string (func v &optional (accum "")) -"Concatenate results of mapcar lambda calls" + "Concatenate results of mapcar lambda calls" (aif (car v) (mapcar-append-string func @@ -268,9 +286,8 @@ (concatenate 'string accum (funcall func it))) accum)) - (defun mapcar2-append-string-nontailrec (func la lb) -"Concatenate results of mapcar lambda call's over two lists" + "Concatenate results of mapcar lambda call's over two lists" (let ((a (car la)) (b (car lb))) (if (and a b) @@ -279,7 +296,7 @@ ""))) (defun mapcar2-append-string (func la lb &optional (accum "")) -"Concatenate results of mapcar lambda call's over two lists" + "Concatenate results of mapcar lambda call's over two lists" (let ((a (car la)) (b (car lb))) (if (and a b) @@ -290,173 +307,17 @@ (concatenate 'string accum (funcall func a b))) accum))) -;;; Strings - -(defmacro string-append (outputstr &rest args) - `(setq ,outputstr (concatenate 'string ,outputstr ,@args))) - -(defmacro string-field-append (outputstr &rest args) - `(setq ,outputstr (concatenate 'string ,outputstr ,@args))) - -(defun list-to-string (lst) - "Converts a list to a string, doesn't include any delimiters between elements" - (format nil "~{~A~}" lst)) - -(defun count-string-words (str) - (declare (simple-string str) - (optimize (speed 3) (safety 0))) - (let ((n-words 0) - (in-word nil)) - (declare (fixnum n-words)) - (dotimes (i (length str)) - (let ((ch (char str i))) - (declare (character ch)) - (if (alphanumericp ch) - (unless in-word - (incf n-words) - (setq in-word t)) - (setq in-word nil)))) - n-words)) - -#+excl -(defun delimited-string-to-list (string &optional (separator #\space)) - (excl:delimited-string-to-list string separator)) - -#-excl -(defun delimited-string-to-list (sequence &optional (separator #\space)) -"Split a string by a delimitor" - (loop - with start = 0 - for end = (position separator sequence :start start) - collect (subseq sequence start end) - until (null end) - do - (setf start (1+ end)))) - -#+excl -(defun list-to-delimited-string (list &optional (separator #\space)) - (excl:list-to-delimited-string list separator)) - -#-excl -(defun list-to-delimited-string (list &optional (separator #\space)) - (let ((output (when list (format nil "~A" (car list))))) - (dolist (obj (rest list)) - (setq output (concatenate 'string output - (format nil "~A" separator) - (format nil "~A" obj)))) - output)) - -(defun string-invert (str) - "Invert case of a string" - (declare (optimize (speed 3) (compilation-speed 0) (debug 0) (safety 0)) - (simple-string str)) - (let ((up nil) (down nil)) - (block skip - (loop for char of-type character across str do - (cond ((upper-case-p char) (if down (return-from skip str) (setf up t))) - ((lower-case-p char) (if up (return-from skip str) (setf down t))))) - (if up (string-downcase str) (string-upcase str))))) - -(defun add-sql-quotes (s) - (substitute-string-for-char s #\' "''")) - -(defun escape-backslashes (s) - (substitute-string-for-char s #\\ "\\\\")) - -(defun substitute-string-for-char (procstr match-char subst-str) -"Substitutes a string for a single matching character of a string" - (let ((pos (position match-char procstr))) - (if pos - (concatenate 'string - (subseq procstr 0 pos) subst-str - (substitute-string-for-char (subseq procstr (1+ pos)) match-char subst-str)) - procstr))) - -(defun string-substitute (string substring replacement-string) - "String substitute by Larry Hunter. Obtained from Google" - (let ((substring-length (length substring)) - (last-end 0) - (new-string "")) - (do ((next-start - (search substring string) - (search substring string :start2 last-end))) - ((null next-start) - (concatenate 'string new-string (subseq string last-end))) - (setq new-string - (concatenate 'string - new-string - (subseq string last-end next-start) - replacement-string)) - (setq last-end (+ next-start substring-length))))) - - -(defun string-trim-last-character (s) -"Return the string less the last character" - (subseq s 0 (1- (length s)))) - -(defun string-hash (str &optional (bitmask 65535)) - (let ((hash 0)) - (declare (fixnum hash) - (simple-string str)) - (dotimes (i (length str)) - (declare (fixnum i)) - (setq hash (+ hash (char-code (char str i))))) - (logand hash bitmask))) - -(defun string-not-null? (str) - (and str (not (zerop (length str))))) - -(defun whitespace? (c) - (declare (character c)) - (declare (optimize (speed 3) (safety 0))) - (or (char= c #\Space) (char= c #\Tab) (char= c #\Return) (char= c #\Linefeed))) - -(defun not-whitespace? (c) - (not (whitespace? c))) - -(defun string-ws? (str) - "Return t if string is all whitespace" - (when (stringp str) - (null (find-if #'not-whitespace? str)))) - ;;; Output -(unless (boundp '+indent-vector+) - (defconstant +indent-vector+ - (make-array 15 :fill-pointer nil :adjustable nil - :initial-contents - '("" - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " ")))) - -(defmacro indent-spaces (n &optional stream) +(defun indent-spaces (n &optional (stream *standard-output*)) "Indent n*2 spaces to output stream" - (let ((st (gensym))) - `(let ((,st ,stream)) - (unless ,st - (setq ,st *standard-output*)) - (when (plusp ,n) - (if (< ,n 10) - (princ (aref +indent-vector+ ,n) ,st) - (dotimes (i ,n) - (declare (fixnum i)) - (format ,st " "))))))) - + (when (numberp n) + (let ((fmt (format nil "~~~DT" (+ n n)))) + (format stream fmt)))) + (defun print-list (l &optional (output *standard-output*)) -"Print a list to a stream" + "Print a list to a stream" (if (consp l) (progn (mapcar (lambda (x) (princ x output) (princ #\newline output)) l) @@ -464,21 +325,12 @@ nil)) (defun print-rows (rows &optional (ostrm *standard-output*)) -"Print a list of list rows to a stream" + "Print a list of list rows to a stream" (dolist (r rows) (mapcar (lambda (a) (princ a ostrm) (princ #\space ostrm)) r) (terpri ostrm))) -;;; Symbol functions - -(defmacro concat-symbol (&rest args) - `(intern (concatenate 'string ,@args))) - -(defmacro concat-symbol-pkg (pkg &rest args) - `(intern (concatenate 'string ,@args) ,pkg)) - - ;;; IO @@ -526,9 +378,9 @@ (defun file-subst (old new file1 file2) (with-open-file (in file1 :direction :input) - (with-open-file (out file2 :direction :output - :if-exists :supersede) - (stream-subst old new in out)))) + (with-open-file (out file2 :direction :output + :if-exists :supersede) + (stream-subst old new in out)))) (defun stream-subst (old new in out) (declare (string old new)) @@ -601,122 +453,32 @@ "Opens a reads a file. Returns the contents as a single string" (when (probe-file file) (with-open-file (in file :direction :input) - (do ((line (read-line in nil 'eof) - (read-line in nil 'eof))) - ((eql line 'eof)) - (format strm "~A~%" line))))) + (let ((eof (gensym))) + (do ((line (read-line in nil eof) + (read-line in nil eof))) + ((eq line eof)) + (format strm "~A~%" line)))))) (defun read-file-to-string (file) "Opens a reads a file. Returns the contents as a single string" (with-output-to-string (out) (with-open-file (in file :direction :input) - (do ((line (read-line in nil 'eof) - (read-line in nil 'eof))) - ((eql line 'eof)) - (format out "~A~%" line))))) + (let ((eof (gensym))) + (do ((line (read-line in nil eof) + (read-line in nil eof))) + ((eq line eof)) + (format out "~A~%" line)))))) (defun read-file-to-strings (file) "Opens a reads a file. Returns the contents as a list of strings" (let ((lines '())) (with-open-file (in file :direction :input) - (do ((line (read-line in nil 'eof) - (read-line in nil 'eof))) - ((eql line 'eof)) - (push line lines))) - (nreverse lines))) - - -;; Generalized equal system - -(defun generalized-equal (obj1 obj2) - (if (not (equal (type-of obj1) (type-of obj2))) - (progn - (terpri) - (describe obj1) - (describe obj2) - nil) - (typecase obj1 - (double-float - (let ((diff (abs (/ (- obj1 obj2) obj1)))) - (if (> diff (* 10 double-float-epsilon)) - nil - t))) - (complex - (and (generalized-equal (realpart obj1) (realpart obj2)) - (generalized-equal (imagpart obj1) (imagpart obj2)))) - (structure - (generalized-equal-fielded-object obj1 obj2)) - (standard-object - (generalized-equal-fielded-object obj1 obj2)) - (hash-table - (generalized-equal-hash-table obj1 obj2) - ) - (function - (generalized-equal-function obj1 obj2)) - (string - (string= obj1 obj2)) - (array - (generalized-equal-array obj1 obj2)) - (t - (equal obj1 obj2))))) - - -(defun generalized-equal-function (obj1 obj2) - (string= (function-to-string obj1) (function-to-string obj2))) - -(defun generalized-equal-array (obj1 obj2) - (block test - (when (not (= (array-total-size obj1) (array-total-size obj2))) - (return-from test nil)) - (dotimes (i (array-total-size obj1)) - (unless (generalized-equal (aref obj1 i) (aref obj2 i)) - (return-from test nil))) - (return-from test t))) - -(defun generalized-equal-hash-table (obj1 obj2) - (block test - (when (not (= (hash-table-count obj1) (hash-table-count obj2))) - (return-from test nil)) - (maphash - #'(lambda (k v) - (multiple-value-bind (value found) (gethash k obj2) - (unless (and found (generalized-equal v value)) - (return-from test nil)))) - obj1) - (return-from test t))) - -(defun generalized-equal-fielded-object (obj1 obj2) - (block test - (when (not (equal (class-of obj1) (class-of obj2))) - (return-from test nil)) - (dolist (field (class-slot-names (class-name (class-of obj1)))) - (unless (generalized-equal (slot-value obj1 field) (slot-value obj2 field)) - (return-from test nil))) - (return-from test t))) - -#+(or allegro lispworks) -(defun class-slot-names (class-name) - "Given a CLASS-NAME, returns a list of the slots in the class." - (mapcar #'clos:slot-definition-name - (clos:class-slots (find-class class-name)))) - -#-(or allegro lispworks) -(defun class-slot-names (class-name) - (warn "class-slot-names not supported on this platform")) - - -(defun function-to-string (obj) - "Returns the lambda code for a function. Relies on -Allegro implementation-dependent features." - (multiple-value-bind (lambda closurep name) (function-lambda-expression obj) - (declare (ignore closurep)) - (if lambda - (format nil "#'~s" lambda) - (if name - (format nil "#'~s" name) - (progn - (print obj) - (break)))))) + (let ((eof (gensym))) + (do ((line (read-line in nil eof) + (read-line in nil eof))) + ((eq line eof)) + (push line lines))) + (nreverse lines)))) ;;; Formatting functions @@ -741,12 +503,57 @@ Allegro implementation-dependent features." (multiple-value-bind (sec min hr day mon year dow daylight-p zone) (decode-universal-time ut) (declare (ignore daylight-p zone)) - (format nil "~[Mon~;Tue~;Wed~;Thu~;Fri~;Sat~;Sun~], ~ -~d ~[Jan~;Feb~;Mar~;Apr~;May~;Jun~;Jul~;Aug~;Sep~;Oct~;Nov~;Dec~] ~d ~ -~2,'0d:~2,'0d:~2,'0d" + (format nil "~[Mon~;Tue~;Wed~;Thu~;Fri~;Sat~;Sun~], ~d ~[Jan~;Feb~;Mar~;Apr~;May~;Jun~;Jul~;Aug~;Sep~;Oct~;Nov~;Dec~] ~d ~2,'0d:~2,'0d:~2,'0d" dow day (1- mon) year hr min sec)))) + +;; Benchmarking + +(defun print-float-units (val unit) + (cond + ((< val 1d-6) + (format t "~,2,9F nano~A" val unit)) + ((< val 1d-3) + (format t "~,2,6F micro~A" val unit)) + ((< val 1) + (format t "~,2,3F milli~A" val unit)) + ((> val 1d9) + (format t "~,2,-9F giga~A" val unit)) + ((> val 1d6) + (format t "~,2,-6F mega~A" val unit)) + ((> val 1d3) + (format t "~,2,-3F kilo~A" val unit)) + (t + (format t "~,2F ~A" val unit)))) + +(defun print-seconds (secs) + (print-float-units secs "sec")) + +(defmacro time-iterations (n &body body) + (let ((i (gensym)) + (count (gensym))) + `(progn + (let ((,count ,n)) + (format t "~&Test with ~d iterations: ~W" ,count (quote ,body)) + (let ((t1 (get-internal-real-time))) + (dotimes (,i ,count) + ,@body) + (let* ((t2 (get-internal-real-time)) + (secs (coerce (/ (- t2 t1) + internal-time-units-per-second) + 'double-float))) + (format t "~&Total time: ") + (print-seconds secs) + (format t ", time per iteration: ") + (print-seconds (coerce (/ secs ,n) 'double-float)))))))) + + +(defun nsubseq (sequence start &optional (end (length sequence))) + (make-array (- end start) + :element-type (array-element-type sequence) + :displaced-to sequence + :displaced-index-offset start))