text:
Visualize the Euclidean algorithm again
Task
Given two positive integers:

Draw the rectangle with dimensions specified by the two integers.
Repeat Step 3 until there is no more space.
Draw and fill the largest square touching three sides of the (remaining) rectangle.
Output the resulting rectangle.

Example
For example, our input is 6 and 10.
We draw the hollow rectangle of size 6 x 10:
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxx

After repeatedly filling squares, this is what we would obtain:
aaaaaabbbb
aaaaaabbbb
aaaaaabbbb
aaaaaabbbb
aaaaaaccdd
aaaaaaccdd

There are 4 squares here (a, b, c, d), each with side length 6, 4, 2, 2 respectively.
Rules and freedom

You must use a different letter for each square.
You can choose which letters to support, as long as the letters supported are all printable characters and there are at least 10 characters supported.
In each iteration of Step 3 above, you have two choices (except in the last iteration, where you only have one choice). Both choices are valid.
The number of squares required will not exceed the number of letters you support.
You can fill in the squares with the letters you support in any order.

Testcases
Input: 6, 10
Output:
aaaaaabbbb
aaaaaabbbb
aaaaaabbbb
aaaaaabbbb
aaaaaaccdd
aaaaaaccdd

or
aaaaaaccdd
aaaaaaccdd
aaaaaabbbb
aaaaaabbbb
aaaaaabbbb
aaaaaabbbb

or
bbbbaaaaaa
bbbbaaaaaa
bbbbaaaaaa
bbbbaaaaaa
ccddaaaaaa
ccddaaaaaa

or
ccddaaaaaa
ccddaaaaaa
bbbbaaaaaa
bbbbaaaaaa
bbbbaaaaaa
bbbbaaaaaa

or
ddddddaaaa
ddddddaaaa
ddddddaaaa
ddddddaaaa
ddddddbbcc
ddddddbbcc

Input: 1,1
Output:
a

Input: 1,10
Output:
abcdefghij

Input: 10,1
Output:
a
b
c
d
e
f
g
h
i
j

Note that there are more possibilities than I can include for the testcases above.
Scoring
This is code-golf. Shortest answer in bytes wins.
Standard loopholes apply.

Related.

Haskell, 90 bytes
(['a'..]!)
a?x=a<$[1..x]
((a:b)!y)x|x*y<1=""?y|x>y=map(a?y++)$b!y$x-y|z<-y-x=a?x?x++(b!z)x

Try it online!

Charcoal, 30 bytes
ＮδＮγＦβ¿×γδ«ＵＯγδι¿‹γδＡ⁻δγδＡ⁻γδγ

Try it online! Explanation:
Ｎδ      Input d
Ｎγ      Input g
Ｆβ      For i In ['a' ... 'z']
 ¿×γδ«   If g * d
  ＵＯγδι   Oblong g, d, i
  ¿‹γδ    If g < d
   Ａ⁻δγδ   d = d - g
   Ａ⁻γδγ   Else g = g - d

Annoyingly Charcoal's Oblong command won't take 0 for a dimension, which costs me 4 bytes. The other approach would be to loop while g * d, but then I couldn't work out how to iterate over b (which is predefined to the lowercase letters).

Oops, sorry, that was a conscious design decision, do you think negative inputs should be allowed as well?

@ASCII-only What's the current behaviour (both for 0 and negative)? My best idea would be that negative would draw to the left/top instead of right/bottom. (Also, if I use Ｗ×γδ, how do I print a different letter each time?)

@Neil wow, I see what you mean that WOULD be annoying.

Uiua, 31 bytes 32 bytes
Edit: The recur ↬ glyph has been removed from the language
|2+@`;⍥(⎋=0⧻.⊙⬚0+.↘⇌×⍖.△.)∞.↯∶1⊂

Try it online!
Explanation:
                                  ⊂   # Put the dimensions into an array
                               ↯∶1    # Make a matrix of 1s with the dimensions
                              .       # Copy the result
      ⍥(                   )∞        # Repeat forever
                          △.          # Get the shape of the latest matrix
                       ⍖.            # Get the descending indices
                      ×              # Multiply these with the shape
                                     # (zeros out the larger dimension)
                     ⇌               # Reverse the array
                    ↘                # Remove the largest square from the latest matrix
              ⊙⬚0+.                 # Add the new matrix to the original
                                     # (using 0s where the shape doesn't match)
         ⎋=0⧻.                       # Break if the length of the new matrix is 0
  +@`;                               # Add char ` to the result to get 1=a, b=2, etc.
|2                                   # Signature of function

Example:
Input: 4 2   Stack (top on left):
             # 4 2
⊂            # [4 2]
↯∶1          # ╭─
             # ╷ 1 1
             #   1 1
             #   1 1
             #   1 1
             #       ╯
.            # ╭─      ╭─
             # ╷ 1 1   ╷ 1 1
             #   1 1     1 1
             #   1 1     1 1
             #   1 1     1 1
             #       ╯       ╯
⍥( )∞
△.           # [4 2] ╭─      ╭─
             #       ╷ 1 1   ╷ 1 1
             #         1 1     1 1
             #         1 1     1 1
             #         1 1     1 1
             #             ╯       ╯
⍖.          # [0 1] [4 2] ╭─      ╭─
             #             ╷ 1 1   ╷ 1 1
             #               1 1     1 1
             #               1 1     1 1
             #               1 1     1 1
             #                   ╯       ╯
×            # [0 2] ╭─      ╭─
             #       ╷ 1 1   ╷ 1 1
             #         1 1     1 1
             #         1 1     1 1
             #         1 1     1 1
             #             ╯       ╯
⇌            # [2 0] ╭─      ╭─
             #       ╷ 1 1   ╷ 1 1
             #         1 1     1 1
             #         1 1     1 1
             #         1 1     1 1
             #             ╯       ╯
↘            # ╭─      ╭─
             # ╷ 1 1   ╷ 1 1
             #   1 1     1 1
             #       ╯   1 1
             #           1 1
             #               ╯
⊙⬚0+.       # ╭─      ╭─
             # ╷ 1 1   ╷ 2 2
             #   1 1     2 2
             #       ╯   1 1
             #           1 1
             #               ╯
⧻.           # 2 ╭─      ╭─
             #   ╷ 1 1   ╷ 2 2
             #     1 1     2 2
             #         ╯   1 1
             #             1 1
             #                 ╯
⎋=0          # No break
△.           # [2 2] ╭─      ╭─
             #       ╷ 1 1   ╷ 2 2
             #         1 1     2 2
             #             ╯   1 1
             #                 1 1
             #                     ╯
⍖.          # [0 1] [2 2] ╭─      ╭─
             #             ╷ 1 1   ╷ 2 2
             #               1 1     2 2
             #                   ╯   1 1
             #                       1 1
             #                           ╯
×            # [0 2] ╭─      ╭─
             #       ╷ 1 1   ╷ 2 2
             #         1 1     2 2
             #             ╯   1 1
             #                 1 1
             #                     ╯
⇌           # [2 0] ╭─      ╭─
             #       ╷ 1 1   ╷ 2 2
             #         1 1     2 2
             #             ╯   1 1
             #                 1 1
             #                     ╯
↘            # [] ╭─
             #    ╷ 2 2
             #      2 2
             #      1 1
             #      1 1
             #          ╯
⊙⬚0+.       # [] ╭─
             #    ╷ 2 2
             #      2 2
             #      1 1
             #      1 1
             #          ╯
⧻.           # 0 [] ╭─
             #      ╷ 2 2
             #        2 2
             #        1 1
             #        1 1
             #            ╯
⎋=0          # Break
+@`;         # ╭─
             # ╷ "bb"
             #   "bb"
             #   "aa"
             #   "aa"
             #        ╯

Previous solution:
+@`!(|1 ⬚0+↬>0/+△.↘⇌×⍖.△..)↯∶1⊂

Welcome to Code Golf, and nice first answer!

Pyth, 34 bytes
M?*GH?<HGCgHG+*G]jk*G]~hZgG-HGYjgF

Try it online!

Haskell, 181 bytes
import Data.List
(['!'..'~']&)
a#[]=a
a#b=zipWith(++)a$transpose b
(s&a)b|b<1=[]|b>a=transpose$s&b$a|n<-div a b,(t,u)<-splitAt n s=foldl1(#)((<$[1..b]).(<$[1..b])<$>t)#(u&b$mod a b)

Try it online!
For 10 bytes more you get a nice spiral instead :)
!!!!!!!!!!!!!$$$#####
!!!!!!!!!!!!!$$$#####
!!!!!!!!!!!!!$$$#####
!!!!!!!!!!!!!%%'#####
!!!!!!!!!!!!!%%&#####
!!!!!!!!!!!!!""""""""
!!!!!!!!!!!!!""""""""
!!!!!!!!!!!!!""""""""
!!!!!!!!!!!!!""""""""
!!!!!!!!!!!!!""""""""
!!!!!!!!!!!!!""""""""
!!!!!!!!!!!!!""""""""
!!!!!!!!!!!!!""""""""

Try it online!
Ungolfed
The (#) operator puts two matrices next to each other, but transposes the right one, eg:
!!!                !!!"
!!! # "#$    ->    !!!#
!!!                !!!$

a # [] = a
a # b  = zipWith (++) a $ transpose b

This is basically the recursive version of Euclid's algorithm, but instead of forgetting the divisors&remainders and returning the gcd, it builds squares from it and accumulates these with (#). The s variable are the remaining characters that we can use:
(s & a) b
  | b == 0 = []                     -- Base case
  | b > a = transpose $ (s & b) a   -- In this case we can just flip the arguments and rotate the result by 90 degrees
  | n <- div a b                    -- set n to the number of squares we need
  , (t,u) <- splitAt n s =          -- take n characters, ..
               ((<$[1..b]).(<$[1..b]) <$> t)                     -- .. build squares from them and ..
    foldl1 (#)                                                   -- put them next to each other
                                             (u & b $ mod a b)   -- recursively build the smaller squares with the remaining characters..
                                            #                    -- .. flip them and put them next to the previous one(s)

The actual function just calls the function from above with a string of all printable characters:
(['!'..'~']&)

You need to count import Data.List to use transpose.

I did but it's (to my knowledge) not possible to do that import when I use a pointfree function. But I included it in the byte count, please see the TIO where the byte count is actually 164..

Oh. You could play wacky preprocessor games, but at some point it makes more sense to simply edit the code in your post manually after copying from TIO.

Jelly, 32 bytes
Ṁ,ạ/y
³,⁴ÇÐĿp/€Fs2
pµ¢ṣLµ€+95ỌsY

Try it online!
Ṁ,ạ/y you want an explanation? Here it is.
Ṁ,ạ/y          - perform one step of the Euclidean Algorithm, input 2-element list
 ,             - pair of the following two:
Ṁ              -  maximum of the the input list
  ạ/           -  absolute difference of the two elements
    y          - use this as a mapping on the input.

³,⁴ÇÐĿp/€Fs2   - apply Euclidean Algorithm
³,⁴            - start with the pair [input 1, input 2]
   Ç           - apply a step of the Euclidean Algorithm
    ÐĿ         - repetitively until the results repeat
      p/€      - take the Cartesian product of each step
         Fs2   - flatten and split into all coordinate pairs of letters

pµ¢ṣLµ€+95ỌsY
p              - Cartesian product of inputs: provides all possible coordinate pairs.
 µ   µ€       - for each coordinate
   ṣL         - find the number of times it is included in
  ¢           - the above list of covered coordinates.
       +95Ọ   - convert number of times to letters
           s  - split into rows
            Y - join by newlines.

I can likely golf a little more by using implicit arguments instead of ³,⁴.

J, 78 bytes
>/|:[:u:97+1(0&g@{:]F.:(((g=.$~,~)~1+0{,),(~:#)|:])}:)@}.0}:@{0|:(<.,|@-)/^:a:

Attempt This Online!
Surprisingly tough to golf in J.  Tried a few approaches, this was the best of them, but still feels like there's room for improvement.
Basic approach:

Generate successive differences until a repeat, eg: 10 6 gives 6 4 2 2.
"Roll it up" starting from the smaller squares (ie, stack a 2x2 on a 2x2, then transpose it and put a 4x4 on that, then transpose that and put a 6x6x on top of it).  We do transposes as needed to make the side lengths line up.  We always stack our new larger square atop the result up to that point.  The whole computation is expressed as a fold.
The first square starts as a square of zeroes, and as we add each additional square its number increments by one.  At the end we have our result but with numbers instead of letters.
Convert the numbers to ascii letters.
Finally, transpose the entire result if needed, so that the result dimensions match the order of the arguments.

J, 30 bytes
[:u:97+0]F.:({.>:)(-/:*|.)^:a:

Attempt This Online!
Saw ... F.: ... ^:a: in Jonah's answer and thought it might work better, and it actually did. Remove the leading [:u:97+ to get a numeric matrix.
[:u:97+0]F.:({.>:)(-/:*|.)^:a:    input: a vector of dimensions
                  (      )^:a:    repeat until convergence and collect iterations:
                   -/:*|.         subtract smaller from larger
                                  (it stops when one side becomes zero)
                      [10,4] becomes [[10,4],[6,4],[2,4],[2,2],[2,0]]
       0]F.:({.>:)    construct the numeric matrix:
       0]F.:(    )    fold from the right, starting with single 0,
             {.>:     increment the input matrix and pad with zeros
                      to the size of the next larger dimensions
[:u:97+    map 0, 1, ... to a, b, ...

J, 37 bytes
[:u:97+f=:$&0`({.1+[:f]-/:*|.)@.(~:/)

Attempt This Online!
Recursive function seems to be the way here. Takes a vector of two positive integers and returns a character matrix. If a numeric matrix is acceptable, 28 bytes: $&0`({.1+[:$:]-/:*|.)@.(~:/).
[:u:97+f=:$&0`({.1+[:f]-/:*|.)@.(~:/)
       f=:$&0`({.1+[:f]-/:*|.)@.(~:/)    inner recursive function
             `                @.(~:/)    if two parts are equal,
          $&0                            create a zero matrix of that dimensions
              (              )      otherwise,
                      ]-/:*|.       subtract the smaller part from the larger part
                   [:f              recurse
               {.1+                 add 1 to each element and pad to the requested size
                                    with zeros
[:u:97+    map 0, 1, ... to a, b, ...

I was hoping you would improve my answer, and this is just beyond beautiful.  Seems obvious in hindsight but I think this insight is brilliant.  Going to reward this with a bounty.

JavaScript (V8), 111 bytes
x=>h=(y,i=x,j=y)=>j?(g=(x,y,k)=>(x>y?x-=y:y-=x,i)?x<i|y<j?k:g(x,y,k+1):`
`)(x,y,0)+(i?h(y,i-1,j):h(y,x,j-1)):''

Try it online!


text:
What my dog really hears
My dog is called Rex. Every time I scold him, he seems not very impressed and the only times I see him react is when I pronounce his name. If I say
Rex, I told you not to do this! You're making me angry Rex!
all he hears is
Rex, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!
The challenge : given an input string, your program must output the same string where all alphabetic characters have been turned to stars, except the characters in the appearances of the word Rex, who must be left untouched. The non-alphabetic characters are also left untouched.
Details : The challenge is case-insensitive, thus rex must be left untouched also. The word Rex can be part of another word, so for example anorexic must be rendered as ***rex**.
Update : as the initial text of this challenge did not clarify how underscores or numbers or accentuated characters should be treated, I do not make any special requirement on those characters. Thus, a solution is valid as long as characters in a-zA-Z (and also the one mentioned in the examples, ,!".) are handled correctly.
Test cases :
Input : Rex lives in Rexland.
Output : Rex ***** ** Rex****.
Input : To call Rex, just say "Rex".
Output : ** **** Rex, **** *** "Rex".
Input : My cat is getting anorexic.
Output : ** *** ** ******* ***rex**.

Will the string contain newlines?

Test case: Hello! Isn't this ... a nice challenge? Can you dog hear *****! ***'* *** ... * **** *********?? If so, you might consider communicating in morse from now on...

Do we only need to support a-zA-Z, or also 0-9 and/or äëïöüÿãõñáéíóúýàèìòùç etc. etc.? And could you add some test cases regarding these?

@KevinCruijssen Since $0-9$ or accented characters are not mentioned in the initial challenge, do not consider them as part of the challenge.

@Shaggy I think we can assume standard ASCII, but replace digits or not is a good question.

@Shaggy The non-alphabetic characters are also left untouched. Alphabetic characters don't include digits.

Can I have an additional new line character at the end?

Will there be newlines in the middle of the string?

Since "dogs don't get the difference between lowercase and uppercase", may the output case be different to the input? (For example: input="Rex lives in Rexland.", output="rex ***** ** rex****."; or alternatively input="My cat is getting anorexic.", output="** *** ** ******* ***Rex**.")

I once had a dog that could spell.

@Joshua Really ?

@EwanDelanoy: Yeah really. We're pretty sure it came from spelling words we didn't want him to overhear. Near the end we were pretty sure he could spell any word he knew, which was quite a few.

Does Rex not respond to [Dog park, walk, treat]? When my dog was shaking I made a joke about him having "Dog parkinsons"  and he definitely only heard "Dog park******"

@Adám Indeed. Sorry for the mistake, I just corrected it

@EwanDelanoy You may want to wait more than two days before accepting an answer, for two reasons: Challenges with no accepted submission may be less attractive, and you are likely to have to reaccept (which could cause bad feelings). I generally wait a week or so, except if there is a 0-2 byte solution.

@Adám Thanks you for your advice. I don't have much experience for participating in this forum. At least my motives for reaccepting are crystal clear (number of bytes).

Ewan, we still need an answer to @Adám's question about underscores.

@Adám Yes, I wasn't sure about the best way to fix the challenge and had forgotten about my earlier comment. Hope it's all OK now

@EwanDelanoy Yes, thank you.

Thanks, @Adám & Ewan; that saves me 3 bytes :)

Why did this challenge use asterisks instead of "blah"s???

Idea possibly from Far Side.

This answer is shorter than my accepted one.

My new answer is shorter than the newly accepted one.

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.

Doesn't retina have \a for [a-z] z.B.?

@LeakyNun no. I'd have to tokenise the regex (or even implement my own flavour) to add any features to the regex flavour itself.

**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:

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

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

See here for overlay function

... but the dog thought he programmed it in REX*.

How many of these upvotes are purely for the choice of language?! :D

@Shaggy At least all of them

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 "*".

See https://codegolf.stackexchange.com/questions/119718/what-my-dog-really-hears/119746#comment293303_119718

nice solution!.

APL (Dyalog Unicode), 22 bytesSBCS
'rex' '\w'⎕R'\0' '*'⍠1

Try it online!
Simple PCRE Replace.
⍠1 sets case insensitivity. Simply replaces rex with itself and all other word characters with asterisks.

\w includes the underscore character, assuming this is RegEx - I don't know APL.

@Shaggy Yeah, Dyalog APL uses PCRE, but it is not certain from OP that underscores will occur. E.g. digits will not.

Do we not have to assume they can occur, unless told otherwise?

@Shaggy Normally, yes, but this seems to indicate that what isn't mentioned will not occur. OP only mentions comma, period, space, and exclamation point.

Hmm ... I think I'll wait for confirmation that they won't occur, seeing as I've left that comment about \w on a few answers now!

Perl 5, 24 bytes
23 bytes of code + -p flag.
I used Martin Ender's regex from his Retina answer (which happens to be shorter in Perl, thanks to \pl), and only had to adapt the right side of the s///.
s%(rex)|\pl%$1//"*"%gie

Try it online!

Retina, 32 31 bytes
iS`(rex)
%iT`Ll`*`^(?!rex).*
¶

Try it online! Explanation: Splits the string into occurrences of the word rex and everything else, but keeps the matches. Then, on lines that don't begin rex (i.e. the "everything else"), change letters to *s. Finally, join everything back together.

This is at least the second time that I'm thinking transliteration mode could use an option to transliterate the non-matches...

@MartinEnder too late :-D

C, 99 97 92 86 74 73 72 65 bytes
f(char*s){*s&&f(s+=strnicmp("Rex",s,3)?!isalpha(*s)||(*s=42):3);}

Pelles IDE environment provides (compile with /Go) the function strnicmp. This function is identical to strncasecmp. See it work here (with the replacement function).
The output is stored in the first parameter which is an in/out parameter.
Thanks to Johan du Toit for letting me know that recursion is slightly shorter.

There exists a C environment that provided strncmpi so you can get it in 69. I've got it on CD.

@Joshua Thanks. What is CD?

Borland C++ 4.5

Ruby, 36 35 32 bytes
->s{s.gsub(/(rex)|\w/i){$1||?*}}

As a test:
f=->s{s.gsub(/(rex)|\w/i){$1||?*}}

tests = [
  ["Rex, I told you not to do this! You're making me angry Rex!", "Rex, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!"],
  ["Rex lives in Rexland.", "Rex ***** ** Rex****."],
  ["To call Rex, just say \"Rex\".", %q(** **** Rex, **** *** "Rex".)],
  ["My cat is getting anorexic.", "** *** ** ******* ***rex**."]
]

tests.each do |input, output|
  if f.call(input) == output
    puts "Fine for #{input.inspect}"
  else
    puts "Problem with :\n#{input.inspect}"
    puts f.call(input)
    puts output
  end
  puts
end

It outputs:
Fine for "Rex, I told you not to do this! You're making me angry Rex!"

Fine for "Rex lives in Rexland."

Fine for "To call Rex, just say \"Rex\"."

Fine for "My cat is getting anorexic."

PHP, 78 Bytes
<?=preg_replace("#[a-df-qs-wyz]|r(?!ex)|(?<!r)e|e(?!x)|(?<!re)x#i","*",$argn);

Try it online!
PHP, 84 Bytes
<?=preg_replace_callback("#(rex)|\pL#i",function($t){return$t[1]?$t[1]:"*";},$argn);

-1 Byte \w instead \pl in this case underscore and numbers are replaced too
\pL is shorter as [a-z] or [[:alpha:]]
Try it online!

You can use \w instead of \pL.

@Adám Thank you it makes my longer approach only 1 Byte shorter and not really clearer what should make in case of underscore or number

The input will never have any underscores or numbers..

@Adám I have make an extra point for it and have edit the longer approach why I have found an other improvement

C (GCC on POSIX), 167 118 93 87 bytes
i,j;f(char*s){for(i=0;j=s[i];i++)strncasecmp("Rex",s+i,3)?s[i]=isalpha(j)?42:j:(i+=2);}

Try it online!

f(char*s){for(;*s;s++)strncasecmp("Rex",s,3)?putchar(isalpha(*s)?42:*s):write(1,s-2,3,s+=2);}. What kind of sorcery is f(s)char*s;{}? I've never seen this syntax before.

Oh, it used to have another parameter but i forgot to remove that.

Doesn't work correctly, because only the first call to the function works. See here: https://tio.run/nexus/c-gcc#jVHLTsMwELznK0Y52SSCKsClIeoXcEHc0ghZebQbpU4UO1VLlG8PtlVokaCwB2t3xzO7Hs8U1nHF8q3obxQfq7ZncZ2olLKYgoAr3ctcqDLfdcx/KQ9@qAIK7/nK3khIiabbClbz1UO0rJeMgiTi8TST1NgJkgz7lgpwb/Rgwk5BIbRIszR6XGRIMMIhv8QIOxQN7UsFkjBFI2Rx62NC@AfxtUUumsZyQtSD0lDiiLUVXPv/Ung@GgUNUtiUWpPcQMi2Lw@UX6dPiB3kDmuFffJb3g4mTaDovWwr5prguPvWSI0p/JJuP8RpkCyMEwkWiE/506XuqRkExmy3/tdyn8oOP6vb6Aatfkan8wbeNH8A Solutions in function form, such as this one, must be able to be called again and produce correct results.

@2501 thanks, fixed.

Java 8, 187 192 168 164 159 138 bytes
s->{for(int i=0;i<s.length();System.out.print(s.regionMatches(0<1,i,"rex",0,3)?s.substring(i,i+=3):s.replaceAll("\\w","*").charAt(i++)));}

-28 bytes thanks to @OlivierGrégoire.
Explanation:
Try it online.
s->{                         // Method with String parameter and no return-type
  for(int i=0;i<s.length();  //  Loop over the characters of the input-String
    System.out.print         //   Print:
     s.regionMatches(1>0,i,"rex",0,3)?
                             //    If we've found "rex" (case-insensitive):
      s.substring(i,i+=3)    //     Print this REX-word (case-sensitive)
     :                       //    Else:
      s.replaceAll("\\w","*").charAt(i++));
                             //     Print the current character,
                             //     or '*' if it's an alpha-numeric character

@Shaggy Should be fixed now. This was posted before it was specified in the comments that 0-9 and accented characters shouldn't be included, only a-zA-Z should.

Could you replace "[a-zA-z]" with /[a-z]/i?

@Shaggy Yes/no. Java uses a slightly different regex syntax than python or C# for example. So yes, it is possible to use a case-insensitive regex, but it will be one byte longer: "[a-zA-Z]" -> "(?i)[a-z]".

I think you should use s.regionMatches(0<1,i,"rex",0,3) instead of s.toLowerCase().substring(i,i+(i>l-3?1:3)).equals("rex").

@KevinCruijssen Take the current code that you have (at 168 bytes), remove the variable l and save 4 bytes.

Python 2 or 3, 75 73 70 bytes
import re;f=lambda s:re.sub('(?i)(rex)|\w',lambda x:x.group(1)or'*',s)

Basically the same as my Ruby's answer.
-2 bytes thanks to @Wondercricket.
As a test:
tests = [
  ["Rex, I told you not to do this! You're making me angry Rex!", "Rex, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!"],
  ["Rex lives in Rexland.", "Rex ***** ** Rex****."],
  ["To call Rex, just say \"Rex\".", "** **** Rex, **** *** \"Rex\"."],
  ["My cat is getting anorexic.", "** *** ** ******* ***rex**."]
]

for test_in, test_out in tests:
    print(test_in)
    print(f(test_in))
    print(f(test_in) == test_out)

You could save 2 bytes by removing the spacing between x.group(1) or '*'

@Wondercricket: Thank you very much, it's my first golf answer in Python.

Python 2, 87 Bytes
import re
print re.sub(r'(?i)[a-df-qs-wyz]|r(?!ex)|(?<!r)e|e(?!x)|(?<!re)x','*',input())

I guess that can be shortened? :)

You can get rid of the spaces after the commas between arguments for 2 bytes off.

sed, 37 33 bytes
36 bytes sourcecode + 1 byte for -r flag.
s:(rex)|\w:\1*:Ig
s:(rex)\*:\1:Ig

Try it online!

@Shaggy Thanks for the heads up!

You use use . instead of \*

QuadR, 11 10 + 1 = 11 bytes
+1 byte for the i flag.
rex
\w
&
*

Try it online!
Explanation: Case-insensitively replace rex and word-chars with itself and asterisks, respectively.

Retina, 54 50 49 bytes
Golfed 5 bytes thanks to @MartinEnder
i(`[\w-[rex]]
*
(?<!r)e|e(?!x)|r(?!ex)|(?<!re)x
*

Try it online!

@Emigna Just realised that my solution does not work for dex, it gives *e* while yours gives **x.

If you group both stages by putting ( after the first i you don't need to configure the second stage.

And your first regex can be written as [a-z-[rex]].

@MartinEnder Thanks, this is the first time I'm seeing character classes where you can exclude certain characters

they exist in several flavours but I believe .NET's syntax for them is unique.

Gema, 25 characters
/[rR][eE][xX]/=$0
<L1>=\*

Sample run:
bash-4.3$ gema '/[rR][eE][xX]/=$0;<L1>=\*' <<< "Rex, I told you not to do this! You're making me angry Rex!
Rex lives in Rexland.
To call Rex, just say \"Rex\".
My cat is getting anorexic."
Rex, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!
Rex ***** ** Rex****.
** **** Rex, **** *** "Rex".
** *** ** ******* ***rex**.

Painful fact that could be \CRex=$0;<L1>=\*, but unfortunately $0 contains the template, not the match. ☹

PowerShell, 60 bytes
{$args|%{$_-replace'(rex)|\p{L}','$1*'-replace'(x)\*','$1'}}

Try it online

My fault. I've replace \w to \p{L}.

Interesting idea. Note that using $args as an array has consequences when things are quoted, as in one example. And if you're only using the first argument anyway, you don't need the foreach.

@AndreiOdegov You can go back to \w. Side note: Are the braces in \p{L} really needed?

very nice Regex, "$args"-replace'(rex)|\p{L}','$1*'-replace'(x)\*','$1' is much shorter overall, wrapping $args in quotes turns it all into a single string and saves you a lot.

@Adám The curly braces in .NET  is required.

@ConnorLSW Thanks for the valuable pointing.

Python 2, 93 92 bytes
f=lambda s:s and("rex"==s[:3].lower()and s[:3]+f(s[3:])or("*"+s)[s[0].isalpha()<1]+f(s[1:]))

Try it online!

MATL, 24 bytes
42y3Y2mFGk'rex'Xf!3:q+((

The input is a string enclosed in single quotes.
Try it online!
Explanation
Consider input 'Rex lives in Rexland.'
42    % Push 42 (ASCII for '*')
      % STACK: 42
y     % Implicit input. Duplicate from below
      % STACK: 'Rex lives in Rexland.', 42, 'Rex lives in Rexland.'
3Y2   % Push string 'ABC...YZabc...yz'
      % STACK: 'Rex lives in Rexland.', 42, 'Rex lives in Rexland.', 'ABC...YZabc...yz'
m     % Ismember
      % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0]
F     % Push false
      % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0
Gk    % Push input lower-cased
      % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, 'rex lives in rexland'
'rex' % Push this string
      % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, 'rex lives in rexland', 'rex'
Xf!   % Strfind and transpose: gives indices of matchings as a column vector
      % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, [1; 14]
3:q   % Push [0 1 2]
      % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, [1; 14], [0 1 2]
+     % Addition, element-wise with broadcast
      % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, [1 2 3; 14 15 16]
(     % Assignment indexing: sets indicated entries to 0
      % STACK: 'Rex lives in Rexland.', 42, [0 0 0 0 1 1 1 1 1 0 1 1 0 0 0 0 1 1 1 1 0]
(     % Assignment indexing: sets indicated entries to 42 (that is, '*'). Implicit display
      % 'Rex ***** ** Rex****.'

Bash, 128 bytes
r=REXrex;a=`tr -c $r'",. !
' l<<<$1`;for i in {r,R}{e,E}{x,X};{
a=`echo ${a[@]//$i/$(tr $r f-k<<<$i)}`;}
tr $r l<<<$a|tr f-l $r*

Try it online!
I'm sticking to tr on my previous answer, non-functional bash array string replace and no preg replace!
Less golfed:
    a=`echo $1 |tr -c 'REXrex.,\"! ' 'z'`;        -> a replaces with z chars in input not matching REXrex or punctuation
    for i in {r,R}{e,E}{x,X}; {                   -> iterates over rex .. rEx .. REX
      j=$(tr 'REXrex' 'ABCabc' <<<$i)}            -> holds a for r, A for R, ando so on
      a=`echo ${a[@]//$i/$j`;                     -> replace each combination of rex .. rEx .. REX with abc ... aBc.. ABC
    }
    tr 'REXrex' 'z' <<<$a |tr 'ABCabcz' 'REXrex*' -> replaces each remainig r,e,x,R,E,X with z and finally each ABC with REX and z with *

Had to use z instead of * because of expansion

looks its not the right tool :P

You can save a couple of characters by not quoting tr's parameters when they contain nothing expandable.

In regards to your other bash answer: Feel free to fix, and then flag for mod attention requesting undeletion.

Looking again, there are quite many rexes for a r=REXrex variable to worth.

If you keep $r out of quoted string, you not need to change the surrounding ' to ", so no need to escape the literal ". Of course, then you write a literal newline by breaking the line in that place instead of \n:  $r'",. !␤'.

:) I wonder if I can change echo and for with tr to have tr only answer

C#, 93 90 bytes
s=>System.Text.RegularExpressions.Regex.Replace(s,"(?i)rex|\w",m=>m.Length>1?m.Value:"*");

Believe this is the first time I've used a regex in a C# answer here because of the long namespace System.Text.RegularExpressions.

Didn't realise it when I wrote my answer but this seems to be the C# version of @Shaggy's JavaScript answer.

Thanks for the mention, even though you came up with your answer independently of mine.

@Shaggy Ah thanks didn't know it had been updated

Perl, 31 bytes
s/(rex)|[a-z]/$1||"*"/ieg;print

Invoke perl with -n option. For example:
echo 'To call rex, just say "Rex".'| perl -ne 's/(rex)|[a-z]/$1||"*"/ieg;print'
** **** rex, **** *** "Rex".

[a-z] can now be replaced with \w as the input will never contain numbers or underscores.

You can use -p instead of -n and remove ;print

CJam, 39 bytes
q{_3<_el"rex"=3{elc_'{,97>&'*@?1}?\o>}h

Try it online!
How it works
q           e# Read the input.
{           e# Do:
 _3<        e#  Copy the string and get its first three characters.
 _el"rex"=  e#  Check case-insensitively if they equal "rex".
  3         e#   If they do, push 3.
  {         e#   If they don't:
   elc_     e#    Take the first character of the three, and make it lowercase.
   '{,97>&  e#    Take its set intersection with the lowercase alphabet. Returns a non-empty
            e#      string (truthy) if it's a letter or an empty string (falsy) if not.
   '*@?     e#    Push a * if the char is a letter, or itself if it's not.
   1        e#    Push 1.
  }?        e#  (end if)
 \o         e#  Print the second-from-the-top stack item.
 >          e#  Slice the string after the first 3 or 1 characters, depending on previous outcome.
}h          e# Repeat the above until the string is empty.

Java 7, 96 98 97 96 bytes
+2 bytes for missing e's preceded by r or followed by x but not both
-1 byte for changing [a-z&&[^rex]] to (?![rex])\\w
String a(String s){return s.replaceAll("(?i)r(?!ex)|(?<!r)e|e(?!x)|(?<!re)x|(?![rex])\\w","*");}

Try it online!
A regex version for replacing using Java
Replaces everything in this regex with a * (note in Java \w has to be escaped as \\w)
(?i)r(?!ex)|(?<!r)e|e(?!x)|(?<!re)x|(?![rex])\w

(?i)                                                   // Case Insensitive
    r(?!ex)                                            // Any r not followed by ex
           |(?<!r)e                                    // Or any e not preceded by r
                   |e(?!x)                             // Or any e not followed by x
                          |(?<!re)x                    // Or any x not preceded by re
                                   |(?![rex])\w        // Or any other word character

CJam, 26 bytes (uppercase output) / 36 bytes (case-preserving)
qeu"REX":R/{__el-'*@?}f%R*

Try it online!
If letter case needs to be preserved (since that's still a little bit unclear), that can be accomplished with 10 extra bytes:
q_32f&:i\eu"REX":R/{__el-'*@?}f%R*.|

Try it online!
By the way, while writing this answer, I found what I would consider to be a design bug in CJam: the bitwise operators & and | are not defined between two char values, so I can't use .| to take the bitwise OR of two strings.  The solution, which ended up costing me two extra bytes, is to first convert one of the strings with :i into an array of integers, which then can be ORed with the other string.  (Actually it cost me three bytes, because if & worked between two chars, I could've also used Sf& instead of 32f& to save the letter case information.)
On the positive side, I did discover that {...}f% indeed works as expected for iterating over the characters in an array of strings.  Nice.
Anyway, here's a (lightly) commented version of the 36-byte code:
q                                       "read input";
 _32f&:i\                               "save the case bit of each input char";
         eu"REX":R/                     "uppercase input and split it on 'REX'";
                   {                    "start code block:"
                    __el-'*@?           "c = (c != lowercase(c) ? '*' : c)";
                             }f%        "apply block to chars in each substring";
                                R*      "join the substrings with 'REX' again";
                                  .|    "bitwise OR the case bits back in";

The case-saving trick works because the case of ASCII letters is solely determined by the fifth bit of the ASCII code: this bit is 0 for uppercase and 1 for lowercase letters.  Thus, taking the bitwise AND of the character code with 32 = 25 extracts the case bit, and bitwise ORing this bit with the uppercased letters restores their original case.
Of course, non-alphabetic characters may have arbitrary values for the fifth bit (although, due to the way ASCII characters are organized, most punctuation characters have the fifth bit set to 1) but this doesn't matter, since those characters are left untouched by uppercasing and the letter censoring loop anyway, and ORing a character with its own fifth bit does't change it.  Also, conveniently, the * character already has the fifth bit set, so it is also left unchanged by the final .|.

Jelly, 23 bytes
No response in over 24 hours to my question regarding case, so I'll post this tentative 23 byter.
“rex”
Œlœṣ¢µØaW;”*yµ€j¢

See the test cases at Try it online!
How?
“rex” - Link 1, get "rex": no arguments
“rex” - literal "rex"

Œlœṣ¢µØaW;”*yµ€j¢ - Main link: string s (or list of characters)
Œl                - convert s to lowercase
    ¢             - call the last link (1) as a nilad (get "rex")
  œṣ              - split left (s) at sublists equal to right ("rex")
     µ            - call the result t
             µ€   - for each word, w, in t:
      Øa          -   literal: lowercase alphabet
        W         -   wrap it in a list
          ”*      -   literal: '*'
         ;        -   concatenate the wrapped alphabet with the asterisk
            y     -   translate: replace all lowercase letters with asterisks.
                ¢ - call the last link (1) as a nilad (get "rex")
               j  - join left (translated, split text) with copies of right ("rex")

I'm not sure this is valid. It doesn't match the test cases.

See my note right at the top, and my question to OP.

( although unfortunately the comment I linked to where the OP said "dogs don't get the difference between lowercase and uppercase" has now gone)

VimScript, 34 bytes
s/\v(rex|r@<=ex|(re)@<=x)@!\w/*/gi

And here's an interesting substitution that almost works:
s/\vr(ex)@!|<e|<x|[rex]@!\w/*/gi

Imagine running this repeatedly on the string Rex, dex, I told you not to do this! You're making me angry Rex! After the first line, the string is Rex, *ex, * **** *** *** ** ** ****! ***'*e ****** *e ***** Rex! The second pass will result in Rex, **x, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!, and the third pass will finish it. Any subsequent passes will not change the string. However, it could take more than 3 substitutions to get there, for example on string xxxxxxxx. So, if there were an easy way to run the above substitution until it stopped changing things, or as many times as the length of the input, that would be another solution. I bet it could be done in V, but it would still probably be longer than 34 bytes.

Thanks for the idea! Here's a V answer. :)

Japt, 21 20 19 17 bytes
A port of my JavaScript solution, with a little help from Oliver. Would be 16 in Japt v2 but RegEx is very bugged right now.
r`(x)|%l`Ïª'*}'i

Try it
Japt v2.0a0, 12 bytes
If, as some other solutions assume, output case is irrelevant.
v Ër\l'*}`x

Try it

Pip, 21 19 bytes
qR-`(rex)|\w`{b|'*}

Takes input from stdin, outputs to stdout. Try it online!
Explanation
q                    Read a line of stdin
 R                   and replace
   `(rex)|\w`          a regex matching `rex` or any single alphanumeric character,
  -                    case-insensitive
             {    }  with this callback function:
              b|'*     If the 1st capture group (rex) matched, return it, else asterisk
                       The result of the replacement is auto-printed

SNOBOL4 (CSNOBOL4), 147 bytes
	L =INPUT
	K =ANY(&UCASE &LCASE)
B	L ARB . X (('R' | 'r') 'ex') . Y REM . L	:F(O)
R	X K ='*'	:S(R)
	O =O X Y :(B)
O	L K ='*'	:S(O)
	OUTPUT =O L
END

Try it online!
Previous version did not properly handle non-alphabetic characters.

V, 27, 24 bytes
I'm leaving both answers up, because I think they're equally interesting.
27 bytes
òÓãr¨ex©À!ü¼eü¼xü[rex]À!÷/*

Try it online!
Thanks to Brian McCutchon for his idea to do this in V
Hexdump:
00000000: f2d3 e372 a865 78a9 c021 fcbc 65fc bc78  ...r.ex..!..e..x
00000010: fc5b 7265 785d c021 f72f 2a              .[rex].!./*

Explanation:
ò                           " Until the output stops changing...
 Óãr¨ex©À!ü¼eü¼xü[rex]À!÷/* " Run the compressed vim regex:

:s/\vr(ex)@!|<e|<x|[rex]@!\w/*/gi

24 bytes
Óãrex/ò&ò
HòÓ÷/*/e
jjòÍî

Try it online!
Ó           " Substitute
 ã          "   (case-insensitive)
  rex       "   'rex'
     /      " with
       &    "   'rex'
      ò ò   "   Surrounded in new lines
ò           " Recursively...
 Ó          "   Substitute...
  ÷         "     A word character
   /        "   with
    *       "     An asterisk
     /e     "     (don't break if there are 0 matches)
jj          "   Move down 2 lines
  ò         " end the recursive loop
   Íî       " Remove all newlines

05AB1E, 21 bytes
l…rex©₄ç:AS'*:₄ç®:s.Ï

I've assumed the input can contain any ASCII character and only upper and lowercase letters are replaced with asterisks. It could be 2 bytes less if we assume the input will never contain substring 1000, in which case both ç can be removed.
Try it online.
Explanation:
l           # Convert the (implicit) input to lowercase
 …rex       # Push string "rex"
     ©      # Store it in the register (without popping)
      ₄ç    # Push 1000 and convert it to a character: "Ϩ"
        :   # Replace all "rex" with "Ϩ"
AS          # Push the lowercase alphabet, and convert it to a list of characters
  '*       '# Push "*"
    :       # Replace every letter in the alphabet with "*"
₄ç          # Push "Ϩ" again
  ®         # Push "rex" from the register
   :        # Replace all "Ϩ" with "rex"
s           # Swap to take the (implicit) input again at the top of the stack
 .Ï         # Match the case of the lowercase "rex" with that of the input
            # (and output implicitly)

PegJS + ES6, 50 bytes
C=a:("rex"i/[a-z]i{return'*'}/.)*{return a.join``}

Online interpreter
The lack of lambda syntax for the expression returns hurts the size.

[a-z] can now be replaced with \w as the input will never contain numbers or underscores.

PegJS is an implementation of Parsing Expression Grammar.  While the overall grammar can accept many more things than regular expressions, the individual [a-z] construct is not a regular expression; it is only a simple character class matcher and doesn't have \w or any equivalent.

Julia, 69 50 bytes.
f(x)=replace(x,r"rex|[a-z]"i,a->length(a)>1?a:"*")

Took some ideas from more concise entries, now it works like them.

[a-z] can now be replaced with \w as the input will never contain numbers or underscores.

Japt 2.0a0, 17 bytes
r/(Rex)|\l/i@Yª'*

Try it online!
Unpacked & How it works
Ur/(Rex)|\l/iXYZ{Y||'*

Ur  Replace on input...
/(Rex)|\l/i  Match this regex globally...
             (`\l` stands for [A-Za-z], and Japt regexes have `g` by default)
XYZ{Y||'*    and replace anything that isn't Rex into '*'.

S.r uses JS's String.replace which can accept a function as 2nd input, but unfortunately, the Japt doc doesn't mention it.
Idea adapted from Martin Ender's Retina answer.

K4, 56 bytes
Solution:
{@[;r;:;x@r:,/ss[_x;"rex"]+\:!3]@[x;&(_x)in .Q.a;:;"*"]}

Examples:
q)k){@[;r;:;x@r:,/ss[_x;"rex"]+\:!3]@[x;&(_x)in .Q.a;:;"*"]}"Rex lives in Rexland."
"Rex ***** ** Rex****."
q)k){@[;r;:;x@r:,/ss[_x;"rex"]+\:!3]@[x;&(_x)in .Q.a;:;"*"]}"To call Rex, just say \"Rex\"."
"** **** Rex, **** *** \"Rex\"."
q)k){@[;r;:;x@r:,/ss[_x;"rex"]+\:!3]@[x;&(_x)in .Q.a;:;"*"]}"My cat is getting anorexic."
"** *** ** ******* ***rex**."

Explanation:
No regular expressions here... Change everything non-alphabetic into stars, put back any occurences of Rex:
{@[;r;:;x@r:,/ss[_x;"rex"]+\:!3]@[x;&(_x)in .Q.a;:;"*"]} / the solution
{                                                      } / lambda taking implicit x
                                @[x;            ;:;"*"]  / apply (@) assignment (:) of "*" at indices...
                                         in .Q.a         / in lowercase alphabet?
                                     (_x)                / lowercase (_) x
                                    &                    / indices where true
 @[; ;:;                       ]                         / another application of assignment
                             !3                          / range til 3 -> 0 1 2
                          +\:                            / add (+) to each left (\:)
              ss[_x;"rex"]                               / string-search for "rex" in lowercase (_) x
            ,/                                           / flatten
          r:                                             / assign to r
        x@                                               / index into x
    r                                                    / indices to modify

Dart, 99 bytes
f(s)=>s.replaceAllMapped(RegExp(r'(rex)|(\w)',caseSensitive:false),(Match m)=>m[1]!=null?m[1]:'*');

Try it online!

Stax, 18 bytes
ùV:┬KsS•u┤8α╞"T-┤☼

Run and debug it

JS (ES5), 75 bytes
function r(l){return l.replace(/rex|\w/gi,function(c){return c[1]?c:"*"})}

Based on this

Is "porting" an answer to another version of the same language permitted? I don't know. But you should at least include a credit in your answer by linking to the original as TheLethalCoder did (unnecessarily, in that case) in their answer.

You may replace [a-z] with \w.

source: Stack Exchange

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/3.0/

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/

sort: votes


text:
Prefix Notation to Postfix Notation
Disclaimer: No, this is not a joke challenge to reverse a string.
Task
There is only one operation to support: subtraction (-).
You also only have two atoms to support: zero (0) and one (1).
Here, the prefix notation -AB is equivalent to the postfix notation AB-, where A and B are expressions.
Your task is to (recursively) convert an expression in prefix notation to its equivalent in postfix notation.
Definitions
An expression in prefix notation is generated by the following grammar:
S > -SS
S > 0
S > 1

An expression in postfix notation is generated by the following grammar:
S > SS-
S > 0
S > 1

Example
Prefix notation:  --01-0-01
Parentheses:      -(-01)(-0(-01))
Convert:          (01-)(0(01-)-)-
Postfix notation: 01-001---

Rules and freedom

You may rename the operation and the atoms to whichever character, as long as it is consistent.
The input format must be consistent with the output format (apart from the fact that the input is in prefix notation and the output is in postfix notation).

Testcase
Input       Output
1           1
0           0
-01         01-
-10         10-
--01-0-01   01-001---

Testcases credits to Dada.

Can you add a few more test cases, please?

@Shaggy what kind of testcases would you like?

@LeakyNun Is it fine to take the input and output as iterators, as I've done in the latest version of my answer?

@L3viathan I suppose so...

brainfuck, 32 bytes
,[[->++++<<+>]>[[-]<<[.[-]<]]>,]

Try it online!
I used @ as the operation, because its code point (64) is convenient.  U is also possible with the same byte count, using 3*85+1=256=0.
Explanation
The tape is used as a stack.  In each iteration of the main loop, the data pointer starts two cells right of the top of the stack.
,[                Take input and start main loop
  [->++++<<+>]    Push input, and compute 4*input
  >[              If 4*input is nonzero (and thus input is not @):
    [-]<<           Zero out this cell and move to top of stack
    [.[-]<]         Pop from stack and output until \0 is reached
  ]
  >,              Move pointer into the correct position.  If input was @, the earlier > pushed \0 onto the stack.
]

Retina, 37 30 29 bytes
M!`-*.
+m`^-(.*)¶(\d.*)
$1$2-

Try it online! Saved 7 bytes by realising that terms always begin with a digit, so I don't have to limit the match to the last - any more (previously it was the only one guaranteed to be followed by two terms). Saved 1 byte by not putting -s on their own line. For example, -01 becomes -0¶1 which is then replaced with 01-. Now, if I have --010 i.e. --0¶1¶0 then I want to change the inner -0¶1 to 01- so that I can replace the -01-¶0 with 01-0-, but it doesn't actually matter which of the two -s I remove, so I remove the one at the beginning of the line, as that's easier to test for.

I think this is your something :)

@Leo Doesn't work in general, e.g. -0-0-00 should become 0000---.

You're right, sorry. I have another idea, but it's substantially different, so I'll post it as a new answer

@Leo I've now found my something...

Nice job, but with my last golf my solution is still 1 byte shorter :)

@Leo With my latest golf we're tied!

Haskell, 62 59 bytes
f(x:r)|x>'-'=([x],r)|(a,(b,c))<-f<$>f r=(a++b++"-",c)
fst.f

Try it online! Usage: fst.f $ "--01-0-01". 0 and 1 can be arbitrary characters that are larger than the character -.
Edit: -3 bytes thanks to Zgarb!
The function f recursively parses one expression and returns a tuple of this expression in postfix notation and the rest string, following the simple grammar from which valid prefix-expressions can be build:
<exp> ::= - <exp> <exp> | 0 | 1

If the first character a of the input string is larger than -, we are at an atomic expression and return a tuple of a string with character a and the rest of the input string.
If we find a -, two expressions need to be parsed. This can be achieved by (a,x)<-f r to get the first expression a and then parse the rest string x again (b,c)<-f x to get the second expression b and the final rest string c. (a,(b,c))<-f<$>f r does exactly this because <$> on tuples maps a function two the second element of a tuple while being three bytes shorter than (a,x)<-f r,(b,c)<-f x. After obtaining both expressions and the rest string, the expressions are concatenated and a "-" is appended: (a++b++"-",c).

You can save 3 bytes by combining the cases: f(x:r)|x>'-'=([x],r)|(a,(b,c))<-f<$>f r=(a++b++"-",c)

@Zgarb Thanks! For some reason I only considered f(x:r)|x<'0',(a,(b,c))<-f<$>f r=(a++b++"-",c)|1<3=([x],r) when I looked for a version with both cases combined, which is byte longer.

Haskell, 54 bytes
v f""=""
v f(a:s)=last(v.v:[id|a>'-'])((a:).f)s
h=v h

The function v takes a string and a function, rearranges the initial sub-expression, then applies the function to the remainder of the string until everything has been rearranged. The call stack and function argument together keep track of what expression is being parsed. The function h answers the challenge, and is just v called with itself as a dummy first argument.

Wow! (1) That's just 53, you don't need to count the final newline. (2) The first line can be shortened to v f l=l if you move it second.

I don't think you need to parse more than one whole expression, so you can save a byte by using the anonymous function v id.

Actually the first line never gets called on valid input, so you can just delete it.

Splitting into guards seems to beat the last trick by one byte.

Python 3, 117 112 105 100 98 76 62 61 59 bytes
def p(s):x=next(s);yield from[x]*(x>"-")or[*p(s),*p(s),"-"]

Changelog:

removed linebreaks where possible (-5 bytes)
lambda instead of a full function (-7 bytes, thanks @Dada)
no else (-5 bytes, thanks @Leaky Nun)
undo overzealous golfing (-2 bytes, thanks @Leaky Nun)
work on a global list instead (-22 bytes)
actually, let's work on iterators instead (-14 bytes)
change != to > (-1 byte, copied from @ovs' suggestion)
lazy evaluation trickery (-2 bytes, thanks @ovs)

Use it like this:
>>> list(p(iter("--01-0-01")))
['0', '1', '-', '0', '0', '1', '-', '-', '-']

Doesn't work because you forgot return

@LeakyNun Just noticed it myself; fixed.

lambda x:p(x)[0] could probably replace your f function.

You don't need else, methinks.

Does having d="-" really save bytes?

@LeakyNun It does not..

@ovs I can't copy that exactly because then the input is not in the same format as the output.

def p(s):x=next(s);yield from[x]*(x>"-")or[*p(s),*p(s),"-"]for 59 bytes

Perl 5, 57 bytes
sub f{"@_"=~s/x((?0)|.)((?0)|.)/my$n=$2;f($1).f($n).x/re}

I use x as operator instead of - (see the TryItOnline link bellow).
Try it online!
Explanations:
/x((?0)|.)((?0)|.)/ matches recursively a full expression: a x at the begining, then an expression (?0) (it's a recursive call) or an atom (.), followed by another expression-or-atom.
Then I need to save the second expression/atom (my$n=$2;) because otherwise the recursive calls will override it.
The function is then recursively called on the first expression (f($1)), then on the second f($n), and the x is appended at the end (.x).

Pyth, 20 bytes
L+&-hbTsyM.-Btbytbhb

This creates a function y that expects a string as parameter.
Try it online: Demonstration or Test Suite
Explanation:
The function y will parse and convert the first prefix expression to a postfix expression. So if it is called like y"10" it will return only 1.
L+&-hbTsyM.-Btbytbhb
L                      define a function y(b), that returns:
   -hbT                   remove the chars "10" from the first char b
                          (T=10, and - will convert a number to a string)
  &                       if this gives the empty string (a falsy value)
 +                hb         then append b[0] to it and return it
                             (so this will parse a digit 0 or 1 from the string)
  &                       otherwise (the first char is a -)
               ytb           parse the first prefix expression from b[1:]
                             (recursive call)
          .-Btb              remove this parsed expression bifurcated from b[1:]
                             this gives a tuple [b[1:], b[1:] without first expr]
        yM                   parse and convert an expression from each one
       s                     join the results
 +                hb         and append the b[0] (the minus) to it and return

Retina, 34 31 29 bytes

;
-;
¶
+`¶(.+);(.+)
$1$2-
;

Try it online!
; are used to indicate nodes, which are initially composed by a single number and then grow to anything that has already been parsed. - are turned into newlines so that with .+ we can grab anything that isn't an unparsed -.

Unix TMG, 49 bytes
This translation-oriented language from 1972 makes this task trivial:
p:parse(b);b:<->b b={2 1<->}|<0>={<0>}|<1>={<1>};

I believe, this is the first solution in this language on this website, so I will explain a bit:
p: parse(b);                # parse() builtin: execute parsing rule `b` and output its translation
b: <-> b b = { 2 1 <-> }    # encountering "-" do recursion; translation rule is in braces
 | <0> = { <0> }            # otherwise: encountering "0" - translate as "0"
 | <1> = { <1> };           # otherwise: encountering "1" - translate as "1"

Perl 6, 45 bytes
my&f={S[x(<~~>)**2|)>.]=$0&&$0>>.&f.join~'x'}

Try it online!

Javascript (ES10), 118 bytes
e=>[...e.matchAll(/(-*)(\d+)/g)].reduce((a,[_,m,[c,...s]])=>(o+=m.slice(l=s.length),a+c+s.join(d='-')+(l?d:s)),o='')+o
Runnable Snippet (TIO doesn't have ES10's String.matchAll yet)

f = e =>
  [...e.matchAll(/(-*)(\d+)/g)].reduce((a, [_, m, [c,...s]]) =>
    (o += m.slice(l=s.length),
    a+c+s.join(d='-')+(l?d:s)), o='')+o;

['1', '0', '-01', '-10', '--01-0-01'].map(v =>
  console.log(`f('${v}') -> ${f(v)}`))

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/3.0/