Haskell, 121 bytes
f d="($0).foldr(\\(Just g)->(.g))id.map(`lookup`["++(init$d>>=(\(c,o,p)->["('",c,"',(",o,"(",p,"))),"]>>=id))++"]);x!y=y" Uses the default operators except for = which is replaced by !.
The function f accepts a list of directives such as [("a","**","3"),("b","*","2"),("c","+","15"),("d","!","0"),("e","/","8")] and returns a Haskell function such as
($0).foldr(\(Just g)->(.g))id.map(`lookup`[('a',(**(3))),('b',(*(2))),('c',(+(15))),('d',(!(0))),('e',(/(8)))]);x!y=y (Try it online!). This function is the interpreter; in the above example, when called with input "cabdcbbe", it returns 7.5.
How?
The idea is to use the list of directives to embed a lookup table in the interpreter. In the above example, the lookup table is [('a',(**3)),('b',(*2)),('c',(+15)),('d',(!0)),('e',(/8))] (some brackets removed for clarity). This table maps each character to the corresponding function. Using the lookup table, the interpreter is able to convert the list of characters (e.g. "cabdcbbe") to a list of functions (e.g. [(+15),(**3),(*2),(!0),(+15),(*2),(*2),(/8)]), which is then foldred with the composition operator (.) to yield the full (interpreted) program. Finally, the program (which, like everything in Haskell, is a function) is applied to the value 0, returning the desired result.
Haskell, 113 bytes
I may be able to golf a few more bytes if the OP confirms thatAssuming the list of instructions can be read right-to-left instead of left-to-right.
f d="g i=foldr(.)id[f|c<-i,(a,f)<-["++(init$d>>=(\(c,o,p)->["('",c,"',(",o,"(",p,"))),"]>>=id))++"],a==c]0;x!y=y" Same idea as above, but without the need to reverse the string.