# Simple octal reader macro in Common Lisp

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 isnum nil)
(if char
(progn
(setq isnum (find char digits))
(if isnum
(vector-push-extend char string)
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 ;-)

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))

• Thank you for being the anti-setq, pro-built-in police in my absence. – Inaimathi Apr 28 '11 at 3:23
• @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 :-) – Duncan Bayne Apr 28 '11 at 7:10