From: Giorgos
Subject: Request for comments for my parse-float
Date: 
Message-ID: <1144275438.224613@athnrd02>
Hi all,

I am new to lisp and, for fun and practice, I just wrote some code to
parse floating-point numbers from a string, which I attached at the
end of this post. 

To be more clear, the code does not do any parsing per se, it depends
on read-from-string. The code essentially implements a series of
tests, so that it ensures that only a properly formatted string is
fed into read-from-string. 

I know that parsing floats has already been done and is available from
some repository (CMU if I remember correctly), but as I said
previously, this was intended for practice. Furthermore, the tests
seem independently useful --- at least for me :-)

I would be very happy if anyone would like to comment on the code.
Since I am just a newbie, I would very much like to hear about the
"lispiness" of the code, i.e. if I should write code like this or I
am working against the language in some respect. But, of course, any
comment is gladly welcome.

Thank you for your attention. 

-- Giorgos


Here is the code. It depends on split-sequence, which I found via this
newsgroup and asdf-install.


;; GENERAL COMMENTS
;; I assume that a string representing a floating point number in the
;; format -12.34e-5 can be seen as a decimal ("-12.34") and an integer
;; ("-5"), split by a single character ("e"). Analogously, the decimal
;; can be seen as two integers ("-12" and "34") split by a single 
;; character (".").

;; So I identify the basic rules of formatting for the sign, the
;; exponentiation characters and the point character (e.g. if there is
;; a sign character in an integer, it must come first), and I
;; implement the respective test. Then I write the tests for properly
;; formatted integers, decimals and floats.

;; Any string that is ok as a float, may safely go into 
;; parse-from-string.


;;;---------------------------------------------------------------
;;; SOME HELPER FUNCTIONS
;;;---------------------------------------------------------------
(defun empty-string-p (str)
  "Tests if an object is a string and it is empty (zero length)"
  (and (stringp str)
       (= (length str) 0)))

(defun char-p (seq)
  "Character predicate constructor"
  #'(lambda(c)
      (find c seq)))


;;;---------------------------------------------------------------
;;; ESSENTIAL CHARACTER PREDICATES
;;;---------------------------------------------------------------

(defun sign-char-p (c)
  "Predicate for sign characters"
  (or (char= #\+ c)
      (char= #\- c)))

(defun exp-char-p (c)
  "Predicate for characters indicating exponentiation"
  (funcall (char-p "eEdDlLsS") c))

(defun point-char-p (c)
  "Predicate for the decimal point"
  (char= #\. c))

(defun integer-char-p (c)
  "Predicate for all characters that may be found
   in integer numbers"
  (or (digit-char-p c)
      (sign-char-p c)))

(defun decimal-char-p (c)
  "Predicate for all characters that may be found
   in decimal numbers"
  (or (point-char-p c)
      (integer-char-p c)))

(defun float-char-p (c)
  "Predicate for all characters that may be found
   in floating point numbers"
  (or (exp-char-p c)
      (decimal-char-p c)))


;;;---------------------------------------------------------------
;;; PREDICATES FOR PROPER NUMBER FORMATTING
;;;---------------------------------------------------------------
(defun sign-ok-p (str)
  (when (not (empty-string-p str))
    (let ((c (count-if #'sign-char-p str)))
      (or (= c 0)
          (and (= c 1)
               (= 0 (position-if #'sign-char-p str))
               (< c (length str)))))))

(defun point-ok-p (str)
  (when (not (empty-string-p str))
    (let ((c (count-if #'point-char-p str)))
      (or (= c 0)
          (and (= c 1)
               (< c (- (length str)
                       (or (count-if #'sign-char-p str) 0))))))))

(defun exp-ok-p (str)
  (when (not (empty-string-p str))
    (let ((c (count-if #'exp-char-p str))
          (pe (position-if #'exp-char-p str))
          (pp (or (position-if #'point-char-p str) 0)))
      (or (= 0 c)
          (and (= c 1)
               (< 0 pe (- (length str) 1))
               (< pp pe))))))

(defun integer-ok-p (str)
  (when (not (empty-string-p str))
    (and (= 0 (count-if (complement #'integer-char-p) str))
         (sign-ok-p str))))

(defun decimal-ok-p (str)
  (when (not (empty-string-p str))
    (and (= 0 (count-if (complement #'decimal-char-p) str))
         (point-ok-p str)
         (sign-ok-p str))))

(defun float-ok-p (str)
  (when (not (empty-string-p str))
    (and (= 0 (count-if (complement #'float-char-p) str))
         (exp-ok-p str)
         (let ((parts (split-sequence-if #'exp-char-p
                                         str
                                         :remove-empty-subseqs t)))
           (and (decimal-ok-p (car parts))
                (or (null (cadr parts))
                    (integer-ok-p (cadr parts))))))))


;;;---------------------------------------------------------------
;;; PARSE FLOATING POINT NUMBERS
;;;---------------------------------------------------------------
(defun parse-float (str)
  "Parse a string into a floating point numbers list"
  (if (float-ok-p str)
      (nth-value 0 (read-from-string str))
      nil))

;;; END

From: Pascal Bourguignon
Subject: Re: Request for comments for my parse-float
Date: 
Message-ID: <87fykrbnxd.fsf@thalassa.informatimago.com>
Giorgos <·······@gmail.com> writes:
> I am new to lisp and, for fun and practice, I just wrote some code to
> parse floating-point numbers from a string, which I attached at the
> end of this post. 
>
> To be more clear, the code does not do any parsing per se, it depends
> on read-from-string. The code essentially implements a series of
> tests, so that it ensures that only a properly formatted string is
> fed into read-from-string. 
>
> I know that parsing floats has already been done and is available from
> some repository (CMU if I remember correctly), but as I said
> previously, this was intended for practice. Furthermore, the tests
> seem independently useful --- at least for me :-)
>
> I would be very happy if anyone would like to comment on the code.
> Since I am just a newbie, I would very much like to hear about the
> "lispiness" of the code, i.e. if I should write code like this or I
> am working against the language in some respect. But, of course, any
> comment is gladly welcome.

This is ok if you want to restrict the syntax for floating point
numbers.  If you don't mind to accept the full CL syntax for floating
point numbers, then there's little point in pre-processing
strings. Just read-from-string, and check you've got a NUMBER or a REAL,
and COERCE it to a float type.

 (defun parse-float (str)
    (let ((value (read-from-string str)))
       (check-type value real)
       (coerce value 'double-float)))


> ;; GENERAL COMMENTS
> ;; I assume that a string representing a floating point number in the
> ;; format -12.34e-5 can be seen as a decimal ("-12.34") and an integer
> ;; ("-5"), split by a single character ("e"). Analogously, the decimal
> ;; can be seen as two integers ("-12" and "34") split by a single 
> ;; character (".").
>
> ;; So I identify the basic rules of formatting for the sign, the
> ;; exponentiation characters and the point character (e.g. if there is
> ;; a sign character in an integer, it must come first), and I
> ;; implement the respective test. Then I write the tests for properly
> ;; formatted integers, decimals and floats.
>
> ;; Any string that is ok as a float, may safely go into 
> ;; parse-from-string.
>
> [...good enough code...]

you can also write it as:

(defun parse-float (str)
  "Check the string has a floating point syntax,
   and if it's the case, then parse it into a floating point number."
   (if #+clisp (regexp:regexp-exec
                   (load-time-value
                     (regexp:regexp-compile 
                        "^ *[-+]?[0-9]+(\\.[0-9]+)?([EDLS][-+]?[0-9]+)? *$"
                        :extended t :ignore-case t))
                     str)
       #-clisp (error "You can also use PCRE http://www.cliki.net/PCRE")
      (values (read-from-string str))
      nil))
(compile 'parse-float)

This way, you can easily update the syntax if need be.


(mapcar (function parse-float)
               '(
                 "123" "123.456" "123e12" "123.456e12" 
                 "-123" "-123.456" "-123e12" "-123.456e12" 
                 "+123" "+123.456" "-123e-12" "-123.456e+12"
                 "  123  " "  123.456  " "  123e12  " "  123.456e12  "   
                 "  -123  " "  -123.456  " "  -123e12  " "  -123.456e12  "   
                 "  +123  " "  +123.456  " "  -123e-12  " "  -123.456e+12  " 
                 "123,456" "123.456^12" "123+456"))
-->
(123 123.456 1.23E14 1.23456E14 -123 -123.456 -1.23E14 -1.23456E14 123 123.456
 -1.23E-10 -1.23456E14 123 123.456 1.23E14 1.23456E14 -123 -123.456 -1.23E14
 -1.23456E14 123 123.456 -1.23E-10 -1.23456E14 NIL NIL NIL)

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
Litter box not here.
You must have moved it again.
I'll poop in the sink. 
From: David Sletten
Subject: Re: Request for comments for my parse-float
Date: 
Message-ID: <80%Yf.23317$WK1.1354@tornado.socal.rr.com>
Giorgos wrote:
> Hi all,
> 
> I am new to lisp and, for fun and practice, I just wrote some code to
> parse floating-point numbers from a string, which I attached at the
> end of this post. 
> 
> To be more clear, the code does not do any parsing per se, it depends
> on read-from-string. The code essentially implements a series of
> tests, so that it ensures that only a properly formatted string is
> fed into read-from-string. 
> 
> I know that parsing floats has already been done and is available from
> some repository (CMU if I remember correctly), but as I said
> previously, this was intended for practice. Furthermore, the tests
> seem independently useful --- at least for me :-)
> 
> I would be very happy if anyone would like to comment on the code.
> Since I am just a newbie, I would very much like to hear about the
> "lispiness" of the code, i.e. if I should write code like this or I
> am working against the language in some respect. But, of course, any
> comment is gladly welcome.
> 
> Thank you for your attention. 
> 
> -- Giorgos
> 

Giorgos,

It looks like you've done some research and learned many things. This 
code is fine for a learning exercise, but you recognize this is not 
really the right way to do this. I'll comment on the approach in a bit, 
but first I'll mention a few Lisp ideas. Bear in mind that there's 
nothing really bad about your code. I'm just making some suggestions to 
mostly make it clearer to read.

You repeatedly use the pattern (when (not ...) ...) below. Common Lisp 
has another macro for this purpose: UNLESS. So, SIGN-OK-P would become:
(defun sign-ok-p (str)
   (unless (empty-string-p str) ...))

However, I would not use either WHEN or UNLESS in these cases. My rule 
of thumb is that WHEN/UNLESS is appropriate for conditional execution 
when there is no "else" clause and the code is executed for side effect. 
On the other hand, your functions here are all predicates and must 
return a value whether or not the required conditions are true. Rather 
than relying on an implicit NIL if the WHEN test fails, I would use an 
IF (or COND) to return an explicit value. So I would write:
(defun sign-ok-p (str)
   (if (empty-string-p str)
       nil
       (let ((c (count-if #'sign-char-p str)))
         (or (= c 0)
             (and (= c 1)
                  (= 0 (position-if #'sign-char-p str))
                  (< c (length str)))) )))

I think I would rearrange this even further though:
(defun sign-ok-p (str)
   (if (empty-string-p str)
       nil
       (let ((pos (position-if #'sign-char-p str)))
         (if pos
             (and (= pos 0)
                  (null (position-if #'sign-char-p str :start (1+ pos)))
                  (> (length str) 1))
             t))))
Using COUNT-IF you have to entirely traverse the string. With 
POSITION-IF twice (with the :START keyword the 2nd time) we can maybe 
save some work. You check for multiple sign characters by counting. I 
merely check to make sure there isn't a second one after the first.

You could define EMPTY-STRING-P this way:
(defun empty-string-p (str)
   "Tests if an object is a string and it is empty (zero length)"
   (equal str "")

I'm not sure what the purpose of CHAR-P is. It's only used once and it 
makes EXP-CHAR-P less efficient than:
(defun exp-char-p (c)
   "Predicate for characters indicating exponentiation"
   (find c "edls" :test #'char-equal))

Or

(defun exp-char-p (c)
   "Predicate for characters indicating exponentiation"
   (find (char-downcase c) "edls"))

In your main function, PARSE-FLOAT, you seem to really not want multiple 
values returned. You could do this:
(defun parse-float (str)
   "Parse a string into a floating point numbers list"
   (if (float-ok-p str)
       (values (read-from-string str))
       nil))

Or you could just leave it alone since multiple values are generally 
discarded unless you arrange for their use:
(defun parse-float (str)
   "Parse a string into a floating point numbers list"
   (if (float-ok-p str)
       (read-from-string str)
       nil))

I would modify FLOAT-OK-P this way:
(defun float-ok-p (str)
   (if (and (not (empty-string-p str))
            (every #'float-char-p str)
            (exp-ok-p str))
       (let ((parts (split-sequence-if #'exp-char-p
                                       str
                                       :remove-empty-subseqs t)))
         (and (decimal-ok-p (first parts))
              (or (null (second parts))
                  (integer-ok-p (second parts)))) )
       nil))
First, this is a predicate and should return an explicit value as noted 
above. Second, all of your initial conditions can be gathered together.

Third, apparently you have learned that COUNT-IF-NOT is deprecated. 
However, some very well-respected Lisp authorities, e.g., Kent Pitman, 
have gone on record that these IF-NOT functions should not be considered 
deprecated--it was a mistake. This would make your code clearer, for 
example:
(defun integer-ok-p (str)
   (when (not (empty-string-p str))
     (and (= 0 (count-if-not #'integer-char-p str))
          (sign-ok-p str))))

But as you see above I think using EVERY is even more straightforward.

I'm not familiar with SPLIT-SEQUENCE-IF, but it appears to return a list 
result. In this case it's probably clearer to use FIRST, REST, SECOND, 
etc... When you are dealing with CONSes that aren't lists then use CAR, 
CDR, CADR, etc... (And when you are dealing with a data structure that 
just happens to use a list for its implementation, e.g., a date: (year 
month day), don't use any of these functions! Define accessors: 
DATE-YEAR, ...)

One obvious problem with your approach is that you do a lot of work to 
validate a string, and then READ-FROM-STRING has to repeat much of that 
work itself. Furthermore, FLOAT-OK-P will reject something like " 1234 " 
(the whitespace characters don't satisfy FLOAT-CHAR-P). In general you 
don't know ahead of time whether or not your target string contains a 
valid number. Typically you'll want to process a stream of characters 
into a stream of recognized objects. This is called tokenization. Here's 
a little tokenizer I adapted from _Compilers: Principles, Techniques, 
and Tools_. It's not completely finished, but you can play with it like 
this:
* (tokenize "(1234 + 2134.48) /  -88 * 0.94e-17") ; Initialize

0
* (get-tokens)

(|(| 1234 + 2134.48 |)| / - 88 * 9.4e-18)

Notice that a leading "-" is treated as a subtraction operator. The 
states in FAIL represent starting states of various types of potential 
matches, e.g., (25) integer, (20) float without exponent, (12) full 
float notation. If a match fails, then the tokenizer backs up and tries 
other available matches or fails if there's nothing left to try. To be 
fair to you, I have to point out that this code just uses 
READ-FROM-STRING to construct a recognized object too! It could build 
numbers manually though.

Aloha,
David Sletten

(defconstant no-match 'no-match)

(let (index length input-string state start lexeme-start
       (eoe (code-char 0)))
   (defun tokenize (s)
     (setf index 0
           length (length s)
           input-string s
           state 0
           start 0
           lexeme-start 0))

   (defun get-tokens ()
     (loop for token = (next-token)
           until (eq token eoe)
           collect token))

   (defun fail ()
     (ecase start
       (0 (setf start 1))
       (1 (setf start 9))
       (9 (setf start 12))
       (12 (setf start 20))
       (20 (setf start 25))
       (25 (setf start no-match)))
     (setf index lexeme-start)
     start)

   (defun next-token ()
     (setf lexeme-start index
           start 0
           state 0)
     (let ((ch eoe))
       (loop
        (case state
          (0 (setf ch (get-next-char))
             (if (space-char-p ch)
                 (incf lexeme-start)
                 (setf state (fail))))
          (1 (setf ch (get-next-char))
             (if (delimiterp ch)
                 (return (intern (subseq input-string lexeme-start index)))
                 (setf state (fail))))
          (9 (setf ch (get-next-char))
             (if (alpha-char-p ch)
                 (setf state 10)
                 (setf state (fail))))
          (10 (setf ch (get-next-char))
              (if (alphanumericp ch)
                  (setf state 10)
                  (setf state 11)))
          (12 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 13)
                  (setf state (fail))))
          (13 (setf ch (get-next-char))
              (cond ((digit-char-p ch) (setf state 13))
                    ((char= ch #\.) (setf state 14))
                    ((char-equal ch #\e) (setf state 16))
                    (t (setf state (fail)))) )
          (14 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 15)
                  (setf state (fail))))
          (15 (setf ch (get-next-char))
              (cond ((digit-char-p ch) (setf state 15))
                    ((char-equal ch #\e) (setf state 16))
                    (t (setf state (fail)))) )
          (16 (setf ch (get-next-char))
              (cond ((digit-char-p ch) (setf state 18))
                    ((or (char= ch #\+) (char= ch #\-)) (setf state 17))
                    (t (setf state (fail)))) )
          (17 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 18)
                  (setf state (fail))))
          (18 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 18)
                  (setf state 19)))
          (20 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 21)
                  (setf state (fail))))
          (21 (setf ch (get-next-char))
              (cond ((digit-char-p ch) (setf state 21))
                    ((char= ch #\.) (setf state 22))
                    (t (setf state (fail)))) )
          (22 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 23)
                  (setf state (fail))))
          (23 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 23)
                  (setf state 24)))
          (25 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 26)
                  (setf state (fail))))
          (26 (setf ch (get-next-char))
              (if (digit-char-p ch)
                  (setf state 26)
                  (setf state 27)))
          ((11 19 24 27) (unless (eq ch eoe)
                        (retract 1))
           (return (read-from-string (subseq input-string lexeme-start 
index))))
          (otherwise (return eoe))))))

   (defun get-next-char ()
     (if (< index length)
         (prog1 (char input-string index) (incf index))
         eoe))

   (defun retract (i)
     (when (<= index length)
       (decf index i))))

(defun delimiterp (ch)
   (find ch "+-*/%^=()"))

(defun space-char-p (ch)
   (find ch '(#\Space #\Page #\Newline #\Return #\Tab #\Vt)))
From: Giorgos
Subject: Re: Request for comments for my parse-float
Date: 
Message-ID: <1144332219.737885@athnrd02>
David Sletten <·····@slytobias.com> writes:

[detailed and helpful comments snipped]

> I'm not familiar with SPLIT-SEQUENCE-IF, but it appears to return a
> list result. In this case it's probably clearer to use FIRST, REST,
> SECOND, etc... When you are dealing with CONSes that aren't lists
then
> use CAR, CDR, CADR, etc... (And when you are dealing with a data
> structure that just happens to use a list for its implementation,
> e.g., a date: (year month day), don't use any of these functions!
> Define accessors: DATE-YEAR, ...)
> 
> One obvious problem with your approach is that you do a lot of work
to
> validate a string, and then READ-FROM-STRING has to repeat much of
> that work itself. Furthermore, FLOAT-OK-P will reject something like
"
> 1234 " (the whitespace characters don't satisfy FLOAT-CHAR-P).

I suspect there is something here that I don't understand.  Isn't it
actually good style to reject something as " 1234 " as a valid float?

To clarify: I realized the need for a PARSE-FLOAT function when I
tried to read a text file which contains columns of floats, delimited
by whitespace, comma or some other character. So it seemed like a good
idea to read the file line by line into a list of strings, and then
use SPLIT-SEQUENCE-IF to further split each line into a list of
strings. SPLIT-SEQUENCE-IF takes a string and a delimiter, and returns
a list of strings, like that:

(split-sequence-if #'(lambda (c)
                       (char-equal c #\space)) "this is a string")
--> ("this" "is" "a" "string")

If I were sure that my data file would be properly formatted, I
could even feed the tokens directly into READ-FROM-STRING, but I
thought it would be better to do some error checking first.

So I thought whitespace should be rather treated as a delimiter. Is my
approach wrong in some respect?

> general you don't know ahead of time whether or not your target
string
> contains a valid number. Typically you'll want to process a stream
of
> characters into a stream of recognized objects. This is called
> tokenization. Here's a little tokenizer I adapted from _Compilers:
> Principles, Techniques, and Tools_. It's not completely finished,
but
> you can play with it like this:

I don't have much time today, but during the weekend I will certainly
try to understand your code. 

By the way, this book you mentioned looks interesting. I searched for
the title in Amazon. I have no formal training in computers, I am a
mechanical engineer and I have seriously programmed only numerical
stuff in fortran. Would you recommend this book to me? 

I have studied most of Practical Common Lisp, and I feel I am missing
knowledge about techniques commonly used for problem solving, e.g. for
parsing, data structures and data representation, whatever, I don't
know!  I think it is not just a coincidence that I did not realize
that I could have also used regular expressions, as Pascal Bourguignon
suggested in another post. In general, I would like to read some
theory, not necessarily specific algorithms. So, what other titles
would you (or anyone in this group) recommend?

My sincere thank you for your (and Pascal's) advice. 

Best regards,

-- Giorgos
From: Babar K. Zafar
Subject: Re: Request for comments for my parse-float
Date: 
Message-ID: <1144509378.664733.239320@i39g2000cwa.googlegroups.com>
Have look at:

Pragmatic Parsing in Common Lisp
http://home.pipeline.com/~hbaker1/Prag-Parse.html

Cheers,
Babar

Giorgos skrev:

> Hi all,
>
> I am new to lisp and, for fun and practice, I just wrote some code to
> parse floating-point numbers from a string, which I attached at the
> end of this post.
>
> To be more clear, the code does not do any parsing per se, it depends
> on read-from-string. The code essentially implements a series of
> tests, so that it ensures that only a properly formatted string is
> fed into read-from-string.
>
> I know that parsing floats has already been done and is available from
> some repository (CMU if I remember correctly), but as I said
> previously, this was intended for practice. Furthermore, the tests
> seem independently useful --- at least for me :-)
>
> I would be very happy if anyone would like to comment on the code.
> Since I am just a newbie, I would very much like to hear about the
> "lispiness" of the code, i.e. if I should write code like this or I
> am working against the language in some respect. But, of course, any
> comment is gladly welcome.
>
> Thank you for your attention.
>
> -- Giorgos
>
>
> Here is the code. It depends on split-sequence, which I found via this
> newsgroup and asdf-install.
>
>
> ;; GENERAL COMMENTS
> ;; I assume that a string representing a floating point number in the
> ;; format -12.34e-5 can be seen as a decimal ("-12.34") and an integer
> ;; ("-5"), split by a single character ("e"). Analogously, the decimal
> ;; can be seen as two integers ("-12" and "34") split by a single
> ;; character (".").
>
> ;; So I identify the basic rules of formatting for the sign, the
> ;; exponentiation characters and the point character (e.g. if there is
> ;; a sign character in an integer, it must come first), and I
> ;; implement the respective test. Then I write the tests for properly
> ;; formatted integers, decimals and floats.
>
> ;; Any string that is ok as a float, may safely go into
> ;; parse-from-string.
>
>
> ;;;---------------------------------------------------------------
> ;;; SOME HELPER FUNCTIONS
> ;;;---------------------------------------------------------------
> (defun empty-string-p (str)
>   "Tests if an object is a string and it is empty (zero length)"
>   (and (stringp str)
>        (= (length str) 0)))
>
> (defun char-p (seq)
>   "Character predicate constructor"
>   #'(lambda(c)
>       (find c seq)))
>
>
> ;;;---------------------------------------------------------------
> ;;; ESSENTIAL CHARACTER PREDICATES
> ;;;---------------------------------------------------------------
>
> (defun sign-char-p (c)
>   "Predicate for sign characters"
>   (or (char= #\+ c)
>       (char= #\- c)))
>
> (defun exp-char-p (c)
>   "Predicate for characters indicating exponentiation"
>   (funcall (char-p "eEdDlLsS") c))
>
> (defun point-char-p (c)
>   "Predicate for the decimal point"
>   (char= #\. c))
>
> (defun integer-char-p (c)
>   "Predicate for all characters that may be found
>    in integer numbers"
>   (or (digit-char-p c)
>       (sign-char-p c)))
>
> (defun decimal-char-p (c)
>   "Predicate for all characters that may be found
>    in decimal numbers"
>   (or (point-char-p c)
>       (integer-char-p c)))
>
> (defun float-char-p (c)
>   "Predicate for all characters that may be found
>    in floating point numbers"
>   (or (exp-char-p c)
>       (decimal-char-p c)))
>
>
> ;;;---------------------------------------------------------------
> ;;; PREDICATES FOR PROPER NUMBER FORMATTING
> ;;;---------------------------------------------------------------
> (defun sign-ok-p (str)
>   (when (not (empty-string-p str))
>     (let ((c (count-if #'sign-char-p str)))
>       (or (= c 0)
>           (and (= c 1)
>                (= 0 (position-if #'sign-char-p str))
>                (< c (length str)))))))
>
> (defun point-ok-p (str)
>   (when (not (empty-string-p str))
>     (let ((c (count-if #'point-char-p str)))
>       (or (= c 0)
>           (and (= c 1)
>                (< c (- (length str)
>                        (or (count-if #'sign-char-p str) 0))))))))
>
> (defun exp-ok-p (str)
>   (when (not (empty-string-p str))
>     (let ((c (count-if #'exp-char-p str))
>           (pe (position-if #'exp-char-p str))
>           (pp (or (position-if #'point-char-p str) 0)))
>       (or (= 0 c)
>           (and (= c 1)
>                (< 0 pe (- (length str) 1))
>                (< pp pe))))))
>
> (defun integer-ok-p (str)
>   (when (not (empty-string-p str))
>     (and (= 0 (count-if (complement #'integer-char-p) str))
>          (sign-ok-p str))))
>
> (defun decimal-ok-p (str)
>   (when (not (empty-string-p str))
>     (and (= 0 (count-if (complement #'decimal-char-p) str))
>          (point-ok-p str)
>          (sign-ok-p str))))
>
> (defun float-ok-p (str)
>   (when (not (empty-string-p str))
>     (and (= 0 (count-if (complement #'float-char-p) str))
>          (exp-ok-p str)
>          (let ((parts (split-sequence-if #'exp-char-p
>                                          str
>                                          :remove-empty-subseqs t)))
>            (and (decimal-ok-p (car parts))
>                 (or (null (cadr parts))
>                     (integer-ok-p (cadr parts))))))))
>
>
> ;;;---------------------------------------------------------------
> ;;; PARSE FLOATING POINT NUMBERS
> ;;;---------------------------------------------------------------
> (defun parse-float (str)
>   "Parse a string into a floating point numbers list"
>   (if (float-ok-p str)
>       (nth-value 0 (read-from-string str))
>       nil))
> 
> ;;; END