Mercurial > core / lisp/std/num/parse.lisp
changeset 698: |
96958d3eb5b0 |
parent: |
0af47621fa8b
|
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Fri, 04 Oct 2024 22:04:59 -0400 |
permissions: |
-rw-r--r-- |
description: |
fixes |
1 ;;; std/num/parse.lisp --- Number parsing functions 7 (declaim (optimize (speed 3) (safety 3))) 8 (define-condition invalid-number (parse-error) 9 ((value :reader invalid-number-value 12 (reason :reader invalid-number-reason 14 :initform "Not specified")) 15 (:report (lambda (c s) 16 (format s "Invalid number: ~S [Reason: ~A]" 17 (invalid-number-value c) 18 (invalid-number-reason c))))) 20 (declaim (type (cons character) *white-space-characters*)) 21 (defparameter *white-space-characters* 22 (list #\Space #\Tab #\Return #\Linefeed) 23 "A list of all of the whitespace characters.") 25 (declaim (inline white-space-p)) 26 (defun white-space-p (x) 27 "Is the given character a whitespace character?" 28 (declare (optimize (speed 3) (safety 0)) 30 (and (find x *white-space-characters*) t)) 32 (declaim (inline parse-integer-and-places)) 33 (defun parse-integer-and-places (string start end &key (radix 10)) 34 "Parse an integer and return a 'parsed-integer'. This is an object 35 whose numerical value can be accessed with the function 36 number-value and whose length can be accessed with the function 38 (declare (optimize (speed 3) (safety 1)) 39 (type simple-string string) 40 (fixnum start end radix)) 41 (multiple-value-bind (integer end-pos) 48 (declare (fixnum integer end-pos)) 49 ;; cl:parse-integer will consume trailing whitespace, thus end-pos may be 50 ;; larger than the number of digits. Instead of trimming whitespace 51 ;; beforehand we count it here 52 (let* ((count (loop for pos from (- end-pos 1) downto start 53 while (member (char string pos) 54 *white-space-characters* 57 (relevant-digits (the fixnum (- end-pos start count)))) 58 (cons integer relevant-digits)))) 60 (defun parse-integers (string start end splitting-points &key (radix 10)) 61 "Parse a string containing multiple integers where SPLITTING-POINTS 62 is a list of locations where each location is inbetween 63 consecutive integers. This will return a list of parsed-integers. 64 The last parsed-integer will have a negative value for its length." 65 (declare (optimize (speed 3) (safety 1)) 66 (type simple-string string) 67 (fixnum start end radix) 68 (list splitting-points)) 69 (values-list (loop for left = start then (1+ right) 70 for point in splitting-points 72 collect (parse-integer-and-places string 80 (parse-integer-and-places string 86 (declaim (inline number-value places)) 87 (defun number-value (x) "Get the value of a parsed-integer." (car x)) 88 (defun places (x) "Get the length of a parsed-integer." (cdr x)) 90 ;; Numbers which could've been parsed, but intentionally crippled not to: 94 ;; Numbers which CL doesn't parse, but this does: 97 (defun parse-number (string &key (start 0) (end nil) (radix 10) 98 ((:float-format *read-default-float-format*) 99 *read-default-float-format*)) 100 "Given a string, and start, end, and radix parameters, produce a number according to the syntax definitions in the Common Lisp Hyperspec." 101 (declare (type simple-string string)) 102 (flet ((invalid-number (reason) 103 (error 'invalid-number 104 :value (subseq string start end) 106 (let ((end (or end (length string)))) 107 (declare (fixnum start end radix)) 108 (if (and (eql (char string start) #\#) 109 (member (char string (1+ start)) '(#\C #\c))) 110 (let ((\(-pos (position #\( string :start start :end end)) 111 (\)-pos (position #\) string :start start :end end))) 112 (when (or (not \(-pos) 114 (position #\( string :start (1+ \(-pos) :end end) 115 (position #\) string :start (1+ \)-pos) :end end)) 116 (invalid-number "Mismatched/missing parenthesis")) 118 (let ((real-pos (position-if-not #'white-space-p string 119 :start (1+ \(-pos) :end \)-pos))) 121 (invalid-number "Missing real part")) 122 (let ((delimiting-space (position-if #'white-space-p string 125 (unless delimiting-space 126 (invalid-number "Missing imaginary part")) 127 (let ((img-pos (position-if-not #'white-space-p string 128 :start (1+ delimiting-space) 131 (invalid-number "Missing imaginary part")) 132 (let ((img-end-pos (position-if #'white-space-p string 135 (complex (parse-real-number string 137 :end delimiting-space 139 (parse-real-number string 144 (parse-real-number string :start start :end end :radix radix))))) 146 (defun parse-real-number (string &key (start 0) (end nil) (radix 10) 147 ((:float-format *read-default-float-format*) 148 *read-default-float-format*)) 149 "Given a string, and start, end, and radix parameters, produce a number according to the syntax definitions in the Common Lisp Hyperspec -- except for complex numbers." 150 (declare (simple-string string)) 151 (let ((end (or end (length string)))) 152 (case (char string start) 154 (* -1 (the fixnum (parse-positive-real-number string 159 (parse-positive-real-number string 164 (case (char string (1+ start)) 166 (parse-real-number string 171 (parse-real-number string 176 (parse-real-number string 180 (t (if (digit-char-p (char string (1+ start))) 181 (let ((r-pos (position #\r string 184 :key #'char-downcase))) 186 (error 'invalid-number 187 :value (subseq string start end) 188 :reason "Missing R in #radixR")) 189 (parse-real-number string 192 :radix (parse-integer string 195 (t (parse-positive-real-number string 200 (defun base-for-exponent-marker (char) 201 "Return the base for an exponent-marker." 206 (coerce 10 *read-default-float-format*)) 214 (defun make-float/frac (radix exp-marker whole-place frac-place exp-place) 215 "Create a float using EXP-MARKER as the exponent-marker and the 216 parsed-integers WHOLE-PLACE, FRAC-PLACE, and EXP-PLACE as the 217 integer part, fractional part, and exponent respectively." 218 (declare (fixnum radix)) 219 (let* ((base (base-for-exponent-marker exp-marker)) 220 (exp (expt base (number-value exp-place)))) 221 (+ (* exp (number-value whole-place)) 222 (/ (* exp (number-value frac-place)) 223 (expt (float radix base) 224 (places frac-place)))))) 226 (defun make-float/whole (exp-marker whole-place exp-place) 227 "Create a float where EXP-MARKER is the exponent-marker and the 228 parsed-integers WHOLE-PLACE and EXP-PLACE as the integer part and 229 the exponent respectively." 230 (* (number-value whole-place) 231 (expt (base-for-exponent-marker exp-marker) 232 (number-value exp-place)))) 234 (defun parse-positive-real-number (string &key (start 0) (end nil) (radix 10) 235 ((:float-format *read-default-float-format*) 236 *read-default-float-format*)) 237 "Given a string, and start, end, and radix parameters, produce a number according to the syntax definitions in the Common Lisp Hyperspec -- except for complex numbers and negative numbers." 238 (declare (simple-string string) 240 (let ((end (or end (length string))) 241 (first-char (char string start))) 242 (flet ((invalid-number (reason) 243 (error 'invalid-number 244 :value (subseq string start end) 246 (when (position-if #'white-space-p string 247 :start (or (position-if-not #'white-space-p string 251 :end (position-if-not #'white-space-p string 255 (invalid-number "Whitespace inside the number")) 258 (invalid-number "Invalid usage of -")) 260 (invalid-number "/ at beginning of number")) 261 ((#\d #\D #\e #\E #\l #\L #\f #\F #\s #\S) 263 (invalid-number "Exponent-marker at beginning of number")))) 264 (let (/-pos .-pos exp-pos exp-marker) 265 (loop for index from start below end 266 for char = (char string index) 270 (invalid-number "Multiple /'s in number") 274 (invalid-number "Multiple .'s in number") 276 ((#\e #\E #\f #\F #\s #\S #\l #\L #\d #\D) 277 ;; We should only execute this if the base is 278 ;; not used for the given radix (ie the digit 279 ;; e is valid in base 15 and up). 281 (- (char-code (char-upcase char)) 286 "Multiple exponent-markers in number")) 288 (setf exp-marker (char-downcase char))))) 289 when (eql index (1- end)) 292 (invalid-number "/ at end of number")) 293 ((#\d #\D #\e #\E #\s #\S #\l #\L #\f #\F) 295 (invalid-number "Exponent-marker at end of number"))))) 296 (cond ((and /-pos .-pos) 297 (invalid-number "Both . and / cannot be present simultaneously")) 299 (invalid-number "Both an exponent-marker and / cannot be present simultaneously")) 301 (if (< exp-pos .-pos) 302 (invalid-number "Exponent-markers must occur after . in number") 304 (invalid-number "Only decimal numbers can contain exponent-markers or decimal points") 305 (multiple-value-bind (whole-place frac-place exp-place) 306 (parse-integers string start end 309 (make-float/frac radix exp-marker whole-place frac-place exp-place))))) 312 (invalid-number "Only decimals can contain exponent-markers") 313 (multiple-value-bind (whole-place exp-place) 314 (parse-integers string start end 317 (make-float/whole exp-marker whole-place exp-place)))) 319 (multiple-value-bind (numerator denominator) 320 (parse-integers string start end 323 (if (>= (number-value denominator) 0) 324 (/ (number-value numerator) 325 (number-value denominator)) 326 (invalid-number "Misplaced - sign")))) 329 (invalid-number "Only decimal numbers can contain decimal points") 330 (multiple-value-bind (whole-part frac-part) 331 (parse-integers string start end 335 ((minusp (places frac-part)) 336 (if (and (zerop (number-value whole-part)) 337 (zerop (places whole-part))) 338 (invalid-number "Only the . is present") 339 (number-value whole-part))) 340 ((>= (number-value frac-part) 0) 341 (coerce (+ (number-value whole-part) 342 (/ (number-value frac-part) 343 (expt 10 (places frac-part)))) 344 *read-default-float-format*)) 346 (invalid-number "Misplaced - sign")))))) 348 (values (parse-integer string