Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lisp: How to MAPCAR "#x" over a list of HEX?

Tags:

common-lisp

Using #x... like below one obtains the decimal of hex value

> #xB1 
177
> #xA5
165
> #xFF
255

Say we have a list of hex, what is the correct syntax using mapcar #x... over the list? Below doesn't work:

> (mapcar #'(lambda (hex) `(#x,hex)) '(B1 A5 FF))

Reader error: Malformed number in a #b/#o/#x/#r macro. [Condition of type SIMPLE-ERROR]

Thanks.

like image 961
user3639261 Avatar asked Aug 27 '14 03:08

user3639261


2 Answers

The #x is what's called a "reader macro". It is very similar to using quotations (ie "") to represent strings. They are executed when the code is read/compiled. What you actually want is a procedure which can convert from hexadecimal strings at run time. The procedure you are looking for is parse-integer, which takes a string and returns the value it represents. The mapcar with it should look something like this:

(mapcar (lambda (hex) 
           (parse-integer hex :radix 16))
        '("B1" "A5" "FF"))

Note that this is using strings, if you want to use symbols as in your suggestion you would have to do something like this:

(mapcar (lambda (hex) 
           (parse-integer (symbol-name hex) :radix 16))
        '(B1 A5 FF))

If you don't know the difference between a symbol and a string, I would suggest reading this: What exactly is a symbol in lisp/scheme?

like image 145
malisper Avatar answered Nov 10 '22 23:11

malisper


It occurs to me that while the best solution for this problem is probably one using parse-integer as mentioned in malisper's answer, there is a sense in which this could be solved with a mapping based approach.

When we write something like #xB1, we're not explicitly invoking a function. Instead, we're using the fact that # is a dispatching read macro character, and that there's a function installed for the subcharacter x that reads numbers written in hexadecimal. That means that by the time the evaluator or compiler gets a form, the number is already there. However, we do have access to the function that is doing the processing of the hexadecimal string, using get-dispatch-macro-character. Viz.:

CL-USER> (get-dispatch-macro-character #\# #\x)
#<FUNCTION SB-IMPL::SHARP-X>                    ; in SBCL

CL-USER> (get-dispatch-macro-character #\# #\x)
#<SYSTEM-FUNCTION SYSTEM::HEXADECIMAL-READER>   ; in CLISP

What can we do with that function? How would we use it?

2.1.4.4 Macro Characters

… If a character is a dispatching macro character C1, its reader macro function is a function supplied by the implementation. This function reads decimal digit characters until a non-digit C2 is read. If any digits were read, they are converted into a corresponding integer infix parameter P; otherwise, the infix parameter P is nil. The terminating non-digit C2 is a character (sometimes called a ``sub-character'' to emphasize its subordinate role in the dispatching) that is looked up in the dispatch table associated with the dispatching macro character C1. The reader macro function associated with the sub-character C2 is invoked with three arguments: the stream, the sub-character C2, and the infix parameter P. For more information about dispatch characters, see the function set-dispatch-macro-character.

That means that when we write something like #xB1, the function above is getting called with a stream from which it can read B1, the character x, and nil. We can try calling that function with arguments like that, although we can't be quite sure what will happen, because implementations might make different assumptions about where the function will be called from.

For instance, this works without a problem in CLISP, but SBCL assumes that the function should be called recursively from read (which we're not doing):

CL-USER> (funcall (get-dispatch-macro-character #\# #\x)
                  (make-string-input-stream "B1")
                  #\x 
                  nil)
177                     ; in CLISP

CL-USER> (funcall (get-dispatch-macro-character #\# #\x)
                  (make-string-input-stream "B1")
                  #\x
                  nil)
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "~A was invoked
; with RECURSIVE-P being true outside of a recursive read operation."
; {1005F245B3}>.  ; in SBCL

That said, for implementations where this will work, we can easily write a mapcar-like function to extract a dispatch macro character function and map it over some strings. Thus, in an implementation where this works:

(defun map-dispatch-macro-character (disp-char
                                     sub-char
                                     list
                                     &optional (readtable *readtable*))
  "Retrieve the dispatch macro character for DISP-CHAR and SUB-CHAR and
map it over the elements in LIST. Each element in LIST is either a
string designator or a two-element list of a string-designator and a
prefix argument."
  (flet ((to-list (x)
           (if (listp x) x
               (list x))))
    (let ((fn (get-dispatch-macro-character disp-char sub-char readtable)))
      (mapcar (lambda (x)
                (destructuring-bind (str &optional prefix) (to-list x)
                  (with-input-from-string (in (string str))
                    (funcall fn in sub-char prefix))))
              list))))

CL-USER> (map-dispatch-macro-character #\# #\x '(B1 "A5" (FF nil)))
(177 165 255)

And of course, if you really want to be able to write #x, you could of course define a version that just extracts the characters from a string of length two, so that you could do:

CL-USER> (map-dispatch-macro-character* "#x" '(B1 A5 FF))
(177 165 255)
like image 43
Joshua Taylor Avatar answered Nov 10 '22 21:11

Joshua Taylor