# AoCG2021 Day 16: Interpret Duet

Related to AoC2017 Day 18, Part 2. (Anyone want to add Duet to esolangs?)

Duet is an assembly-like language that involves two processes running the same program simultaneously. Each process of Duet operates with 26 registers named a to z, all initialized to zero, except for the register p which contains the process ID (0 or 1).

Individual instructions in Duet are as follows. X is a register, and Y and Z can be a register or a constant. A constant is an integer which can be positive, negative, or zero, and is written in base 10 with optional - sign.

• set X Y sets register X to the value of Y.
• add X Y increases register X by the value of Y.
• mul X Y sets register X to the result of multiplying the value contained in register X by the value of Y.
• mod X Y sets register X to the remainder of dividing the value contained in register X by the value of Y (that is, it sets X to the result of X modulo Y).
• X may be assumed to be strictly non-negative and Y may be assumed to be strictly positive in this challenge.
• jgz Y Z jumps with an offset of the value of Z, but only if the value of Y is greater than zero. (An offset of 2 skips the next instruction, an offset of -1 jumps to the previous instruction, and so on.)
• snd Y sends the value of Y to the other program. These values wait in a queue until that program is ready to receive them. Each program has its own message queue, so a program can never receive a message it sent.
• rcv X receives the next value and stores it in register X. If no values are in the queue, the program waits for a value to be sent to it. Programs do not continue to the next instruction until they have received a value. Values are received in the order they are sent.

After each jgz instruction, the program continues with the instruction to which the jump jumped. After any other instruction, the program continues with the next instruction. Continuing (or jumping) off either end of the program in either process terminates the entire program (i.e. both processes), as well as being stuck in a deadlock (both programs waiting at a rcv instruction).

Duet does not have any I/O facility. The only observable behavior of a program is that either it terminates, or it doesn't.

Both of these programs below should terminate by a deadlock:

snd 1
snd 2
snd p
rcv a
rcv b
rcv c
rcv d

set i 31
set a 1
mul p 17
jgz p p
mul a 2
jgz i -2
set i 127
set p 680
mul p 8505
mod p a
mul p 129749
mod p a
set b p
mod b 10000
snd b
jgz i -9
jgz a 3
rcv b
jgz b -1
set f 0
set i 126
rcv a
rcv b
set p a
mul p -1
jgz p 4
snd a
set a b
jgz 1 3
snd b
set f 1
jgz i -11
snd a
jgz f -16
jgz a -19


Write an interpreter for Duet, which takes a valid source code as input (list of lines is OK), and runs it until the program terminates. Your interpreter should terminate if and only if the input Duet program terminates.

Standard rules apply. The shortest code in bytes wins.

Some examples that halt because one process exits early:

• Process 0 waits but Process 1 exits early by jumping over the end
mul p 10
jgz p p
rcv x

• Process 1 loops indefinitely but Process 0 halts by jumping before the start
jgz p 0
jgz 1 -2

• Process 0 loops indefinitely but Process 1 runs through the program and exits
add p 1
mod p 2
jgz p 0


Some examples that never halt:

• Both processes run an infinite loop that passes some values around
set a 10
snd 1
snd 2
rcv x
rcv y
jgz a -4

• Both processes run a tight loop
jgz 1 0

• Shall we assume that mod X Y will never be called with Y=0? Dec 16, 2021 at 0:30
• @Arnauld Yes. For consistency, let's say Y is always positive. Dec 16, 2021 at 0:58
• Is register p a constant or it just special at initial?
– tsh
Dec 16, 2021 at 5:33
• @tsh p is only different in its initial value, and can be modified in the program. Dec 16, 2021 at 6:55
• Is -5 % 3 -2 or 1? Dec 17, 2021 at 2:58

# Pari/GP, 459 bytes

p->a=matrix(2,26);a[2,16]=1;c=[l=List(),l];n=[1,1];while(![m<=0||m>#p|m<-n]&&sum(k=1,2,#c[k]||Vec(p[n[k]])[1]!="r"),for(k=1,2,(g(u,v)=if(0<t=s[u]-96,a[k,t],eval(Strchr(s[u..v]))));s=Vecsmall(p[n[k]]);i=s[2]-96;v=if(i-14&&i-7,x=s[5]-96;7,5);i-7||while(s[v]-32,v++)||z=g(5,v++-2);if(i-3,y=g(v,#s);i-5||a[k,x]=y;i-4||a[k,x]+=y;i-21||a[k,x]*=y;i-15||a[k,x]%=y;i-7||(z>0&&n[k]+=y-1);i-14||listput(c[3-k],y),if(#c[k],a[k,x]=c[k][1];listpop(c[k],1),n[k]--));n[k]++))


Try it online!

Takes a list of lines as input.

# JavaScript, 249 bytes

P=>{for(Q=[...P],P.M=[],Q.M=[],I=J=Q.p=1;E=I+J&&P[P.$??=0];[P,Q]=[Q,P])with(P)I=J,J=1,([[,O],X,Y]=E.split ).map(C=>P[C]??=0),eval({g:X+>0&&($+=${Y}-1),n:Q.M.push(${X}),c:M+""?${X}=M.shift():J=0}[O]||X+{e:'=',d:'+=',u:'*=',o:'%='}[O]+Y),$+=J}

F=P=>{
for(
// Initial
Q=[...P],   // P is process 0; Q is process 1
// We use the array for instructions,
// use P.a ~ P.z for registers
// use P._ for message queue received
// use P.$for index of next instruction to execute P._=[], // initial message queue Q._=[], I=J= // is last instruction successfully executed? (not waiting message) Q.p=1; // initial Process ID for Process Q // While not dead lock and not out of range E=I+J&& // terminate if all process waiting message P[P.$??=0]; // next instruction to execute
// Swap process schedule after each instruction execution
[P,Q]=[Q,P]
)with(P)        // use variables in process P
I=J,J=1,      // initial success flags
([
[,O],       // The second character is different for each instruction
X, Y        // two operand
]=E.split ) // parse instruction, split it by space
.map(C=>P[C]??=0), // Initial any value used to 0
eval({        // execute the instruction
g:X+>0&&($+=${Y}-1),              // jgz
n:Q._.push(${X}), // snd c:_+""?${X}=_.shift():J=0         // rcv
}[O]||
X+{e:'=',d:'+=',u:'*=',o:'%='}[O]+Y // other math op
),
$+=J // move to next instruction if success }  F=P=>{for(Q=[...P],P.M=[],Q.M=[],I=J=Q.p=1;E=I+J&&P[P.$??=0];[P,Q]=[Q,P])with(P)I=J,J=1,([[,O],X,Y]=E.split ).map(C=>P[C]??=0),eval({g:X+>0&&($+=${Y}-1),n:Q.M.push(${X}),c:M+""?${X}=M.shift():J=0}[O]||X+{e:'=',d:'+=',u:'*=',o:'%='}[O]+Y),$+=J} <form onsubmit="F(T.value.trim().split(/\s*[\n;]\s*/));return false;"> <textarea id=T></textarea> <br> <button id=R>RUN</button> </form> -1 byte by Neil • No point assigning to unused variable K, is there? – Neil Dec 16, 2021 at 9:01 • @Neil Ah, got it. I didn't know that I can omit some variables in destructing assignments... – tsh Dec 16, 2021 at 9:11 • Oh, I didn't even knew JavaScript was pass-by-reference like Java/C#. TIL. (I tried P._=Q._=[] in your function, but apparently they reference the same list in JS, which I didn't know.) Nice answer btw, and pretty readable for such a long a codegolf answer! :) Dec 16, 2021 at 11:08 # Rust, 576 bytes |c:&[&str]|{let c:Vec<Vec<_>>=c.iter().map(|s|s.split(' ').collect()).collect();let(r,n,mut p)=(|r:&str|r.as_bytes()[0]as usize,|n:&str,f|n.parse::<i64>().unwrap_or(f),(vec![0;123],vec![0;123]));p.0[112]=1;while p.0[1]+p.1[1]<2{p=(p.1,p.0);let(c,m)=(c.get(p.0[0]as usize)?,&mut p.0);let x=r(c[1]);if c[0]=="snd"{p.1.push(n(c[1],m[x]));}else if c[0]=="rcv"{if m.len()>123{m[x]=m.remove(123);m[1]=0}else{m[1]=1;m[0]-=1}}else{let y=n(c[2],m[r(c[2])]);match c[0]{"set"=>m[x]=y,"add"=>m[x]+=y,"mul"=>m[x]*=y,"mod"=>m[x]%=y,"jgz" if n(c[1],m[x])>0=>m[0]+=y-1,_=>()}}m[0]+=1}Some(1)}  Try it online! Fun! Ungolfed: |cmds: &[&str]| { let cmds: Vec<Vec<_>> = cmds.iter().map(|s| s.split(' ').collect()).collect(); // split commands let (register, number_or, mut process) = ( |r: &str| r.as_bytes()[0] as usize, |n: &str, f| n.parse::<i64>().unwrap_or(f), (vec![0; 123], vec![0; 123]), // process state: // 0 - program counter // 1 - rcv signal // b'a'..=b'z' - registers // b'z'+1.. - rcv queue ); process.0[112] = 1; // b'p' == 112 while process.0[1] + process.1[1] < 2 { // while not deadlocked process = (process.1, process.0); // swap processes let (cmd, memory) = (cmds.get(process.0[0] as usize)?, &mut process.0); let x = register(cmd[1]); if cmd[0] == "snd" { process.1.push(number_or(cmd[1], memory[x])); } else if cmd[0] == "rcv" { if memory.len() > 123 { memory[x] = memory.remove(123); memory[1] = 0 } else { memory[1] = 1; memory[0] -= 1 // don't advance program } } else { let y = number_or(cmd[2], memory[register(cmd[2])]); match cmd[0] { "set" => memory[x] = y, "add" => memory[x] += y, "mul" => memory[x] *= y, "mod" => memory[x] %= y, "jgz" if number_or(cmd[1], memory[x]) > 0 => memory[0] += y - 1, _ => (), } } memory[0] += 1 } Some(1) };  # Ruby, 537 ... 312 bytes ->s{g=[{},{p:1}];*q=[],[];*a=0,0;r=s.lines;(2.times{|x|l,m,n=r[a[x]].split h="g[x][:" eval [[k=[z=h+m+?],y=?(+z+"||0)"]*?=,t=n&&n>?9?"(#{h+n}]||0)":n]*?%,"q[x][0]?(#{z}=q[x].shift):a[x]-=1",k+?++t,z+?=+t,"q[1-x]<<#{d=m&&m>?9?y:m}","#{d}>0&&a[x]+=#{t}-1",k+?*+t][l[1].ord%37%8] a[x]+=1} )while a.min>=0&&r[a.max]}  Try it online! A little more golfy. Still very copy-pasty. # Charcoal, 224 bytes ＷＳ⊞υ⪪ι ≔Ｅ²⊞ＯＥ²⁸∧ι⁼¹⁵λ¹θＷ∧⊙θ§κ²⁸⬤θ⁼§κ²⁶﹪§κ²⁶Ｌυ«≔§θ⁰η≔⮌θθ≔§υ§η²⁶ζ≔⌕β§ζ¹ε≔⌕β§ζ±¹δ≔⎇⊕δ§ηδＩ§ζ±¹δ≡§§ζ⁰¦¹e§≔ηεδd§≔ηε⁺§ηεδu§≔ηε×§ηεδo§≔ηε﹪§ηεδgＦ‹⁰⎇⊕ε§ηεＩ§ζ¹§≔η²⁶⁺§η²⁶⊖δc«§≔η²⁸‹§η²⁷⁻Ｌη²⁹§≔ηε§η⁺²⁹§η²⁷§≔η²⁷⁺§η²⁷§η²⁸»⊞§θ⁰δ§≔η²⁶⁺§η²⁶§η²⁸  Try it online! Link is to verbose version of code. Takes input as a list of newline-terminated instructions. Explanation: ＷＳ⊞υ⪪ι  Read the instructions, splitting on spaces. ≔Ｅ²⊞ＯＥ²⁸∧ι⁼¹⁵λ¹θ  Create two threads of execution. Each thread is represented by an array of values. The first 26 represent the variables a-z, then comes the program counter, the receive counter, and the running flag, which is normally 1 but gets reset to 0 if a rcv instruction is executed when the receive counter exceeds the number of items in the send list. Any further items are the send list. Items are not removed from the send list, instead the receive counter is used to determine which the next item is. Ｗ∧⊙θ§κ²⁸⬤θ⁼§κ²⁶﹪§κ²⁶Ｌυ«  Repeat until either both running flags are zero or at least one program counter is out of range. ≔§θ⁰η  Get the current thread. ≔⮌θθ  Schedule the other thread to become the current thread. ≔§υ§η²⁶ζ  Get the current instruction. ≔⌕β§ζ¹ε  Get the index of the first argument, assuming it's a variable. ≔⌕β§ζ±¹δ  Get the index of the last argument, assuming it's a variable. ≔⎇⊕δ§ηδＩ§ζ±¹δ  Evaluate the last argument as a variable or a constant as appropriate. ≡§§ζ⁰¦¹  Switch over the second character of the instruction, as it's unique across all instructions. e§≔ηεδ  For set simply assign the evaluated last argument to the index given by the first argument. d§≔ηε⁺§ηεδ  Add the last argument to the first argument. u§≔ηε×§ηεδ  Multiply the first argument by the last argument. o§≔ηε﹪§ηεδ  Modulo the first argument by the last argument. gＦ‹⁰⎇⊕ε§ηεＩ§ζ¹§≔η²⁶⁺§η²⁶⊖δ  For jgz evaluate the first argument as a variable or constant and if it's positive then update the program counter by the given offset (but taking the autoincrement into account). c«  If this is rcv then... §≔η²⁸‹§η²⁷⁻Ｌη²⁹  Calculate whether this thread is stalled. §≔ηε§η⁺²⁹§η²⁷  Try to get the value out of the send list anyway. (If there aren't enough items, Charcoal will fetch a meaningless value, but it will be overwritten by the correct value once it's been added to the send list.) §≔η²⁷⁺§η²⁷§η²⁸  Increment the receive pointer if there was a waiting item in the send list. »⊞§θ⁰δ  Otherwise this is snd so push the last argument to the (now other thread's) send list. §≔η²⁶⁺§η²⁶§η²⁸  Increment the program counter, unless the thread is stalled on a receive. # TypeScript Types, 1203 bytes //@ts-ignore type a<T,A=0,N=[]>=T extends${N["length"]}?N:a<T,A,[...N,A]>;type b<T,P=0,N=[1,0][P]>=T extends[N,...infer T]?T:[...T,P];type c<T,U,P=0>=T extends[infer X,...infer T]?c<T,b<U,[P,[1,0][P]][X]>>:U;type d<T,U,N=[]>=T extends[infer X,...infer T]?d<T,c<U,N,X>>:N;type e<T,U>=T extends[...{[K in keyof U]:{}},...infer T]?e<T,U>:T;type f<T>=T extends${number}?T extends-${infer U}?a<U,1>:a<T>:T;type g<M,R,V>=Omit<M,R>&Record<R,V>;type h<M,A>=A extends keyof M?M[A]:A extends{}[]?A:[];type i<S,P=[]>=S extends${infer I}\n${infer R}?I extends${infer O}${infer X}${${infer Y}|""}?i<R,[...P,[O,f<Exclude<X,${string}${string}>>,f<Y>]]>:0:P;type j<k,l={p:[]},m=[],n=[],o={p:[0]},p=[],q=[],r=0>=[m,${m["length"]}]extends[0[],keyof k]?k[m["length"]]extends[infer s,infer X,infer Y]?[h<l,X>,h<l,Y>]extends[infer t,infer u]?{set:j<k,g<l,X,u>,b<m>,n,o,p,q>,add:j<k,g<l,X,c<t,u>>,b<m>,n,o,p,q>,mul:j<k,g<l,X,d<t,u>>,b<m>,n,o,p,q>,mod:j<k,g<l,X,e<t,u>>,b<m>,n,o,p,q>,jgz:t extends 1[]?j<k,l,b<m>,n,o,p,q>:j<k,o,p,q,l,c<m,u>,n>,snd:j<k,l,b<m>,n,o,p,[...q,h<l,X>]>,rcv:n extends[infer v,...infer w]?j<k,g<l,X,v>,b<m>,w,o,p,q>:r extends 1?0:j<k,o,p,q,l,m,n,1>}[s]:0:0:0;type M<T>=j<i<${T}
>>


Try It Online!

## Ungolfed / Explanation

// Converts a stringified number to a tuple representation
type StrNumToTuple<T, A=0, N=[]> = T extends ${N["length"]} ? N : StrNumToTuple<T, A, [...N, A]> // Increment/decrement an integer stored as e.g. [0, 0] for 2, [1, 1, 1] for -3, and [] for 0 // Invoked as Inc<T> to increment, or Inc<T, 1> to decrement type Inc<T, Pos = 0, Neg = [1, 0][Pos]> = T extends [Neg, ...infer T] ? T : [...T, Pos] // Add/subtract two integers // Invoked as Add<T, U> to add, or Add<T, U, 1> to subtract type Add<T, U, P = 0> = T extends [infer X, ...infer T] ? Add<T, Inc<U, [P,[1,0][P]][X]>> : U // Multiply two integers type Mult<T, U, N = []> = T extends [infer X, ...infer T] ? Mult<T, Add<U, N, X>> : N // Modulo two integers type Mod<T, U> = T extends [...{ [K in keyof U]: {} }, ...infer T] ? Mod<T, U> : T // Parse an argument (like "a" or "-3") into either a register ("a") or an integer ([1, 1, 1]) type ParseArg<T> = T extends ${number} ? T extends -${infer U} ? StrNumToTuple<U,1> : StrNumToTuple<T> : T // Set a register in memory type SetArg<Mem, Reg, Val> = Omit<Mem, Reg> & Record<Reg, Val> // Get the value of an argument type GetArg<Mem, Arg> = Arg extends keyof Mem ? Mem[Arg] : Arg extends {}[] ? Arg : [] type ParseProgram<Str, Prog = []> = // Get the first line of Str Str extends ${infer Inst}\n${infer Rest} // Garbage parsing logic ? Inst extends ${infer Op} ${infer X}${ ${infer Y}|""} ? ParseProgram<Rest, [...Prog, [Op, ParseArg<Exclude<X,${string} ${string}>>, ParseArg<Y>]]> : 0 // Str is empty : Prog type Run< Prog, // Active process MemA={p:[]}, IpA=[], QueueA=[], MemB={p:[0]}, // Inactive process IpB=[], QueueB=[], // Set to 1 when rcv is called with an empty queue; used to detect deadlock JustSwapped=0 > = // If IpA > 0 and < Prog.length [IpA, ${IpA["length"]}] extends [0[], keyof Prog]
// Store the parts of the current instruction in Op, X, and Y
? Prog[IpA["length"]] extends [infer Op, infer X, infer Y]
// Get the values at X and Y and store them in XV and YV, respectively
? [GetArg<MemA, X>, GetArg<MemA, Y>] extends [infer XV, infer YV]
// Switch on Op
? {
set: Run<Prog, SetArg<MemA, X, YV>, Inc<IpA>, QueueA, MemB, IpB, QueueB>
mul: Run<Prog, SetArg<MemA, X, Mult<XV, YV>>, Inc<IpA>, QueueA, MemB, IpB, QueueB>
mod: Run<Prog, SetArg<MemA, X, Mod<XV, YV>>, Inc<IpA>, QueueA, MemB, IpB, QueueB>
jgz:
// If XV <= 0
XV extends 1[]
? Run<Prog, MemA, Inc<IpA>, QueueA, MemB, IpB, QueueB>
// Swap to the other process (to protect against infinite loops on only one thread)
: Run<Prog, MemB, IpB, QueueB, MemA, Add<IpA, YV>, QueueA>
snd: Run<Prog, MemA, Inc<IpA>, QueueA, MemB, IpB, [...QueueB, GetArg<MemA, X>]>
rcv:
// Get the first value in QueueA
QueueA extends [infer Val, ...infer NewQueue0]
? Run<Prog, SetArg<MemA, X, Val>, Inc<IpA>, NewQueue0, MemB, IpB, QueueB>
// QueueA is empty
: JustSwapped extends 1
? 0
// Swap to the other process, enabling JustSwapped
: Run<Prog, MemB, IpB, QueueB, MemA, IpA, QueueA, 1>
}[Op]
: 0 // unreachable
: 0 // unreachable
// Otherwise, halt
: 0


# Python3, 686 bytes:

import operator as o
u=lambda g,n:n.get(g,0)if g.isalpha()else int(g)
def e(s,n,i,q,w,r):
k=i['i']
if q and r:n[r.pop()]=q.pop();i['i']+=(not r);return
if (v:=s[k][0])=='set':n[s[k][1]]=u((g:=s[k][2]),n);i['i']+=1
elif v=='snd':w.append(u(s[k][1],n));i['i']+=1
elif v=='rcv':
if q:n[s[k][1]]=q.pop();i['i']+=1
else:r.append(s[k][1])
elif v=='jgz':i['i']=i['i']+(1 if not u(s[k][1],n) else u(s[k][2],n))
else:n[s[k][1]]=getattr(o,s[k][0])(n[s[k][1]],u(s[k][2],n));i['i']+=1
def f(p):
p=[i.split()for i in p.split('\n')]
n,m,i,t,q,w,r,s={'p':0},{'p':1},{'i':0},{'i':0},[],[],[],[]
while 0<=i['i']<len(p)and 0<=t['i']<len(p)and not any([r,s]):
e(p,n,i,q,w,r);e(p,m,t,w,q,s)


Try it online!

• You can swap the comparisons if v=='snd' -> if'snd'==v to save a byte on whitespace Dec 16, 2021 at 1:25
• Also, since the set of instructions is so limited, you can probably use some lexicographic comparisons with < and > instead of == Dec 16, 2021 at 1:27
• and not any([r,s]) -> and not(r or s) Dec 16, 2021 at 1:28
• (1 if not u(s[k][1],n) else u(s[k][2],n)) -> (not u(s[k][1],n)or u(s[k][2],n)). You have a few unneeded spaces as well, like in if (v:=... and n[s[k][1]], u(s[k][2],n). Dec 16, 2021 at 1:30
• The [i.split()for i in p.split('\n')] can be combined into the inside of f so you can remove the l function Dec 16, 2021 at 1:30

# Wolfram Language (Mathematica), 373 bytes

(Clear@A;P=ToExpression@StringSplit@#;S=A/@{0,1};(#@_=0;#@p=#[[1]];#@i_?NumberQ:=i;#@C={};#@N=1)&/@S;While[0<#@N<=Length@P&/@And@@S&&(#@C!={}||P[[#@N,1]]=!=rcv&/@Or@@S),(P[[#@N]]/.{i_,x_,y_:0}:>Switch[Y=#@y;i,set,#@x=Y,add,#@x+=Y,mul,#@x*=Y,mod,#@x=#@x~Mod~Y,jgz,#@x>0&&(#@N+=Y-1),snd,(1-#&/@#)@C~AppendTo~#@x,_,If[#@C!={},{{#@x},#@C}=#@C~TakeDrop~1,#[N]--]];#[N]++)&/@S])&


Try it online!

# Java 10, 630627 625 bytes

import java.util.*;L->{Stack<Long>m=new Stack(),q=new Stack();long r[][]=new long[2][26],V,P,t;++r[0][15];for(int c,f=1,g=1,d=1,i=0,j=0,o,v,p;f>-g&d>0;)for(c=2;c--*d>0;d=d>=0&d<L.length?1:0)try{f=g;V=P=g=1;var l=L[d=c<1?j:i];var x=l.split(" ");o=l.charAt(1);l=x[x.length-1];v=(v=l.charAt(0))>57?v-(int)(V=97):new Integer(l);V=V>1?r[c][v]:v;t=r[c][p=x.length>2?(p=x[1].charAt(0))>57?p-(int)(P=97):new Integer(x[1]):0];P=P>1?t:p;r[c][p]=o=='e'?V:o=='d'?t+V:o>116?t*V:o=='o'?t%V:t;l=L[o=='g'&P>0?c<1?j+=V-1:(i+=V-1):0];if(o=='n')(c<1?q:m).add(0,V);if(o<100)r[c][p]=(c<1?m:q).pop();l=L[c<1?j+=g:(i+=g)];}catch(Exception e){g=0;}}


-5 bytes thanks to @ceilingcat.

Input as a String-array.

Try it online.

Explanation:

import java.util.*;          // Import for 3x Stack
L->{                         // Method with String-array parameter and no return
Stack<Long>m=new Stack(),  //  Message-queue of process 1
q=new Stack();  //  Message-queue of process 0
long r[][]=new long[2][26],//  Registers, starting at 26x 0
//  Index 0 is process 1, index 1 is process 0
V,P,t;                //  Value V, Register-position P, temp t
++r[0][15];                //  Increase 'p' in process 1 by 1
for(int c,                 //  Current process
f=1,g=1,               //  Process flag-integers
d=1,                   //  Bounds flag-integer
i=0,                   //  Index-integer of process 0
j=0,                   //  Index-integer of process 1
o,v,p;                 //  Operation o, Value v, Register-position p
//  (we use V/P as actual values, and v/p
//  as indices in case we need to modify data at
//  those positions)
f>-g                   //  Loop as long as one process-flag is still 1,
&d>0;)                 //  and the indices are within bounds:
for(c=2;c--              //   Inner loop c in the range (2,0],
*d>0                 //   as long as the indices are within bounds:
;                    //     After every iteration:
d=d>=0&d<L.length?  //      If d is within bounds:
1                //       Set it as flag to 1
:                 //      Else:
0)               //       Set it as flag to 0
try{f=g;               //    Save flag-value g in flag-f
V=P=g=1;             //    Reset flag-g, V, and P to 1
var l=L[d=c<1?j:i    //    Set d to the index of the current process
];          //    Get the current String-line
var x=l.split(" ");  //    Split this line on spaces
o=l.charAt(1);       //    Set o to the second character of the line
l=x[x.length-1];     //    Set l to the last value of the split line,
//    which is either the third if there are three,
//    or the second if there are two
v=(v=l.charAt(0))>57?//    If this is a letter:
v-(int)(V=97)     //     Convert it to an index,
//     and set V to something larger than 1
:                  //    Else (it's a number instead):
new Integer(l);   //     Convert it from String to integer
V=V>1?               //    If V is now larger than 1:
r[c]              //     Get the values of the current process
[v]           //     and set V to the v'th value
:                  //    Else:
v;                //     Set V to v
t=r[c]               //    Get the values of the current process
[p=                //    and set t to the p'th value,
//    where p is:
x.length>2?     //     If the line has three parts:
(p=x[1].charAt(0))>57?
//      If the second part is a letter:
p-(int)(P=97) //       Convert it an index,
//       and set P to something larger than 1
:              //      Else:
new Integer(x[1])
//       Convert it from String to integer
:               //     Else:
0;             //      Simply set it to 0
P=P>1?               //    If P is now larger than 1:
t                 //     Set P to this t
:                  //    Else:
p;                //     Set P to p
r[c][p]=             //    Set the p'th value to:
o=='e'?      //     If the line is a 'set':
V           //      Simply set it to V
:o=='d'?     //     Else-if the line is an 'add':
t+V         //      Set it to t plus V
:o>116?      //     Else-if the line is a 'mul':
t*V         //      Set it to t multiplied by V
:o=='o'?     //     Else-if the line is a 'mod':
t%V         //      Set it to t modulo-V
:            //     Else:
t;          //      Keep its same value by setting to t
l=L[o=='g'           //    If the line is 'jgz': †
&P>0?            //     If P is larger than 0:
c<1?            //      If it's process 1:
j+=V-1         //       Increase index j by V minus 1
:               //      Else (it's process 0):
(i+=V-1):0];   //       Increase index i by V minus 1
if(o=='n')           //    If the line is 'snd':
(c<1?              //     If it's process 1:
q                //      Use Message-queue q
:                 //     Else (it's process 0):
m                //      Use Message-queue m instead
).add(0,V);        //     And prepend V to this Message-queue
if(o<100)            //    If the line is 'rcv':
r[c][p]=           //     Set the p'th value to:
(c<1?             //      If it's process 1:
m               //       Use Message-queue m
:                //      Else (it's process 0):
q               //       Use Message-queue q
).pop();          //      And pop and get its top value
l=L[c<1?             //    If it's process 1: †
j+=g            //     Increase index j by g
:                //    Else (it's process 0):
(i+=g)];        //     Increase index i by g
}catch(Exception e){   //   If an error occurs (which is either an
//   ArrayIndexOutOfBoundsException if an i/j/
//   p/v we've used for indexing is out of
//   bounds, or an EmptyStackException if we tried
//   pop() on an empty Message-queue)
g=0;}}               //     Set flag g to 0


†: l=L[o=='g'&P>0?c<1?j+=V-1:(i+=V-1):0]; and l=L[c<1?j+=g:(i+=g)]; may seem like an odd way to increase j/i based on the if-condition, since why do we set it to l and get it from L? But this is 3 bytes shorter than the traditional way: if(o=='g'&P>0)if(c<1)j+=V-1;else i+=V-1; and if(c<1)j+=g;else i+=g;