JavaScript (Node.js), 608 597 586 551 bytes
Takes input as (source)(input)
, where input is an array containing single characters and/or integers.
s=>I=>eval([..."@CDEJKLMNQTUWYZ^_bkmqw"].reduce((s,c)=>(a=s.split(c)).join(a.pop()),"(R=X=>(H={},o=P=[],S=[],z=x=p=i=0,gUs[p]?~(j=` \n`.indexOf(s[p++]N?j:gK:3,hUgK<2?Mn*2+j):V=n,GUx=z--&&S.pop(y=x),FUg(x=`mbQQ$mS[z+~(b])TSCce((z-=V=b-1,V)$mkJLmyJkTTLH[x]=y$mHD)qR[M1)]=_?R[P@p),V]:_Y_&!kY_&k<0Yp$E?P.popK:pT$^QQQQQqW+yw-yw*yw/y|0w%yN:^QTTo+=Buffer(D)$o+=k$Z.codePointAtKZ`Ct`$`[n-9],x&&eval(x,n=1N<3&&F(n*3+jN(1N(0)||R(1)||owN:^$WqTT$mz=S@kGKbgK?-M0):M0N_p$M1),E^E?g:pZ$HD=I[i++]Y?R[V]:Wz>1?(LmxU=n=>T$$QqqN))Mh(Lk,k,K()J),mx)$Ep=XD[k][email protected]"))
Try it online! (all test cases without comments)
Try it online! (an example with a commented source code as input)
How?
Because the code is packed with RegPack, it is more efficient to repeat the same pieces of code over and over rather than defining too many helper functions.
The sequences of spaces, tabs and linefeeds are converted to an integer in base 3 with a leading \$1\$ until they match a known value:
seq. | mnemonic | base 3 | decimal
------+----------+--------+---------
SS | PSH | 100 | 9
STS | CPY | 1010 | 30
STN | SLI | 1012 | 32
SNS | DUP | 1020 | 33
SNT | SWP | 1021 | 34
SNN | DIS | 1022 | 35
------+----------+--------+---------
TTS | PUT | 1110 | 39
TTT | GET | 1111 | 40
------+----------+--------+---------
NSS | LBL | 1200 | 45
NST | JSR | 1201 | 46
NSN | JMP | 1202 | 47
NTS | JZE | 1210 | 48
NTT | JMI | 1211 | 49
NTN | RTN | 1212 | 50
NNN | END | 1222 | 53
------+----------+--------+---------
TSSS | ADD | 11000 | 108
TSST | SUB | 11001 | 109
TSSN | MUL | 11002 | 110
TSTS | DIV | 11010 | 111
TSTT | MOD | 11011 | 112
------+----------+--------+---------
TNSS | CHR | 11200 | 126
TNST | INT | 11201 | 127
TNTS | RCH | 11210 | 129
TNTT | RNU | 11211 | 130
The commands are simply stored in a lookup table and executed with eval()
.
Everything is wrapped within the function \$R\$ which is called twice:
- First pass (\$X=0\$): the jumps and the stack errors are ignored so that the whole code is parsed linearly and all labels are stored
- Second pass (\$X=1\$): the code is executed normally
Unpacked and formatted
s => I => (
R = X => (
H = {},
o = P = [],
S = [],
z = x = p = i = 0,
g = n => s[p] ? ~(j = ` \t\n`.indexOf(s[p++])) ? j : g() : 3,
h = n => g() < 2 ? h(n * 2 + j) : V = n,
G = n => x = z-- && S.pop(y = x),
F = n => g(
x = [
/* PSH */ "z=S.push(g()?-h(0):h(0))",,,,,,,,,,,,,,,,,,,,,
/* CPY */ "z=S.push(S[z+~(g()?-h(0):h(0))])",,
/* SLI */ "S.splice((z-=V=g()?-h(0):h(0))-1,V)",
/* DUP */ "z=S.push(G()),z=S.push(x)",
/* SWP */ "G(),G(),z=S.push(y),z=S.push(x)",
/* DIS */ "G()",,,,
/* PUT */ "G(),G(),H[x]=y",
/* GET */ "z=S.push(H[G()])",,,,,
/* LBL */ "R[h(1)]=p",
/* JSR */ "h(1),p=X?R[P.push(p),V]:p",
/* JMP */ "h(1),p=X?R[V]:p",
/* JZE */ "h(1),p=X&!G()?R[V]:p",
/* JMI */ "h(1),p=X&G()<0?R[V]:p",
/* RTN */ "p=X?P.pop():p",,,
/* END */ "p=X?g:p",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
/* ADD */ "z>1?(G(),G(),z=S.push(x+y)):p=X?g:p",
/* SUB */ "z>1?(G(),G(),z=S.push(x-y)):p=X?g:p",
/* MUL */ "z>1?(G(),G(),z=S.push(x*y)):p=X?g:p",
/* DIV */ "z>1?(G(),G(),z=S.push(x/y|0)):p=X?g:p",
/* MOD */ "z>1?(G(),G(),z=S.push(x%y)):p=X?g:p",,,,,,,,,,,,,,
/* CHR */ "o+=Buffer([G()])",
/* INT */ "o+=G()",,
/* RCH */ "H[G()]=I[i++].codePointAt()",
/* RNU */ "H[G()]=I[i++]"
][n - 9],
x && eval(x, n = 1)
) < 3 && F(n * 3 + j)
)(1)
)(0) || R(1) || o