;;; This is a -*-Lisp-*- file.

;;; **********************************************************************
;;; This code was written as part of the Spice Lisp project at
;;; Carnegie-Mellon University, and has been placed in the public domain.
;;; Spice Lisp is currently incomplete and under active development.
;;; If you want to use this code or any part of Spice Lisp, please contact
;;; Scott Fahlman (FAHLMAN@CMUC). 
;;; **********************************************************************

;;; Character functions for Spice Lisp.  Part of the standard Spice Lisp
;;; environment.

;;; Written by Guy Steele.
;;; Rewritten by David Dill.
;;; Hacked up for speed and currently maintained by Scott Fahlman.

;;; This file assumes the use of ASCII codes and the specific character
;;; formats used in Spice Lisp and Vax Common Lisp.  It is optimized for
;;; performance rather than for portability and elegance.  It will have
;;; to be rewritten for any implementation that uses a significantly
;;; different layout for characters.  In particular, several thing here
;;; assume that the code field is the rightmost after the char is converted
;;; to a fixnum.

;;; *******************************************************************

;;; The following are visible to the user.

(defconstant char-code-limit 256
  "The upper exclusive bound on values produced by CHAR-CODE.")
(defconstant char-font-limit 256
  "The upper exclusive bound on values produced by CHAR-FONT.")
(defconstant char-bits-limit 256
  "The upper exclusive bound on values produced by CHAR-BITS.")

(defconstant char-control-bit 1
  "This bit indicates a control character.")
(defconstant char-meta-bit 2
  "This bit indicates a meta character.")
(defconstant char-super-bit 4
  "This bit indicates a super character.")
(defconstant char-hyper-bit 8
  "This bit indicates a hyper character.")


;;; The following alist is set up at init time, since it is a bit too
;;; complex for the cold-loader to digest.

(defparameter char-name-alist
	`(("NULL" . ,(code-char 0))
	  ("BELL" . ,(code-char 7))
	  ("BACKSPACE" . ,(code-char 8)) ("BS" . ,(code-char 8))
	  ("TAB" . ,(code-char 9))
	  ("LINEFEED" . ,(code-char 10)) ("LF" . ,(code-char 10))
	  ("VT" . ,(code-char 11))
	  ("PAGE" . ,(code-char 12)) ("FORM" . ,(code-char 12))
	  ("FORMFEED" . ,(code-char 12)) ("FF" . ,(code-char 12))
	  ("RETURN" . ,(code-char 13)) ("NL" . ,(code-char 10))
	  ("NEWLINE" . ,(code-char 10))  ("CR" . ,(code-char 13))
	  ("ALTMODE" . ,(code-char 27)) ("ALT" . ,(code-char 27))
	  ("ESCAPE" . ,(code-char 27)) ("ESC" . ,(code-char 27))
	  ("SPACE" . ,(code-char 32)) ("SP" . ,(code-char 32))
	  ("RUBOUT" . ,(code-char 127)) ("DELETE" . ,(code-char 127)))
  "This is the alist of (character-name . character) for characters
  with long names.  The first name in this list for a given character
  is used on typeout and is the preferred form for input.")

;;; The following macros are for internal use in this file.

;;; Turn a character into a fixnum and complain if it is not really
;;; a character.  Most other functions in this file operate on the
;;; fixnum extracted here.

(defmacro get-and-check-int (c)
  `(if (characterp ,c)
       (%sp-make-fixnum ,c)
       (error "~S is not a character." ,c)))

;;; Turn a fixnum into a character.

(defmacro int-to-char (n)
  `(%primitive make-immediate-type ,n %character-type))

;;; Here begin the actual user-level functions.

(defun standard-char-p (char)
  "The argument must be a character object.  Standard-char-p returns T if the
   argument is a standard character -- one of the 95 ASCII printing characters
   or <return>."
  (let ((n (logand (get-and-check-int char) %character-int-mask)))
    (or (< 31 n 127)
	(= n 13))))


(defun graphic-char-p (char)
  "The argument must be a character object.  Graphic-char-p returns T if the
  argument is a printing character (space through ~ in ASCII), otherwise
  returns ()."
  (< 31 (logand (get-and-check-int char) %character-code-control-mask) 127))


(defun string-char-p (char)
  "The argument must be a character object.  String-char-p returns T if the
   argument can be stored in a string."
  (< (char-int char) 256))


(defun alpha-char-p (char)
  "The argument must be a character object.  Alpha-char-p returns T if the
   argument is an alphabetic character, A-Z or a-z; otherwise ()."
  (let ((m (logand (get-and-check-int char) %character-code-control-mask)))
    (or (< 64 m 91) (< 96 m 123))))


(defun upper-case-p (char)
  "The argument must be a character object; upper-case-p returns T if the
   argument is an upper-case character, () otherwise."
  (< 64 (logand (get-and-check-int char) %character-code-control-mask) 91))


(defun lower-case-p (char)
  "The argument must be a character object; lower-case-p returns T if the 
   argument is a lower-case character, () otherwise."
  (< 96 (logand (get-and-check-int char) %character-code-control-mask) 123))


(defun both-case-p (char)
  "The argument must be a character object.  Both-case-p returns T if the
  argument is an alphabetic character and if the character exists in
  both upper and lower case.  For ASCII, this is the same as Alpha-char-p."
  (let ((m (logand (get-and-check-int char)
		   %character-code-control-mask)))
    (or (< 64 m 91) (< 96 m 123))))


(defun digit-char-p (char &optional (radix 10.))
  "If char is a digit in the specified radix, returns the fixnum for
  which that digit stands, else returns NIL.  Radix defaults to 10
  (decimal)."
  (let ((m (- (logand (get-and-check-int char)
		      %character-code-control-mask)
	      48)))
    (cond ((<= radix 10.)
	   ;; Special-case decimal and smaller radices.
	   (if (and (>= m 0) (< m radix))  m  nil))
	  ;; Cannot handle radix past Z.
	  ((> radix 36)
	   (error "~S too large to be an input radix."  radix))
	  ;; Digits 0 - 9 are used as is, since radix is larger.
	  ((and (>= m 0) (< m 10)) m)
	  ;; Check for upper case A - Z.
	  ((and (>= (setq m (- m 7)) 10) (< m radix)) m)
	  ;; Also check lower case a - z.
	  ((and (>= (setq m (- m 32)) 10) (< m radix)) m)
	  ;; Else, fail.
	  (t nil))))


(defun alphanumericp (char)
  "Given a character-object argument, alphanumericp returns T if the
   argument is either numeric or alphabetic."
  (let ((m (logand (get-and-check-int char)
		   %character-code-control-mask)))
    (or (< 47 m 58) (< 64 m 91) (< 96 m 123))))


(defun char= (character &rest more-characters)
  "Returns T if all of its arguments are the same character."
  (do ((clist more-characters (cdr clist)))
      ((atom clist) T)
    (unless (eq (car clist) character) (return nil))))


(defun char/= (character &rest more-characters)
  "Returns T if no two of its arguments are the same character."
  (do* ((head character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (do* ((l list (cdr l)))                  ;inner loop returns T 
		 ((atom l) T)			     ; iff head /= rest.
	      (if (eq head (car l)) (return nil)))
      (return nil))))


(defun char< (character &rest more-characters)
  "Returns T if its arguments are in strictly increasing alphabetic order."
  (do* ((c character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (< (char-int c) (char-int (car list))) (return nil))))


(defun char> (character &rest more-characters)
  "Returns T if its arguments are in strictly decreasing alphabetic order."
  (do* ((c character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (> (char-int c) (char-int (car list))) (return nil))))


(defun char<= (character &rest more-characters)
  "Returns T if its arguments are in strictly non-decreasing alphabetic order."
  (do* ((c character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (<= (char-int c) (char-int (car list))) (return nil))))


(defun char>= (character &rest more-characters)
  "Returns T if its arguments are in strictly non-increasing alphabetic order."
  (do* ((c character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (>= (char-int c) (char-int (car list))) (return nil))))



;;; Equal-char-int is used by the following functions as a version of char-int
;;;  which loses font, bits, and case info.

(defmacro equal-char-int (character)
  `(let ((ch (logand (char-int ,character) %character-code-mask)))
     (if (< 96 ch 123) (- ch 32) ch)))



(defun char-equal (character &rest more-characters)
  "Returns T if all of its arguments are the same character.
  Font, bits, and case are ignored."
  (do ((clist more-characters (cdr clist)))
      ((atom clist) T)
    (unless (= (equal-char-int (car clist))
	       (equal-char-int character))
      (return nil))))


(defun char-not-equal (character &rest more-characters)
  "Returns T if no two of its arguments are the same character.
   Font, bits, and case are ignored."
  (do* ((head character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (do* ((l list (cdr l)))
		 ((atom l) T)
	      (if (= (equal-char-int head) (equal-char-int (car l)))
		  (return nil)))
      (return nil))))


(defun char-lessp (character &rest more-characters)
  "Returns T if its arguments are in strictly increasing alphabetic order.
   Font, bits, and case are ignored."
  (do* ((c character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (< (equal-char-int c) (equal-char-int (car list))) (return nil))))


(defun char-greaterp (character &rest more-characters)
  "Returns T if its arguments are in strictly decreasing alphabetic order.
   Font, bits, and case are ignored."
  (do* ((c character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (> (equal-char-int c) (equal-char-int (car list))) (return nil))))


(defun char-not-greaterp (character &rest more-characters)
  "Returns T if its arguments are in strictly non-decreasing alphabetic order.
   Font, bits, and case are ignored."
  (do* ((c character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (<= (equal-char-int c) (equal-char-int (car list))) (return nil))))


(defun char-not-lessp (character &rest more-characters)
  "Returns T if its arguments are in strictly non-increasing alphabetic order.
   Font, bits, and case are ignored."
  (do* ((c character (car list))
	(list more-characters (cdr list)))
       ((atom list) T)
    (unless (>= (equal-char-int c) (equal-char-int (car list))) (return nil))))


(defun character (object)
  "Coerces its argument into a character object if possible.  Accepts
  characters, strings and symbols of length 1, and integers."
  (typecase object
    (character object)
    (integer (int-char object))
    (string (if (= 1 (length object))
		(elt object 0) nil))
    (symbol (if (= 1 (length (symbol-name object)))
		(elt (symbol-name object) 0) nil))))


(defun char-code (char)
  "Given a character object argument, char-code returns the code attribute
   of that object as a non-negative integer."
  (logand (get-and-check-int char) %character-code-mask))


(defun char-bits (char)
  "Given a character object argument, char-code returns the bits attribute
   of that object as a non-negative integer."
  (ldb %character-control-byte (get-and-check-int char)))


(defun char-font (char)
  "Given a character object argument, char-code returns the font attribute
   of that object as a non-negative integer."
  (ldb %character-font-byte (get-and-check-int char)))


(defun code-char (code &optional (bits 0) (font 0))
  "All three arguments, must be non-negative integers; the last two are 
   optional with default values of 0 (for the bits and font attributes).
   Returns a character object with the specified code, bits, and font,
   or returns NIL if this is not possible."
  (cond ((not (< -1 code char-code-limit)) nil)
	((and (zerop bits) (zerop font) (int-char code)))
	(t (and (< -1 bits char-bits-limit)
		(< -1 font char-font-limit)
		(int-char (dpb font %character-font-byte
			       (dpb bits %character-control-byte
				    code)))))))


(defun make-char (char &optional (bits 0) (font 0))
  "Replaces the bits and font attributes of the specified character with
  those supplied by the user as fixnums.  Bits and font both default to 0."
  (and (< -1 bits char-bits-limit)
       (< -1 font char-font-limit)
       (int-to-char (dpb font %character-font-byte
			 (dpb bits %character-control-byte
			      (logand (get-and-check-int char)
				      %character-code-mask))))))


(defun char-upcase (char)
  "Returns a character with the same bits and font as the input character,
  converted to upper-case if that is possible."
  (let ((n (get-and-check-int char)))
    (cond ((< 96 (logand n %character-code-control-mask) 123)
	   (int-to-char (- n 32)))
	  (t char))))


(defun char-downcase (char)
  "Returns a character with the same bits and font as the input character,
  converted to lower-case if that is possible."
  (let ((n (get-and-check-int char)))
    (cond ((< 64 (logand n %character-code-control-mask) 91)
	   (int-to-char (+ n 32)))
	  (t char))))

(defun digit-char (weight &optional (radix 10) (font 0))
  "All arguments must be integers.  Returns a character object that
  represents a digit of the given weight in the specified radix.  Returns
  NIL if no such character exists.  The character will have the specified
  font attributes."
  (and (>= weight 0) (< weight radix) (< weight 36)
       (code-char (if (< weight 10) (+ 48 weight) (+ 55 weight))
		  0 font)))


(defun char-int (ch)
  "The argument must be a character-object.  Returns the font, bits, and
  code fields as a single non-negative integer.  Implementation dependent.
  Used mostly for hashing."
  (logand (get-and-check-int ch) %character-int-mask))


(defun int-char (n)
  "Performs the inverse of char-int.  The argument must be a non-negative
  integer of the appropriate size.  It is turned into a character object."
  (cond ((and (fixnump n) (<= 0 n %character-int-mask))
	 (int-to-char n))
	(t nil)))


(defun char-name (char)
  "Given a character object, char-name returns the name for that
  object (a symbol)."
  (car (rassoc char char-name-alist)))


(defun name-char (name)
  "Given an argument acceptable to string, name-char returns a character
  object whose name is that symbol, if one exists.  Otherwise, () is returned."
  (cdr (assoc (string name) char-name-alist :test #'string-equal)))


(defun char-bit (char name)
  "Returns T if the named bit is set in character object CHAR.  Else,
  returns NIL.  Legal names are :CONTROL, :META, :HYPER, and :SUPER."
  (logtest (case name
	     (:control char-control-bit)
	     (:meta char-meta-bit)
	     (:hyper char-hyper-bit)
	     (:super char-super-bit))
	   (char-bits char)))


(defun set-char-bit (char name newvalue)
  "Returns a character just like CHAR except that the named bit is
  set or cleared, according to whether NEWVALUE is non-null or NIL.
  Legal bit names are :CONTROL, :META, :HYPER, and :SUPER."
  (let ((bit (case name
	      (:control char-control-bit)
	      (:meta char-meta-bit)
	      (:hyper char-hyper-bit)
	      (:super char-super-bit)
	      (t 0))))
    (code-char (char-code char)
	       (if newvalue
		   (logior bit (char-bits char))
		   (logand (lognot bit) (char-bits char)))
	       (char-font char))))