Mercurial > core / lisp/std/num/parse.lisp
changeset 122: |
4ba88cac5bc7 |
child: |
f3d814fb136a |
author: |
ellis <ellis@rwest.io> |
date: |
Sun, 24 Dec 2023 01:06:20 -0500 |
permissions: |
-rw-r--r-- |
description: |
num/parse, added DAT system, net/fetch, time/local, refactored trees |
3 (define-condition invalid-number (parse-error) 4 ((value :reader invalid-number-value 7 (reason :reader invalid-number-reason 9 :initform "Not specified")) 10 (:report (lambda (c s) 11 (format s "Invalid number: ~S [Reason: ~A]" 12 (invalid-number-value c) 13 (invalid-number-reason c))))) 15 (declaim (type cons *white-space-characters*)) 16 (defparameter *white-space-characters* 17 '(#\Space #\Tab #\Return #\Linefeed) 18 "A list of all of the whitespace characters.") 20 (declaim (inline white-space-p)) 21 (defun white-space-p (x) 22 "Is the given character a whitespace character?" 23 (declare (optimize (speed 3) (safety 0)) 25 (and (find x *white-space-characters*) t)) 27 (declaim (inline parse-integer-and-places)) 28 (defun parse-integer-and-places (string start end &key (radix 10)) 29 "Parse an integer and return a 'parsed-integer'. This is an object 30 whose numerical value can be accessed with the function 31 number-value and whose length can be accessed with the function 33 (declare (optimize (speed 3) (safety 1)) 35 (type fixnum start end radix)) 36 (multiple-value-bind (integer end-pos) 43 ;; cl:parse-integer will consume trailing whitespace, thus end-pos may be 44 ;; larger than the number of digits. Instead of trimming whitespace 45 ;; beforehand we count it here 46 (let ((relevant-digits (- end-pos start 47 (loop :for pos :from (- end-pos 1) :downto start 48 :while (member (char string pos) 49 *white-space-characters*) 51 (cons integer relevant-digits)))) 53 (defun parse-integers (string start end splitting-points &key (radix 10)) 54 "Parse a string containing multiple integers where SPLITTING-POINTS 55 is a list of locations where each location is inbetween 56 consecutive integers. This will return a list of parsed-integers. 57 The last parsed-integer will have a negative value for its length." 58 (declare (optimize (speed 3) (safety 1)) 60 (type fixnum start end radix)) 61 (values-list (loop for left = start then (1+ right) 62 for point in splitting-points 64 collect (parse-integer-and-places string 72 (parse-integer-and-places string 78 (declaim (inline number-value places)) 79 (defun number-value (x) "Get the value of a parsed-integer." (car x)) 80 (defun places (x) "Get the length of a parsed-integer." (cdr x)) 82 ;; Numbers which could've been parsed, but intentionally crippled not to: 86 ;; Numbers which CL doesn't parse, but this does: 90 (defun parse-number (string &key (start 0) (end nil) (radix 10) 91 ((:float-format *read-default-float-format*) 92 *read-default-float-format*)) 93 "Given a string, and start, end, and radix parameters, produce a number according to the syntax definitions in the Common Lisp Hyperspec." 94 (flet ((invalid-number (reason) 95 (error 'invalid-number 96 :value (subseq string start end) 98 (let ((end (or end (length string)))) 99 (if (and (eql (char string start) #\#) 100 (member (char string (1+ start)) '(#\C #\c))) 101 (let ((\(-pos (position #\( string :start start :end end)) 102 (\)-pos (position #\) string :start start :end end))) 103 (when (or (not \(-pos) 105 (position #\( string :start (1+ \(-pos) :end end) 106 (position #\) string :start (1+ \)-pos) :end end)) 107 (invalid-number "Mismatched/missing parenthesis")) 108 (let ((real-pos (position-if-not #'white-space-p string 109 :start (1+ \(-pos) :end \)-pos))) 111 (invalid-number "Missing real part")) 112 (let ((delimiting-space (position-if #'white-space-p string 115 (unless delimiting-space 116 (invalid-number "Missing imaginary part")) 117 (let ((img-pos (position-if-not #'white-space-p string 118 :start (1+ delimiting-space) 121 (invalid-number "Missing imaginary part")) 122 (let ((img-end-pos (position-if #'white-space-p string 125 (complex (parse-real-number string 127 :end delimiting-space 129 (parse-real-number string 134 (parse-real-number string :start start :end end :radix radix))))) 136 (defun parse-real-number (string &key (start 0) (end nil) (radix 10) 137 ((:float-format *read-default-float-format*) 138 *read-default-float-format*)) 139 "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." 140 (let ((end (or end (length string)))) 141 (case (char string start) 143 (* -1 (parse-positive-real-number string 148 (parse-positive-real-number string 153 (case (char string (1+ start)) 155 (parse-real-number string 160 (parse-real-number string 165 (parse-real-number string 169 (t (if (digit-char-p (char string (1+ start))) 170 (let ((r-pos (position #\r string 173 :key #'char-downcase))) 175 (error 'invalid-number 176 :value (subseq string start end) 177 :reason "Missing R in #radixR")) 178 (parse-real-number string 181 :radix (parse-integer string 184 (t (parse-positive-real-number string 189 (defun base-for-exponent-marker (char) 190 "Return the base for an exponent-marker." 195 (coerce 10 *read-default-float-format*)) 203 (defun make-float/frac (radix exp-marker whole-place frac-place exp-place) 204 "Create a float using EXP-MARKER as the exponent-marker and the 205 parsed-integers WHOLE-PLACE, FRAC-PLACE, and EXP-PLACE as the 206 integer part, fractional part, and exponent respectively." 207 (let* ((base (base-for-exponent-marker exp-marker)) 208 (exp (expt base (number-value exp-place)))) 209 (+ (* exp (number-value whole-place)) 210 (/ (* exp (number-value frac-place)) 211 (expt (float radix base) 212 (places frac-place)))))) 214 (defun make-float/whole (exp-marker whole-place exp-place) 215 "Create a float where EXP-MARKER is the exponent-marker and the 216 parsed-integers WHOLE-PLACE and EXP-PLACE as the integer part and 217 the exponent respectively." 218 (* (number-value whole-place) 219 (expt (base-for-exponent-marker exp-marker) 220 (number-value exp-place)))) 222 (defun parse-positive-real-number (string &key (start 0) (end nil) (radix 10) 223 ((:float-format *read-default-float-format*) 224 *read-default-float-format*)) 225 "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." 226 (let ((end (or end (length string))) 227 (first-char (char string start))) 228 (flet ((invalid-number (reason) 229 (error 'invalid-number 230 :value (subseq string start end) 232 (when (position-if #'white-space-p string 233 :start (or (position-if-not #'white-space-p string 237 :end (position-if-not #'white-space-p string 241 (invalid-number "Whitespace inside the number")) 244 (invalid-number "Invalid usage of -")) 246 (invalid-number "/ at beginning of number")) 247 ((#\d #\D #\e #\E #\l #\L #\f #\F #\s #\S) 249 (invalid-number "Exponent-marker at beginning of number")))) 250 (let (/-pos .-pos exp-pos exp-marker) 251 (loop for index from start below end 252 for char = (char string index) 256 (invalid-number "Multiple /'s in number") 260 (invalid-number "Multiple .'s in number") 262 ((#\e #\E #\f #\F #\s #\S #\l #\L #\d #\D) 263 ;; We should only execute this if the base is 264 ;; not used for the given radix (ie the digit 265 ;; e is valid in base 15 and up). 267 (- (char-code (char-upcase char)) 272 "Multiple exponent-markers in number")) 274 (setf exp-marker (char-downcase char))))) 275 when (eql index (1- end)) 278 (invalid-number "/ at end of number")) 279 ((#\d #\D #\e #\E #\s #\S #\l #\L #\f #\F) 281 (invalid-number "Exponent-marker at end of number"))))) 282 (cond ((and /-pos .-pos) 283 (invalid-number "Both . and / cannot be present simultaneously")) 285 (invalid-number "Both an exponent-marker and / cannot be present simultaneously")) 287 (if (< exp-pos .-pos) 288 (invalid-number "Exponent-markers must occur after . in number") 290 (invalid-number "Only decimal numbers can contain exponent-markers or decimal points") 291 (multiple-value-bind (whole-place frac-place exp-place) 292 (parse-integers string start end 295 (make-float/frac radix exp-marker whole-place frac-place exp-place))))) 298 (invalid-number "Only decimals can contain exponent-markers") 299 (multiple-value-bind (whole-place exp-place) 300 (parse-integers string start end 303 (make-float/whole exp-marker whole-place exp-place)))) 305 (multiple-value-bind (numerator denominator) 306 (parse-integers string start end 309 (if (>= (number-value denominator) 0) 310 (/ (number-value numerator) 311 (number-value denominator)) 312 (invalid-number "Misplaced - sign")))) 315 (invalid-number "Only decimal numbers can contain decimal points") 316 (multiple-value-bind (whole-part frac-part) 317 (parse-integers string start end 321 ((minusp (places frac-part)) 322 (if (and (zerop (number-value whole-part)) 323 (zerop (places whole-part))) 324 (invalid-number "Only the . is present") 325 (number-value whole-part))) 326 ((>= (number-value frac-part) 0) 327 (coerce (+ (number-value whole-part) 328 (/ (number-value frac-part) 329 (expt 10 (places frac-part)))) 330 *read-default-float-format*)) 332 (invalid-number "Misplaced - sign")))))) 334 (values (parse-integer string