What my dog really hears

**REXX 151 148 141 bytes **

(Kinda seemed appropriate)

i=arg(1)
m=i
o=translate(m,'',xrange('A','z'),'*')
p=0
do forever
   p=pos('REX',m,p+1)
   if p=0 then leave
   o=overlay(substr(i,p,3),o,p)
end
say o

Try it here

Notes for non-REXXers:

  1. translate is a character replacement function (name comes from an assembler instruction on IBM MF). It searches string1 for the characters in string3. Every time it finds one it replaces it with the same positioned one in string2. If string2 is too short it is padded with the pad character.

See here for translate function

  1. overlay simply overlays the string1 on top of string2 at the specified position.

See here for overlay function


Retina, 24 21 bytes

i`(rex)|(\w)
$1$#2$**

Try it online!

Explanation

Skipping rexs is easiest by matching them as well, because matches can't overlap. So if we give priority to rex over other letters, those will be covered in a single match and not touched by the individual-letter matches.

But how do we do different things depending on the alternative that was used for the match? Unfortunately, Retina doesn't (yet) have any conditional substitution syntax like the Boost regex flavour. But we can fake it by including both substitutions in a single replacement and making sure that only one of them is non-empty:

  • $1 is the first capturing group, i.e. the (rex). If we did match rex this simply writes it back (so it does nothing), but if we didn't match rex then $1 is an empty string and vanishes.
  • $#2$** should be read as ($#2)$*(*). $#2 is the number of times group 2 was used, i.e. the (\w). If we matched rex this is 0, but if we matched any other individual letter, this is 1. $* repeats the next character as many times as its left-hand operand. So this part inserts a single * for individual-letter matches and nothing at all for rex.

JavaScript (ES6), 42 41 38 bytes

s=>s.replace(/rex|\w/gi,m=>m[1]?m:"*")

Try It

o.innerText=(f=

s=>s.replace(/rex|\w/gi,m=>m[1]?m:"*")

)(i.value="Rex, I told you not to do this! You're making me angry Rex!")
oninput=_=>o.innerText=f(i.value)
<input id=i><pre id=o>


Explanation

s=>            :Anonymous function that takes the string as an argument via parameter s.
s.replace(x,y) :Replace x in s with y.
/rex|\w/gi     :Case-insenstive regular expression that globally matches all occurrences
                of "rex" or any single letter, number or underscore.
                (numbers & underscores will never be included in the input.)
m=>            :Anonymous function that takes each match as an argument via parameter m.
m[1]?          :If string m has a second character, ...
                (Equivalent to m.charAt(1))
m              :Return m, ...
:"*"           :Otherwise return "*".