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 ;-)
1 Answer 1
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))
-
1\$\begingroup\$ Thank you for being the anti-
setq
, pro-built-in police in my absence. \$\endgroup\$Inaimathi– Inaimathi2011年04月28日 03:23:49 +00:00Commented 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\$Duncan Bayne– Duncan Bayne2011年04月28日 07:10:45 +00:00Commented Apr 28, 2011 at 7:10