Trivial Brainf**k Substitution Interpreter

Python 2, 447 bytes

import re,sys
def f(b,r,a,i,n,f,u,c,k):
 d,e,g,m=[0]*30000,0,0,''
 for n in re.finditer('|'.join(r'(?P<%s>%s)'%m for m in[(j,re.escape(l))for j,l in zip('rlidoswen',[r,a,i,n,f,u,c,k,'.'])]),b):
  m+=' '*g+{'r':'e+=1','l':'e-=1','i':'d[e]+=1','d':'d[e]-=1','o':'sys.stdout.write(chr(d[e])),','s':'d[e]=ord(sys.stdin.read(1))','w':'while d[e]:','e':'pass'}.get(n.lastgroup,'')+'\n';g+=n.lastgroup=='w';g-=n.lastgroup=='e'
 try:exec m
 except:d[e]=-1

Try it online!

The first argument b is the code and the arguments r a i n f u c k are > < + - . , [ ] respectively.

Well... Lemme know if I missed out on anything.


Explanation

This implements a golfed tokenizer.

import re, sys

def trivial_brainfuck_substitution(code, r, a, i, n, f, u, c, k):  # define function with arguments
    tape, index, loop, result = [0]*30000, 0, 0, ''                # initialize tape, pointer, loop state and the final code

    for i in re.finditer(                                                    # iterate over matches of the regex...
                 '|'.join(                                                   # which is the next list joined by '|'
                     r'(?P<%s>%s)' % m for m in                              # construct a regex with a named group
                         [(j, re.escape(l)) for j, l in                      # a tuple of...
                             zip('rlidoswen', [r, a, i, n, f, u, c, k,'.'])  # the group name and the command
                         ]
                  ),
             code):                                                          # in the given code

        result += (' ' * loop +                                         # a space for the current amount of loops
                      {
                          'r': 'index += 1',                            # move to next cell
                          'l': 'index -= 1',                            # move to previous cell
                          'i': 'tape[index] += 1',                      # increment current cell
                          'd': 'tape[index] -= 1',                      # decrement current cell
                          'o': 'sys.stdout.write(chr(tape[index]))',    # output current cell's value as ASCII character
                          's': 'tape[index] = ord(sys.stdin.read(1))',  # store ASCII number of input in current cell
                          'w': 'while tape[index]:',                    # start a while loop
                          'e': 'pass'                                   # end a while loop
                      }.get(i.lastgroup, '') + '\n'                     # get respective command or an empty string for a no-op
                  )

        loop += i.lastgroup == 'w'  # increment the loop counter
        loop -= i.lastgroup == 'e'  # decrement the loop counter

    try:
        exec result       # execute the generated code as Python code
    except:               # except on a TypeError, thrown when no input is found
        tape[index] =- 1  # store -1 in current cell

Python 2, 373 372 381 380 375 bytes

-1 Thanks to @StepHen

-1 Thanks to @RohanJhunjhunwala

-5 More thanks to @RohanJhunjhunwala's suggestion and cleaning up some repeated code

+9 Cause @totallyhuman made a valid point about raw_input() requiring a new line

import sys
def f(c,r,l,i,d,p,n,b,j):
 t,x,a,h,s,o,v=[0]*30000,0,0,[],0,'',[r,l,i,d,p,n,b,j," "]
 w=map(len,v)
 while a<len(c):
	for u in range(9):
	 if v[u]==c[a:w[u]+a]:break
	if s<1or u>5:
	 exec"x+=1|x-=1|t[x]+=1|t[x]-=1|o+=chr(t[x])|t[x]=ord(sys.stdin.read(1))|if t[x]and s<1:h+=[a]\nelse:s+=1|if s<1:a=h.pop()-w[6]\nelse:s-=1|".split("|")[u]
	x%=30000
	a+=w[u]
 return o

Try it online!

Works on the first two test cases, but it looks like the third one is messed up since it has 9 strings instead of 8, and two are the same. It also works on some other Hello World! Brainfuck programs.


Explanation

import sys
def TrivialBrainfuckSubstitution(code, greater, less, plus, minus, period, comma, leftbracket, rightbracket):
    tape, index, slot, loopindex, skip, output = [0]*8**5, 0, 0, [], 0, ''                  # Set up variables for the tape, tape index, code index, list of loop indices we're in, skipping indicator, and output string
    values = [greater, less, plus, minus, period, comma, leftbracket, rightbracket," "]     # Create a list of all possible values to find in the code
    lengths = map(len, values)                                          # Create a list with the lengths of all of the elements in value
    while slot < len(code):                                             # While we havent reached the end of the string
        for u in range(9):                                              # For each value in the value list
            if value[u] == code[slot:lengths[u] + slot]:break           # If the u-th element of values is equal to the same length chuck of the code from the current index
        if skip < 1 or u > 5:                                           # If we're not skipping operations or if we found a leftbracket or rightbracket
            if u == 0:                                                  # If the option was a greater
                index += 1                                              # Increment the tape counter
            elif u == 1:                                                # Else if it was a less
                index -= 1                                              # Decrement the tape counter
            elif u == 2:                                                # Else if it was a plus
                tape[index] += 1                                        # Increment the current tape cell
            elif u == 3:                                                # Else if it was a minus
                tape[index] -= 1                                        # Decrement the current tape cell
            elif u == 4:                                                # Else if it was a period
                output += chr(tape[index])                              # Append the character value of the current tape cell to the output string
            elif u == 5:                                                # Else if it was a comma
                tape[index] = ord(sys.stdin.read(1))                    # Store only a single character of input into the tape cell
            elif u == 6:                                                # Else if it was a leftbracket
                if tape[index] and skip < 1:                            # If the current cell is non-zero and we aren't currently skipping over a loop
                    loopindex += [index]                                # Append the current index to the loopindex list
                else:                                                   # Else we're trying to skip to the end of a loop
                    skip += 1                                           # Increment the skip counter (a count of the number of rightbrackets we need to skip to continue executing)
            elif u == 7:                                                # Else if it was a rightbracket
                if skip < 1:                                            # If we're executing code normally
                    slot = loopindex.pop() - lengths[6]                 # Move to the index we stored when we hit the leftbracket, but push forward enough so we hit it with the next character
                                                                        # Simultaneously remove the last value from the loopindex list since we just used it
                else:                                                   # Else we're trying to move past some loop
                    skip -= 1                                           # Decrement the skip counter since we found another rightbracket
        index = index % 30000                                           # Map the index back to 0-29999
        slot += lengths[u]                                              # Move past the current operator
 return output                                                          # Return the output string we generated

I took a different approach than totallyhuman did, so that I didn't need to import and modules. It does however use the same trick with passing strings to exec to have it do all the work. Effectively, at the start of each loop, it looks at the current index and sees if the text that follows can be matched to any of the operators that were given. If it can't, then the counter will end at an index associated with a non-valid character in this language.

The hardest part for me was getting a refined way of matching loop starts and ends. I eventually settled on a counter that gets incremented every time it sees a loop start operator, and decrements every time it sees a loop end. So in order to have the now parsed option executed, we have to be not skipping over code (indicated by a skip counter of 0) or it's the loop start or loop end characters (or a bad character, but nothing happens with that, so it's okay).

That counter index is then used to take the index-th slot of an array of strings that is created by splitting that massive string you see at all of the |s. That line of code is then executed by exec which will do a handful of checks and variable mutations. At the end of the loop we skip past the operator we just found and keep going. Finally, return the output string we've been adding onto this entire time.

On of the key pieces to this was how skipping back to loops was handled. Every time a loop was started, it appended its index to the back of a list, and every time a loop ended, it set the code index to the last value of that list (minus the length of the loop end operator so that when it was pushed "past" the loop end operator we would be at the loop start operator again).


APL (Dyalog), 49 bytes

Prompts for program as text, then for a list of eight PCRE patterns. Note that this allows for substitutions of differing lengths, and even mapping multiple substitutions to each BF command, at the cost of having to escape some characters. Outputs to STDERR.

⎕CY'dfns'
bf(⎕,'.')⎕R(,¨'><+-.,[] ')⊢⍞

Try it online! (runs first two test cases, as the third is faulty)

⎕CY'dfns'Copy the dfns workspace

 prompt for text input (the symbol is mnemonic: a quote character in a console)

 yield that (serves to separate the substitution patterns from the input)

()⎕R()Replace:

⎕,'.' input (mnemonic: console) followed by PCRE for "any character"

… with:

'><+-.,[] ' the BF symbols followed by a BF no-op space

 ravel each (to make them into character vectors/strings instead of scalars)

bf evaluate as BF.


As of posting, this is way more than 50 bytes shorter than the other two solutions.