;;; -*- Mode:LISP; Base:10; Readtable:CL -*-
(DEFUN PARSE-INTEGER (STRING &KEY (START 0) END (RADIX 10.) JUNK-ALLOWED)
  "Return a number parsed from the contents of STRING, or a part of it.
START and END are indices specify the part of the string; END = NIL means the end of it.
RADIX is a number from 2 to 36, which defaults to ten.

If JUNK-ALLOWED is NIL, the string must contain simply a number
surrounded by whitespace.  The number must be just digits (including suitable
letters if RADIX is > 10) and an optional sign.  Otherwise it is an error.

If JUNK-ALLOWED is non-NIL, parsing stops when anything is encountered that
doesn't fit that description, or at non-leading whitespace.
If no number has been found by then, the value is NIL.

The second value is the index in STRING of where parsing stopped."
  (DECLARE (VALUES NUMBER-OR-MAYBE-NIL STOP-INDEX))
  (SETQ STRING (STRING STRING))
  (LET ((INDEX START)
	(STOP (OR END (LENGTH STRING)))
	NUM SIGN TEM letter)
    ;; Skip initial whitespace.
    (DO () ((= INDEX STOP))
      (LET ((CH (CHAR STRING INDEX)))
;character lossage font
	(UNLESS (MEMQ CH '(#/SP #/TAB))
	  (RETURN))
	(INCF INDEX)))
    (DO ()
	((= INDEX STOP))
      (LET ((CH (CHAR-UPCASE (CHAR STRING INDEX))))
	(COND ((SETQ TEM (or (and (> radix 10.) ;calculate digit values for calls with high radix values here
				  (< radix 37.)	;radix must be between 2 and 36, inclusive...
				  (setq letter (string-search ch	
							      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"	;all 26 needed for base 36...
							      0
							      (- radix 10.)))	;use only the legal letters for this radix...
				  letter	;string-search returned nil if letter was not a legal one for this radix...
				  (+ 10. letter))	;add the proper amount for the letter given
			     (DIGIT-CHAR-P CH)))
	       (SETQ NUM (+ (* (OR NUM 0) RADIX) TEM)))
;character lossage font
	      ((AND (NULL SIGN) (NULL NUM) (EQ CH #/+)) (SETQ SIGN 1))
	      ((AND (NULL SIGN) (NULL NUM) (EQ CH #/-)) (SETQ SIGN -1))
	      (T
	       (UNLESS JUNK-ALLOWED
		 ;; No junk allowed.  Skip any trailing whitespace.
		 (DO () ((= INDEX STOP))
;character lossage font
		   (LET ((CH (CHAR STRING INDEX)))
		     (UNLESS (MEMQ CH '(#/SP #/TAB))
		       (RETURN))
		     (INCF INDEX)))
		 ;; If this did not get us to the end, barf.
		 (UNLESS (= INDEX STOP)
		   (FERROR "~S does not contain simply a number surrounded by whitespace."
			   STRING)))
	       (RETURN)))
	(INCF INDEX)))
    (VALUES (AND NUM (IF (EQ SIGN -1) (- NUM) NUM))
	    INDEX)))

(DEFUN PARSE-NUMBER (STRING &OPTIONAL (FROM 0) TO (RADIX 10.) FAIL-IF-NOT-WHOLE-STRING)
  "Return a number parsed from the contents of STRING, or a part of it.
FROM and TO specify the part of the string; TO = NIL means the end of it.
RADIX defaults to decimal.

If the string or part doesn't start with a number, NIL is returned.
The second value is the index in STRING of the first non-digit, or NIL if none.
FAIL-IF-NOT-WHOLE-STRING means return NIL and 0 unless the whole string or
specified part can be parsed as a number."
  (LOOP WITH SIGN = NIL
	WITH NUM = NIL
	with letter = nil
	WITH RADIX = (OR RADIX 10.)
	WITH TEM
	FOR I FROM FROM BELOW (OR TO (STRING-LENGTH STRING))
	FOR CH = (CHAR STRING I)
     DO (COND ((SETQ TEM  (or (and (> radix 10.)
				   (< radix 37.)
				  (setq letter (string-search ch "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 0 (- radix 10.)))
				  letter
				  (+ 10. letter))
			     (DIGIT-CHAR-P CH)))
	       (SETQ NUM (+ (* (OR NUM 0) RADIX) TEM)))
;character lossage font
	      ((AND (NULL SIGN) (NULL NUM) (EQ CH #/+)) (SETQ SIGN 1))
;character lossage font
	      ((AND (NULL SIGN) (NULL NUM) (EQ CH #/-)) (SETQ SIGN -1))
	      (T
	       (IF FAIL-IF-NOT-WHOLE-STRING
		   (RETURN (VALUES NIL 0))
		 (LOOP-FINISH))))
     FINALLY (RETURN (VALUES (AND NUM (IF (EQ SIGN -1) (- NUM) NUM))
			     (IF NUM I 0)))))
