kokko1G
kokko1G

Reputation: 163

Lisp: How to prompt-read a float?

I have a function where I am using parse-integer and prompt-read together. However, I need one of these integers to be a float. When I change parse-integer to parse-float it no longer works. Here is the function:

(defun prompt-for-cat ()                                                       
  (add-record                                                                  
    (make-cat                                                                    
      (prompt-read "Name")                                                        
      (prompt-read "Coloring")                                                    
      (or (parse-integer (prompt-read "Weight") :junk-allowed t) 0)               
      (or (parse-integer (prompt-read "Experience") :junk-allowed t) 0)           
      (or (parse-integer (prompt-read "Length") :junk-allowed t) 0))))  

This works as is, but I need that first integer, "Weight" to be a float. parse-float does not work and I cannot find the correct way to do this.

Upvotes: 0

Views: 594

Answers (2)

user5920214
user5920214

Reputation:

There are two approaches to doing this. One is to use read in some form, and the other one is to use a float-parsing library.

Using read

Using read is fraught with danger, to put it mildly. In particular you are open to code injection attacks unless you are careful. Never use read in code where you do not fully trust the input, unless you have, at least, wrapped some safety precautions around it.

That said, here is a function which tries to use read in a minimally safe way to read a float with a prompt.

(defun prompt-for-float (prompt &optional (default 0.0))
  ;; Use READ to read a float, in a way which should be at least
  ;; minimally safe.
  (with-standard-io-syntax
    (let ((*read-eval* nil))
      (format *query-io* "~A: " prompt)
      (finish-output *query-io*)
      (let ((result (read *query-io*)))
        (typecase result
          (float result)
          (real (coerce result 'float))
          (t default))))))

This at least tries to be safer by standardising the syntax, and turning off read-time evaluation, which is how code injection attacks happen.

However this may still not be completely safe. As well as possibly not being safe, in the sense of allowing the execution of uncontrolled code on the system, using read without a heavily-constrained readtable also is not side-effect free (it can intern symbols, for instance), and can allow various possible denial-of-service attacks, for instance by causing a large amount of memory allocation. To deal with those problems you either need to do a lot of work cutting the readtable down to something which is safe, or use a library which just reads the type or types you care about.

Using a float-parsing library

There is a library called parse-float which solves this problem for you. This library is available via Quicklisp. Assuming you have Quicklisp installed (which you should have: if not, then your first problem is arranging for that to be true), then using is it is as simple as saying (ql:quickload "parse-float"), and then parsing the float with, for instance:

> (parse-float:parse-float "12.2")
12.2
4

Upvotes: 4

Leo
Leo

Reputation: 1934

(let ((weight (progn
                (format t "Weight: ")
                (read t))))
  (if (floatp weight) weight 0))

Upvotes: 1

Related Questions