2
\$\begingroup\$

As an exercise, I've implemented a simple reader macro in Common Lisp (using SBCL). It converts Octal (unsigned integers only) into numbers.

Usage:

* #z1234
668

The code:

(defun oct-string-to-number 
 (string)
 "Converts an octal string to a number. Only digits from 0 - 7 are accepted; sign or decimal point symbols will cause oct-to-number to fail"
 (setq place 1)
 (setq result 0)
 (setq digits '(#0円 #1円 #2円 #3円 #4円 #5円 #6円 #7円))
 (loop for char across (reverse string)
 do 
 (setq pos (position char digits))
 (setq result (+ result (* pos place)))
 (setq place (* 8 place)))
 result)
(defun slurp-octal-digits 
 (stream)
 (setq string (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t))
 "Slurps all digits from 0 - 7 from a stream into a string, stopping at EOF, no data, or a non-digit character." 
 (setq digits '(#0円 #1円 #2円 #3円 #4円 #5円 #6円 #7円))
 (with-output-to-string (out)
 (loop do
 (setq char (read-char stream))
 (setq isnum nil)
 (if char
 (progn
 (setq isnum (find char digits))
 (if isnum
 (vector-push-extend char string)
 (unread-char char stream))))
 while (not (eq nil isnum))))
 string)
(defun octal-string-transformer 
 (stream subchar args)
 "Slurps an octal number from stream, and converts it to a number. Number must be an unsigned integer."
 (setq oct-string (slurp-octal-digits stream))
 (oct-string-to-number oct-string))
;; Sets #z to call octal-string-transformer, so e.g. #z1234 will evaluate to 668. Use #z as SBCL has #o already :-)
(set-dispatch-macro-character
 #\# #\z
 #'octal-string-transformer)

I'm quite new to Common Lisp, so I'd greatly appreciate feedback on everything: formatting, style, idiom, correctness :-)

Edited to add: Actually, the formatting in the pasted code snippet is rendered oddly; indentation that is present when I edit vanishes when I submit. So maybe be a bit gentle with feedback about the formatting ;-)

asked Apr 27, 2011 at 22:44
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

First of all a note about variables: You're using setq on previously undefined and undeclared variables. This is actually undefined behavior and in most implementations will create global dynamic/special variables. To create local variables (which you presumably intended) use let.

(defun oct-string-to-number 
 (string)
 "Converts an octal string to a number. Only digits from 0 - 7 are accepted; sign or decimal point symbols will cause oct-to-number to fail"

This whole function can be implemented as (parse-integer string :radix 8). However for the sake of learning let's go through the function anyway:

(setq digits '(#0円 #1円 #2円 #3円 #4円 #5円 #6円 #7円))
...
(setq pos (position char digits))

Iterating through the list of digits to convert a digit to an integer, is neither the most efficient nor the most succinct to do it. You can use the digit-char-p function which returns the digit's int value, if the given char is a digit and nil otherwise. (The same applies in slurp-octal-digits where you do the same thing).

(loop for char across (reverse string)
 do 
 (setq pos (position char digits))
 (setq result (+ result (* pos place)))
 (setq place (* 8 place)))

Instead of reversing the string and keeping track of a place variable, you can just iterate over the string from the beginning and multiply the result by 8 each step:

(loop for char across string
 do 
 (setq result (+ (* 8 result) (digit-char-p char))))

That being said, I'd rather use reduce then loop here (that is, if I weren't using parse-integer) - especially as higher order functions are one of the most useful things in lisp that a newcomer should get acquainted to. Using reduce the function would be written like this:

(defun oct-string-to-number (string)
 "Converts an octal string to a number. Only digits from 0 - 7 are accepted; sign, non-octal digit or decimal point will signal error."
 (reduce
 (lambda (sum digit)
 (+ (* 8 sum) (digit-char-p digit 8)))
 string :initial-value 0))
answered Apr 28, 2011 at 0:04
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thank you for being the anti-setq, pro-built-in police in my absence. \$\endgroup\$ Commented Apr 28, 2011 at 3:23
  • \$\begingroup\$ @Inaimathi: it's good to know about the built-in functionality, but I did this as a learning exercise; otherwise I'd have just used #o in the first place :-) \$\endgroup\$ Commented Apr 28, 2011 at 7:10

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.