Print, Increment, Decrement, Alias - Interpret Prindeal

Pyth, 162 136 bytes

JfTmchcd\#).zKHW<ZlJI!e=T@J~+Z1=@Tk)=k0 .x=J+]h=Nm.xL@Tskd@K=NhT+]+tN0>J~Z0,=Y.x@H=eT0?qN\pps[Td\=dYb)?xGN?qN\iXHThY?YXTH_1=k1XKT:JZ=+Z3

Demonstration.

Golfed out 26 characters by inlining variables and changing from I and E based control flow to ? and .x based control flow.

For the first time ever, I ran out of variables in Pyth. Every single variable in Pyth (bdkGHNTY and JK) was in use, and I wanted to use b as a newline. Fortunaely, I was able to use N to mean two completely different things in different parts of the program, and so it still works.

Ungolfed (run with -m):

JfTmchcd\#).z
KH
W<ZlJ
  I!e=T@J~+Z1
    =@Tk)
  =k0
     .x
      =J+]h=Nm.xL@Tskd@K=NhT+]+tN0>J~Z0
      ,
        =Y.x@H=eT0
        ?qN\p
          ps[Td\=dYb)
          ?xGN
            ?qN\i
              XHThY
              ?Y
                XTH_1
                =k1
            XKT:JZ=+Z3

Python 2, 600 584 397 373 bytes

This is my own golfed reference solution. Anyone is welcome to improve it or follow its logic in their own answer as long as attribution is given.

The neat part about it is that no recursion is done, so it will never have problems with Python's recursion limit. For example, Sp's Countup Prindeal program can run indefinitely.

p=filter(len,[l.split('#')[0].split()for l in input().split('\n')]);m={};v={};i=0
while i<len(p):
 s=p[i]
 if'('in`s`:s=s[f]
 n,f=s[0],0
 if n in m:a,b,c=([s[int(g)]if g.isdigit()else g for g in t]for t in m[n]);p=[a,(b,c)]+p[i+1:];i=0;continue
 s=s[1]
 q=v.get(s,0)
 if'd'>n:m[s]=p[i+1:i+4];i+=3
 elif'i'<n:print s,'=',q
 elif'd'<n:v[s]=q+1
 elif q:v[s]-=1
 else:f=1
 i+=1

It's a program that takes in the quoted program string with newlines escaped, e.g.
'p _MyVariable_321\np screaming_hairy_armadillo'.

I took various golfing cues from Sp's and Pietu's answers. Thanks guys :)


Python 3, 345 336 335 328 bytes

a=0
A={}
V={}
def f(l):
 if l[0]in"d p i":c,u=l;U=V[u]=V.get(u,0)+"pi".find(c);S=U<0;V[u]+=S;c<"p"or print(u,"=",U)
 else:d=lambda q:[w.isdigit()and l[int(w)]or w for w in A[l[0]][q]];S=f(d(1+f(d(0))))
 return S
for z in open("P"):
 l=z.split("#")[0].split()
 if"a "==z[:2]:a,s,*x=3,l[1]
 elif l*a:x+=l,;a-=1;A[s]=x
 elif l:f(l)

(-6 bytes thanks to @orlp)

Still golfing. Assumes the program is stored in a file named P.

Putting the calls to f inside the lambda d would save a few bytes, but it would make the last test case hit max recursion depth.

Some Prindeal programs

The useless subtraction program

Here is a useless subtraction program. It's useless because, even though it subtracts properly, it doesn't return success/failure accordingly.

The output should be:

a = 15
b = 6
__________ = 0
a = 9
b = 6

Countup

a helper
 p 1
 countup 1
 i success
 
a countup
 i 1
 helper 1
 d failure
 
countup n

Counts upwards and prints n forever. Could possibly work as a test for interpreter speed (beware the long tracebacks on keyboard interrupt).