Is there a string to 32 bit integer coding system in elisp? - emacs

Is there a coding system such that (encode-coding-string msg '??? t) would convert my message into a list of 32 bit integers?
The binary coding system converts well the message to 8 bit data, and I am aware that I could post-process it to convert the result into 32 bits. I'm just wondering if there is already a coding system that does this... :) #lazy

Ok, attempt number two.
I wrote a test generator snippet in python:
def make_test_2():
with open('test2.bin', 'wb') as f:
args = [1867, 1982]
for a in args:
f.write((a).to_bytes(4, byteorder='little'))
This is a pretty hacky (a couple of reverse calls etc). But, its only meant to be a quick and dirty prototype.
(defun read-int-list2 (filename)
(let ((result '())
(accumulator '())
(accum-count 0)
(accum-max 4))
(with-temp-buffer
(set-buffer-multibyte nil)
(setq buffer-file-coding-system 'binary) ;; find a way to set temporarily? not sure
(insert-file-contents-literally filename)
(while (< (point) (point-max))
(if (< accum-count accum-max)
(progn
(setq accumulator (cons (aref (buffer-substring-no-properties (point) (1+ (point))) 0) accumulator))
(setq accum-count (1+ accum-count))))
(if (>= accum-count accum-max) ;; four bytes accumulated, lets bundle
(progn
(let* ((s (reverse accumulator))
(e1 (elt s 0))
(e2 (elt s 1))
(e3 (elt s 2))
(e4 (elt s 3))
(val (logior (lsh e4 24) (lsh e3 16) (lsh e2 8) e1))) ;; assume little endian (intel, ARM)
;; (message (format "%x %x %x %x -> %d" e1 e2 e3 e4 val))
(setq result (cons val result))
(setq accum-count 0)
(setq accumulator '()))))
(forward-char)))
(reverse result)))
(read-int-list2 "test2.bin") ;; (1867 1982)
I only did the one test. So that needs improvement. In words:
accumulate 8 byte chars from the special temp buffer (special because binary/literal load)
once the bytes per integer count has been reached, merge the accumulated bytes into an integer by bit shifting (be aware some machines are big endian, I assume here little endian) the bytes into place.
dump merged into result list
reset the accumulator
go to step 1
i have no doubt there are many improvements, my lisp is rusty.

Related

What does gensym do in Lisp?

contextualization: I've been doing a university project in which I have to write a parser for regular expressions and build the corresponding epsilon-NFA. I have to do this in Prolog and Lisp.
I don't know if questions like this are allowed, if not I apologize.
I heard some of my classmates talking about how they used the function gensym for that, I asked them what it did and even checked up online but I literally can't understand what this function does neither why or when is best to use it.
In particular, I'm more intrested in what it does in Lisp.
Thank you all.
GENSYM creates unique symbols. Each call creates a new symbol. The symbol usually has a name which includes a number, which is counted up. The name is also unique (the symbol itself is already unique) with a number, so that a human reader can identify different uninterned symbols in the source code.
CL-USER 39 > (gensym)
#:G1083
CL-USER 40 > (gensym)
#:G1084
CL-USER 41 > (gensym)
#:G1085
CL-USER 42 > (gensym)
#:G1086
gensym is often used in Lisp macros for code generation, when the macro needs to create new identifiers, which then don't clash with existing identifiers.
Example: we are going to double the result of a Lisp form and we are making sure that the Lisp form itself will be computed only once. We do that by saving the value in a local variable. The identifier for the local variable will be computed by gensym.
CL-USER 43 > (defmacro double-it (it)
(let ((new-identifier (gensym)))
`(let ((,new-identifier ,it))
(+ ,new-identifier ,new-identifier))))
DOUBLE-IT
CL-USER 44 > (macroexpand-1 '(double-it (cos 1.4)))
(LET ((#:G1091 (COS 1.4)))
(+ #:G1091 #:G1091))
T
CL-USER 45 > (double-it (cos 1.4))
0.33993432
a little clarification of the existing answers (as the op is not yet aware of the typical common lisp macros workflow):
consider the macro double-it, proposed by mr. Joswig. Why would we bother creating this whole bunch of let? when it can be simply:
(defmacro double-it (it)
`(+ ,it ,it))
and ok, it seems to be working:
CL-USER> (double-it 1)
;;=> 2
but look at this, we want to increment x and double it
CL-USER> (let ((x 1))
(double-it (incf x)))
;;=> 5
;; WHAT? it should be 4!
the reason can be seen in macro expansion:
(let ((x 1))
(+ (setq x (+ 1 x)) (setq x (+ 1 x))))
you see, as the macro doesn't evaluate form, just splices it into generated code, it leads to incf being executed twice.
the simple solution is to bind it somewhere, and then double the result:
(defmacro double-it (it)
`(let ((x ,it))
(+ x x)))
CL-USER> (let ((x 1))
(double-it (incf x)))
;;=> 4
;; NICE!
it seems to be ok now. really it expands like this:
(let ((x 1))
(let ((x (setq x (+ 1 x))))
(+ x x)))
ok, so what about the gensym thing?
let's say, you want to print some message, before doubling your value:
(defmacro double-it (it)
`(let* ((v "DOUBLING IT")
(val ,it))
(princ v)
(+ val val)))
CL-USER> (let ((x 1))
(double-it (incf x)))
;;=> DOUBLING IT
;;=> 4
;; still ok!
but what if you accidentally name value v instead of x:
CL-USER> (let ((v 1))
(double-it (incf v)))
;;Value of V in (+ 1 V) is "DOUBLING IT", not a NUMBER.
;; [Condition of type SIMPLE-TYPE-ERROR]
It throws this weird error! Look at the expansion:
(let ((v 1))
(let* ((v "DOUBLING IT") (val (setq v (+ 1 v))))
(princ v)
(+ val val)))
it shadows the v from the outer scope with string, and when you are trying to add 1, well it obviously can't. Too bad.
another example, say you want to call the function twice, and return 2 results as a list:
(defmacro two-funcalls (f v)
`(let ((x ,f))
(list (funcall x ,v) (funcall x ,v))))
CL-USER> (let ((y 10))
(two-funcalls (lambda (z) z) y))
;;=> (10 10)
;; OK
CL-USER> (let ((x 10))
(two-funcalls (lambda (z) z) x))
;; (#<FUNCTION (LAMBDA (Z)) {52D2D4AB}> #<FUNCTION (LAMBDA (Z)) {52D2D4AB}>)
;; NOT OK!
this class of bugs is very nasty, since you can't easily say what's happened.
What is the solution? Obviously not to name the value v inside macro. You need to generate some sophisticated name that no one would reproduce in their code, like my-super-unique-value-identifier-2019-12-27. This would probably save you, but still you can't really be sure. That's why gensym is there:
(defmacro two-funcalls (f v)
(let ((fname (gensym)))
`(let ((,fname ,f))
(list (funcall ,fname ,v) (funcall ,fname ,v)))))
expanding to:
(let ((y 10))
(let ((#:g654 (lambda (z) z)))
(list (funcall #:g654 y) (funcall #:g654 y))))
you just generate the var name for the generated code, it is guaranteed to be unique (meaning no two gensym calls would generate the same name for the runtime session),
(loop repeat 3 collect (gensym))
;;=> (#:G645 #:G646 #:G647)
it still can potentially be clashed with user var somehow, but everybody knows about the naming and doesn't call the var #:GXXXX, so you can consider it to be impossible. You can further secure it, adding prefix
(loop repeat 3 collect (gensym "MY_GUID"))
;;=> (#:MY_GUID651 #:MY_GUID652 #:MY_GUID653)
GENSYM will generate a new symbol at each call. It will be garanteed, that the symbol did not exist before it will be generated and that it will never be generated again. You may specify a symbols prefix, if you like:
CL-USER> (gensym)
#:G736
CL-USER> (gensym "SOMETHING")
#:SOMETHING737
The most common use of GENSYM is generating names for items to avoid name clashes in macro expansion.
Another common purpose is the generaton of symbols for the construction of graphs, if the only thing demand you have is to attach a property list to them, while the name of the node is not of interest.
I think, the task of NFA-generation could make good use of the second purpose.
This is a note to some of the other answers, which I think are fine. While gensym is the traditional way of making new symbols, in fact there is another way which works perfectly well and is often better I find: make-symbol:
make-symbol creates and returns a fresh, uninterned symbol whose name is the given name. The new-symbol is neither bound nor fbound and has a null property list.
So, the nice thing about make-symbol is it makes a symbol with the name you asked for, exactly, without any weird numerical suffix. This can be helpful when writing macros because it makes the macroexpansion more readable. Consider this simple list-collection macro:
(defmacro collecting (&body forms)
(let ((resultsn (make-symbol "RESULTS"))
(rtailn (make-symbol "RTAIL")))
`(let ((,resultsn '())
(,rtailn nil))
(flet ((collect (it)
(let ((new (list it)))
(if (null ,rtailn)
(setf ,resultsn new
,rtailn new)
(setf (cdr ,rtailn) new
,rtailn new)))
it))
,#forms
,resultsn))))
This needs two bindings which the body can't refer to, for the results, and the last cons of the results. It also introduces a function in a way which is intentionally 'unhygienic': inside collecting, collect means 'collect something'.
So now
> (collecting (collect 1) (collect 2) 3)
(1 2)
as we want, and we can look at the macroexpansion to see that the introduced bindings have names which make some kind of sense:
> (macroexpand '(collecting (collect 1)))
(let ((#:results 'nil) (#:rtail nil))
(flet ((collect (it)
(let ((new (list it)))
(if (null #:rtail)
(setf #:results new #:rtail new)
(setf (cdr #:rtail) new #:rtail new)))
it))
(collect 1)
#:results))
t
And we can persuade the Lisp printer to tell us that in fact all these uninterned symbols are the same:
> (let ((*print-circle* t))
(pprint (macroexpand '(collecting (collect 1)))))
(let ((#2=#:results 'nil) (#1=#:rtail nil))
(flet ((collect (it)
(let ((new (list it)))
(if (null #1#)
(setf #2# new #1# new)
(setf (cdr #1#) new #1# new)))
it))
(collect 1)
#2#))
So, for writing macros I generally find make-symbol more useful than gensym. For writing things where I just need a symbol as an object, such as naming a node in some structure, then gensym is probably more useful. Finally note that gensym can be implemented in terms of make-symbol:
(defun my-gensym (&optional (thing "G"))
;; I think this is GENSYM
(check-type thing (or string (integer 0)))
(let ((prefix (typecase thing
(string thing)
(t "G")))
(count (typecase thing
((integer 0) thing)
(t (prog1 *gensym-counter*
(incf *gensym-counter*))))))
(make-symbol (format nil "~A~D" prefix count))))
(This may be buggy.)

how to implement natural sort in common lisp?

I tried to implement a natural sort:
Break 21 [92]> (defparameter *sss* '("1.txt" "10.txt" "13.txt" "12.txt" "2.txt" "23.txt"))
*SSS*
Break 21 [92]> (sort *sss* #'string-lessp)
("1.txt" "10.txt" "12.txt" "13.txt" "2.txt" "23.txt")
Break 21 [92]>
Unfortunately, the code above does not work.
Could someone help me to get a natural sort function?
Here is a general string-natural-lessp:
(defun string-natural-lessp (string-a string-b
&key
(start-a 0)
(end-a (length string-a))
(start-b 0)
(end-b (length string-b)))
(do ((a-index start-a)
(b-index start-b))
((or (>= a-index end-a)
(>= b-index end-b))
(not (>= b-index end-b)))
(multiple-value-bind (a-int a-pos)
(parse-integer string-a
:start a-index
:junk-allowed t)
(multiple-value-bind (b-int b-pos)
(parse-integer string-b
:start b-index
:junk-allowed t)
(if (and a-int b-int)
(if (= a-int b-int)
(setf a-index a-pos
b-index b-pos)
(return-from string-natural-lessp (< a-int b-int)))
(if (char-equal (aref string-a a-index)
(aref string-b b-index))
(progn
(incf a-index)
(incf b-index))
(return-from string-natural-lessp
(char-lessp (aref string-a a-index)
(aref string-b b-index)))))))))
Depends on the use case, I guess. I'd try something like
(defun natural-compare (a b)
(labels ((int (str) (parse-integer str :junk-allowed t)))
(let ((n-a (int a))
(n-b (int b)))
(if (and n-a n-b (/= n-a n-b))
(<= n-a n-b)
(string<= a b)))))
(defun natural-sort (strings)
(sort (copy-list strings) #'natural-compare))
It works:
CL-USER> (defparameter *sss* '("1.txt" "test.txt" "36-test.txt" "36-taste.txt" "sicp.pdf" "answers.txt" "10.txt" "13.txt" "12.txt" "2.txt" "23.txt"))
*SSS*
CL-USER> (natural-sort *sss*)
("1.txt" "2.txt" "10.txt" "12.txt" "13.txt" "23.txt" "36-taste.txt"
"36-test.txt" "answers.txt" "sicp.pdf" "test.txt")
CL-USER>
but does a bit more work than it really needs to. Note that natural-sort copies the input list because sort is a destructive procedure.
Generate a proper sorting key for every element and then use those for comparison:
(defun skip-zeros (string offset length)
(do ((i offset (1+ i)))
((or (>= i length)
(not (eql (aref string i) #\0)))
i)))
(defun skip-digits (string offset length)
(do ((i offset (1+ i)))
((or (>= i length)
(not (digit-char-p (aref string i))))
i)))
(defun skip-alphas (string offset length)
(do ((i offset (1+ i)))
((or (>= i length)
(not (alpha-char-p (aref string i))))
i)))
(defun make-natural-sorting-key (string)
(let* ((length (length string))
(key (make-array (+ length 5)
:element-type 'character
:fill-pointer 0
:adjustable t))
(offset 0))
(do ()
((>= offset length) (coerce key 'simple-string))
(block eater
(let ((c (aref string offset))
(end))
(cond
((digit-char-p c) (setf offset (skip-zeros string offset length))
(setf end (skip-digits string offset length))
(do ((digits (- end offset) (- digits 9)))
((< digits 9) (vector-push-extend (digit-char digits) key))
(vector-push-extend #\9 key)))
((alpha-char-p c) (setf end (skip-alphas string offset length)))
(t (incf offset)
(return-from eater)))
(do ((i offset (1+ i)))
((>= i end))
(vector-push-extend (aref string i) key))
(vector-push-extend #\nul key)
(setf offset end))))))
(sort data #'string< :key #'make-natural-sorting-key)
Though, ensure that your sort implementation caches the keys.
Unfortunately, the code above does not work.
It looks like it worked. After all, you explicitly asked to sort by string comparison, and according to a string comparison, "2.txt" is between "13.txt", and "23.txt". If you want to sort numerically, you could use a key function that would read the number from the beginning of the string. Also, sort is destructive, so you shouldn't use it on literal data (like a quoted list).
At any rate, it's not too hard to cobble together something that will get you the sort of sorting that you're looking for. Here's a definition for a natural-string-lessp function:
(defun natural-string-lessp (a b)
(multiple-value-bind (ai aend)
(parse-integer a :junk-allowed t)
(multiple-value-bind (bi bend)
(parse-integer b :junk-allowed t)
(or (and ai
(or (not bi)
(and bi
(or (< ai bi)
(and (= ai bi)
(string-lessp a b :start1 aend :start2 bend))))))
(and (not ai)
(not bi)
(string-lessp a b))))))
It only handles the leading numbers, and not numbers in the middle of a string, so, e.g., "a-100-foo.txt" will still come before "a-3-foo.txt", but it might be sufficient for your needs. Here's an example of its use:
(let ((sss (copy-list '("1.txt" "10.txt" "13.txt" "12.txt"
"2.txt" "23.txt"))))
(sort sss #'natural-string-lessp))
;=> ("1.txt" "2.txt" "10.txt" "12.txt" "13.txt" "23.txt")
The documentation for parse-integer and the keyword arguments for string-lessp may be helpful.
A more robust implementation would figure out how to turn each string into a sequence of strings and numbers, (e.g., "12.txt" &rightarrow; (12 ".txt")) and then sort those lists lexicographically with an ordering among types (e.g., numbers before strings), and with an ordering within each type.

How do I find and insert the average of multiple lines in Emacs / Elisp?

I have a file that looks similar to:
AT 4
AT 5.6
AT 7.2
EG 6
EG 6
S 2
OP 3
OP 1.2
OP 40
and I want to compute the average (I've just made these averages up) for each of the titles and output something like:
AT 5.42
EG 6
S 2
OP 32.1
The file is in order, so all headings will be right under each other, but there are a varying amount of headings. eg. AT has three, but S only has one.
How would I sum together each of these lines, divide by the number of lines, and then replace all of the lines in emacs / elisp?
I decided to try to solve this question while still learning elisp myself. There is perhaps more efficient ways to solve this.
After defining the function, you'll want to set the region around the scores. (If the whole file, then M-<, C-SPC, M->) I figured this would be cleanest since your scores may be in the middle of other text. My function will compute the averages and then insert the answer at the end of the region.
(defun my/averages (beg end)
(interactive "r")
(let ((avgs (make-hash-table :test 'equal))
(answer "")
(curval nil)
(key nil)
(val nil))
; Process each line in region
(save-excursion
(goto-char beg)
(while (< (point) end)
; split line
(let ((split-line
(split-string
(buffer-substring-no-properties
(line-beginning-position) (line-end-position)))))
(setq
key (car split-line)
val (string-to-number (cadr split-line))
curval (gethash key avgs '(0 . 0)))
(puthash key (cons (+ (car curval) 1) (+ (cdr curval) val )) avgs))
; Advance to next line
(forward-line))
; Accumulate answer string
(maphash
(lambda (k v)
(setq answer
(concat answer "\n" k " "
(number-to-string (/ (cdr v) (car v))))))
avgs)
(end-of-line)
(insert answer))))
As a warning, I have zero error checking for lines that do not strictly meet your formatting.
You need libraries dash, s, f, and their functions -map, -sum, -group-by, s-split, f-read-text.
;; average
(defun avg (values)
(/ (-sum values) (length values)))
(-map (lambda (item)
(list (car item)
(avg (-map (lambda (x)
(string-to-number (cadr x)))
(cdr item)))))
(-group-by (lambda (item)
(car item))
(-map (lambda (line)
(s-split " " line t))
(s-split "[\n\r]"
(f-read-text "file.txt")
t))))
Presuming your file is called "file.txt", the code above returns (("AT" 5.6000000000000005) ("EG" 6) ("S" 2) ("OP" 14.733333333333334)).
After that you can convert that into text:
(s-join "\n"
(-map (lambda (item)
(s-join " "
(list (car item)
(number-to-string (cadr item)))))
This string you can write into file using f-write-text. Don't forget you can format ugly floating-point numbers like that:
(format "%.2f" 3.33333333) ; => "3.33"

Print a padded binary word in Racket

I want to print a padded to 32 bits word in binary in Racket. I know about printf and "~b", but I want it padded to be 32 bits long each time. How do I do this?
Example
(printf "~b" 42)
=> 101010
Want: 00000000000000000000000000101010
Here's a concise way to do it with Racket 5.3.1 and above:
Welcome to Racket v5.3.2.3.
-> (require racket/format)
-> (~r 42 #:base 2 #:min-width 32 #:pad-string "0")
"00000000000000000000000000101010"
See racket/format for more details.
In older Racket versions, you can do this:
Welcome to Racket v5.3.
-> (require srfi/13)
-> (string-pad (number->string 42 2) 32 #\0)
"00000000000000000000000000101010"
Well, I forced together a solution:
(define (print-word x)
(if (not (<= -2147483648 x 4294967295))
(error 'print-word "ERROR This number is bigger than a word ~a" x)
(let* ([positive-x (if (< x 0) (+ #x100000000 x)
x)]
[str (number->string positive-x 2)]
[padded-str (string-append
(make-string (- 32 (string-length str)) #\0)
str)])
(build-string 39
(λ(i) (cond [(= (remainder (+ 1 i) 5) 0) #\space]
[else (string-ref padded-str (- i (quotient i 5)))]))))))
This actually return the string with spaces between every 4 digits, as it was actually rather hard to read the other way.
Well here's a simple, inefficient way to do it:
(define (pad-left length padding the-str)
(if (> length (string-length the-str))
(pad-left length padding (string-append padding the-str))
the-str))
(write (pad-left 32 "0" (format "~b" 42)))

How do I convert a string of hex into ASCII using elisp?

Today I received a reply to one of my emails in the form of a string of hex bytes:
"686170707920333974682068617665206120676f6f64206f6e6521"
And I was thinking of the most efficient clean way to convert the string into it's ASCII equivalent. I'll add my answer to the question but I didn't feel it was as elegant as it could have been.
Here's an iterative solution
(defun decode-hex-string (hex-string)
(let ((res nil))
(dotimes (i (/ (length hex-string) 2) (apply #'concat (reverse res)))
(let ((hex-byte (substring hex-string (* 2 i) (* 2 (+ i 1)))))
(push (format "%c" (string-to-number hex-byte 16)) res)))))
And one using loop, if you're looking to avoid side-effect operations (you may need to (require 'cl) in order to use this one):
(defun decode-hex-string (hex-string)
(apply #'concat
(loop for i from 0 to (- (/ (length hex-string) 2) 1)
for hex-byte = (substring hex-string (* 2 i) (* 2 (+ i 1)))
collect (format "%c" (string-to-number hex-byte 16)))))
In general, it's best to avoid recursion in Elisp and Common Lisp; your stack is going to keel over with a big enough input, and neither language guarantees tail recursion (which you aren't using, but still). In Scheme, it's a different story.
Incidentally, Happy 39th.
For those that come here searching...
Elaborating a bit on Inaimathi's answer, here's the code to replace the selected region with the decoded hexa:
(defun decode-hex-string (hex-string)
(apply #'concat
(loop for i from 0 to (- (/ (length hex-string) 2) 1)
for hex-byte = (substring hex-string (* 2 i) (* 2 (+ i 1)))
collect (format "%c" (string-to-number hex-byte 16)))))
(defun hex-decode-region (start end)
"Decode a hex string in the selected region."
(interactive "r")
(save-excursion
(let* ((decoded-text
(decode-hex-string
(buffer-substring start end))))
(delete-region start end)
(insert decoded-text))))
(provide 'decode-hex-string)
(provide 'hex-decode-region)
Save that on a file and then M-x load-file. Or put on ~/emacs.d, or whatever. Then select the region with the hexa contents and M-x hex-decode-region. Enjoy!
If you use Magnar Sveen's dash.el list API (and you should), try:
(concat (--map (string-to-number (concat it) 16) (-partition 2 (string-to-list "686170707920333974682068617665206120676f6f64206f6e6521"))))
the solution uses Emacs functions string-to-number, string-to-list and concat, and dash.el functions -partition and anaphoric version of -map. What's good about concat is that it concatenates not only strings, but lists or vectors of characters too. We can rewrite this code using ->> threading macro. It takes the result of 1st argument, then applies it to 2nd, 3rd, etc arguments, just like Unix pipe.
(->> (string-to-list "686170707920333974682068617665206120676f6f64206f6e6521")
(-partition 2)
(--map (string-to-number (concat it) 16))
concat)
Building the answers provided by Inaimathi and
Shrein, I also added an encode function. Here is an implementation of both encode and decode, for both string and region arguments:
;; ASCII-HEX converion
(defun my/hex-decode-string (hex-string)
(let ((res nil))
(dotimes (i (/ (length hex-string) 2) (apply #'concat (reverse res)))
(let ((hex-byte (substring hex-string (* 2 i) (* 2 (+ i 1)))))
(push (format "%c" (string-to-number hex-byte 16)) res)))))
(defun my/hex-encode-string (ascii-string)
(let ((res nil))
(dotimes (i (length ascii-string) (apply #'concat (reverse res)))
(let ((ascii-char (substring ascii-string i (+ i 1))))
(push (format "%x" (string-to-char ascii-char)) res)))))
(defun my/hex-decode-region (start end)
"Decode a hex string in the selected region."
(interactive "r")
(save-excursion
(let* ((decoded-text
(my/hex-decode-string
(buffer-substring start end))))
(delete-region start end)
(insert decoded-text))))
(defun my/hex-encode-region (start end)
"Encode a hex string in the selected region."
(interactive "r")
(save-excursion
(let* ((encoded-text
(my/hex-encode-string
(buffer-substring start end))))
(delete-region start end)
(insert encoded-text))))
Here's mine. I'm not claiming this is particularly idiomatic or elegant, either. Maybe a bit old-skool.
(defun hex-string-decode (str)
"Decode STR of the form \"4153434949\" to corresponding \"ASCII\"."
(let (decoded sub)
(while (> (length str) 0)
(setq sub (substring str 0 2)
decoded (cons (string-to-number sub 16) decoded)
str (substring str 2) ) )
(when (not (zerop (length str))) (error "residue %s" str))
(mapconcat #'char-to-string (nreverse decoded) "") ) )
At first I didn't see a requirement that it must be Elisp, so I did it interactively and the code below follows my interactive procedure.
(defun decode-hex-string (hex-string)
(with-temp-buffer
(insert-char 32 (/ (length hex-string) 2))
(beginning-of-buffer)
(hexl-mode)
(hexl-insert-hex-string hex-string 1)
(hexl-mode-exit)
(buffer-string)))
This was the solution I came up with which struck me as a bit ugly:
(defun decode-hex-string(string)
"Decode a hex string into ASCII"
(let* ((hex-byte (substring string 0 2))
(rest (substring string 2))
(rest-as-string (if (> (length rest) 2)
(decode-hex-string rest)
"")))
(format "%c%s" (string-to-number hex-byte 16) rest-as-string)))