Using reduce twice in a row for Run Length Encoding
Algorithm

Your algorith is quadratic for no good reason because last and length
are linear in list length.

I suggest that you add new elements to the beginning instead of the
end of the return value in compress-reduce and
nreverse
it in compress.

Style

Avoid nth 0 in favor of car.

(= (char-int last-char) (char-int curr)) is better written as (char= last-char curr).

Avoid mixing and and push: keep conditions in and and side-effects
(like push) in progn.

Repeated reduce and map

Each map
(and mapcar et al)
allocates a fresh list, so doing a repeated map can waste memory (and
garbage collection cycles), so either using
map-into
or an explicit function composition is a good idea: instead of

(mapcar #'foo (mapcar #'bar my-list))


use either

(let ((res (mapcar #'bar my-list)))
  (map-into res #'foo res))


or

(mapcar (lambda (x) (foo (bar x))) my-list)


However, no such allocation happens with reduce
so there is no reason to avoid nested reduces.

Note that the proverbial "sufficiently smart compiler" should be able to
handle these problems (but not necessarily the quadraticity above!), so
you should only worry about this if you discover it to be the
performance bottleneck.

Remember (SICP):


  ... a computer language is not just a way of getting a computer to
  perform operations, but rather ... it is a novel formal medium for
  expressing ideas about methodology