Simulate keystrokes

Vim, 76, 64, 62, 58 keystrokes

Thanks to Loovjo for saving 7 keystrokes


Did someone say simulate keystrokes? Well then, it's a good thing my favorite language to golf in is all about simulating keystrokes!

:no s :%s/\M[
sB]/<C-v><C-h>
sC]/<C-v><esc>0y$A
sD]/<C-v><esc>"_S
sP]/<C-v><C-r>"
s<bs>\n
S<C-r>"

Input comes in this format:

h
e
l
l
o

[C]
[P]

This is a pretty straightforward answer. It just translates each "command" to the vim keystroke equivalent of that command. Let's take it line by line.

:no s :%s/\M[

This saves a ton of bytes. Vim has a builtin "command line" where you can create mappings, change settings, save files, etc. Here we are creating a mapping. :no is short for :nnoremap which means "When we are in normal mode, substitute this left hand side for this right hand side." Since we are calling :%s/ five different times, this saves a lot. The \M is a nice trick. It means that the following search will be "Very No Magic", which means that the regex [B] will match the literal text [B] rather than a range containing just a B in it. Since the majority of the substitute commands have a brackets in them, we fill in the first one.

Then, we call five substitute commands. It's worth noting why I called <C-v> so many times. Characters like <esc>, <C-v>, <C-r>, etc. are unprintable characters, and must be typed into the command line with a <C-v>.

  • [B]: backspace. This one is pretty easy. Simply substitute each [B] with Ctrl-h, which is equivalent to backspace in vim.

  • [C]: copy all of what has already been written. This is translated to <esc>0y$A. This means:

    <esc>      " Escape to normal mode
         0     " Move to the beginning of this line
          y$   " Yank to the end of the line
            A  " Re enter insert mode at the end of this line.
    

    We could almost simply do Y in place of 0y$ which means "yank the whole line", but this also grabs a newline that we don't want.

  • [D]: delete all of what has been written. This is <esc>"_S. As before, <esc> exits insert mode so we can run commands. There are some things that are more convenient here. So we do

      S         " Delete this whole line and enter insert mode again
    "_          " Send it to 'the black hole register'. This is just so that we don't overwrite the main register.
    
  • [P]: paste what has been copied. This one is also very straightforward. It is just <C-r>" which means Insert the contents of register '"'. " happens to be the main register that 'y' yanks to.

Now that we have translated all of the commands, we must join all the lines together by removing all of the newline characters. Thanks to our mapping, this is just

s<bs>\n

The <bs> is a backspace, (ASCII 0x08) and we need it because of the [ we filled in.

By now, we have translated the input into vim code, and we just need to run it. So we:

S           " Delete this whole line and enter insert mode
 <C-r>"     " Insert the keystrokes of register '"' as if they were typed by the user

CJam, 33 bytes

q~{_[`';"];""L~""]:L~"]\1>3b=}%s~

Try it online!

Explanation

q~                                  Read an evaluate input list.
  {                          }%     Map over each string in it:
   _                                 Duplicate the string, say S.
    [`';"];""L~""]:L~"]              Replace it the following list:
                                      [repr(S) '; "];" "L~" "]:L~"]
                       \             Bring S on top of the stack.
                        1>           Chop off the first char.
                          3b         Base-3 conversion.
                            =        Modular index into the list.
                               s~   Concatenate and run as CJam code.

The “hash function” 1>3b maps

  • single-character strings to 0 (= 0 mod 5),
  • [B] to 291 (= 1 mod 5),
  • [D] to 297 (= 2 mod 5),
  • [P] to 333 (= 3 mod 5),
  • [C] to 294 (= 4 mod 5).

This value (mod 5) is used as an index into a list of CJam code snippets:

  • For single-character strings, say h, the snippet "h" is returned, which pushes a single-character string to the stack.
  • For [B], the snippet ; is returned, which pops an element.
  • For [D], the snippet ]; is returned, which clears the stack.
  • For [P], the snippet L~ is returned, which appends variable L onto the stack.
  • For [C], the snippet ]:L~ is returned, which stores the current stack in the variable L.

These snippets are concatenated and executed; the final stack is printed implicitly by CJam. L is initially the empty list, so the copy buffer is initially “empty”.


JavaScript (ES6), 84 80 77 76 bytes

Saved 3 bytes thanks to @Neil, 1 more thanks to @edc65

x=>x.reduce((s,[c,z])=>z<'C'?s.slice(0,-1):z<'D'?t=s:z<'P'?"":s+=z?t:c,t="")

.map is two bytes longer:

x=>x.map(([c,z])=>s=z<'C'?s.slice(0,-1):z<'D'?t=s:z<'P'?"":s+=z?t:c,s=t="")&&s

Test snippet

f=x=>x.reduce((s,[c,z])=>z<'C'?s.slice(0,-1):z<'D'?t=s:z<'P'?"":s+=z?t:c,t="")

console.log(f(['e', '[C]', '[B]', 'I', ' ', 'l', 'i', 'k', '[P]', ' ', 'b', '[P]', '[P]', 's', '!']))
<button onclick="console.log(f(I.value.split('\n')))">Run</button>
<br><textarea id=I rows=10>H
e
l
l
o
 
[C]
[P]</textarea><br>