Execute SNUSP/D: Difference between revisions

From Rosetta Code
Content added Content deleted
(+D)
 
m (Fixed syntax highlighting.)
 
(11 intermediate revisions by 5 users not shown)
Line 1: Line 1:
{{implementation|SNUSP}}{{collection|RCSNUSP}}
This implementation includes all '''Bloated SNUSP''' commands, plus a minor custom feature : ''debugInput''. ''debugInput'' is a pattern to setup a pre-defined string for (I/O) input. That is, just before reading I/O input operation, if this string is not empty yet, a char is ''pop/taken'' from the string, instaed of actually read from I/O. The pattern is '''debug['''<''string''>''']debug'''. Similar to the staring point '$', this pattern is searched inside the source code before execution, but from bottom lines first.<br> <br>
The following are some notes.


This [[D]] implementation supports commands from all the three SNUSP variants, as described on the [[eso:SNUSP|Esolang SNUSP page]], plus an extended mode, '''SUPERNATURAL'''.
*'''Memory'''
**The memory space is represented by a class ''Mem''. Since Bloated SNUSP allows memory pointer to move up and down, the memory space is a 2-dimensional space;
**Mem is internally represented as an associative array, with ''cfloat'' as key and ''int'' as value. Externally, the ''keys'' is a integer 2-dimensional coordinates tuple (x,y), which acts as a memory pointer. The tuple will convert to cfloat before accessing the associative array. The conversion is unique as long as the magnitude of x and y is less than 0x7fffff. The type of the memory cells is ''int'', it will be only interpreted as ''char''/''string'' during IO operation;
*'''Code Pointer'''
**This again is in a 2-dimensional space. The Code pointer CP structure contain the pointer position and moving direction. It also includes function to get the ''command code'' in the SNUSP program source under the pointer position, and to manipulate its own position and direction;
**If CP go out of the code space, no error is threw. In this implementation, such situation is treated as a sub-routine return (#);
*'''Core/Thread/Execution Unit''''
**This class is which actually execute the command. In fact, ''execute'' is the only non-trivial function (beside the ''fwd'' short hand ) inside this class. Each Core owns a code pointer, a memory pointer (actually 2 int) and a cp call stack, and shared with the same ''CPU''. Via the CPU, each Core can accessed the common resources like IO, Memory Space and Code Space(program) ;
**Since this implementation supported ''Bloated'' feature, aka thread execution, this class is a result of separating execution unit from ''CPU''.
*'''CPU'''
**CPU acts as a resource manager and thread execution scheduler(trivially).
*'''IO'''
**This may be the simplest class.
<br>
'''Note :'''This implementation should run fine in both D1 & D2.
<d>module rcsnusp ;
import std.stdio, std.c.stdio, std.string, std.random, std.file ;


'''SUPERNATURAL Mode''':
// array stack template
*'''~ : code input''' (materialization):
T[] push(T)(inout T[] stk, T value) { stk ~= value ; return stk ; }
#Read the char in current code pointer as input, assign it to memory currently pointed to by memory pointer.
T pop(T)(inout T[] stk, bool discard = true)
*'''* : join and wait tasks''' (telepathy):
{ T top = stk[$-1] ; if(discard) stk.length = stk.length - 1 ; return top ; }
#A task has a state property of free/join/wait;
bool empty(T)(T[] stk) { return stk.length <= 0 ; }
#A task is by default initialized as free-state;
#The splited new thread inherites free/join/wait-state from the old thread;
#When a free-state task first executes this '''*''' command, the task enters into a join-state;
#Then if a join-state task executes another '''*''' command, the task enters into a wait-state;
#A wait-state task stops its execution, and waits until all alive join-state tasks are turning into a wait-state, which then all these wait-state tasks are return to free-state;
#This command enables global synchronization of the tasks.
#Difference to original specification:
::*The original specification does not require threads execution in a specific order. For this command to be useful, the order of execution of tasks(threads) becomes important;
::*In this implementation, the order of execution is first-created-first-executed;
::*The original specification specifies that ''(&)SPLIT''-ed old thread skips the immediate code (see below a->b->c->d example), which may lead to anti-intuition codes (which is good for an esoteric language :). This implementation retain old-thread-skips-immediate-code behavior in ''BLOATED'' mode, but new-thread-skips-immediate-code in ''SUPERNATURAL'' mode ( A->B->C->D example). 1->2->...->8 is thread creation order in ''SUPERNATURAL'' mode.
:::<tt style="line-height: 1em;">$*&\==&\:&\:&\:=\<br>&nbsp;&nbsp;&nbsp;;&nbsp;&nbsp;&nbsp;~&nbsp;&nbsp;~&nbsp;&nbsp;~&nbsp;&nbsp;~&nbsp;&\=>&nbsp;new(B)<br>&nbsp;&nbsp;&nbsp;;&nbsp;&nbsp;2A&nbsp;3B&nbsp;4C&nbsp;5D&nbsp;&nbsp;\=>&nbsp;old(A)<br>&nbsp;&nbsp;&nbsp;;&nbsp;&nbsp;&nbsp;\=!\=!\=!\===*..=.#<br>&nbsp;&nbsp;&nbsp;;&nbsp;&nbsp;&nbsp;/=!/=!/=!/===*.=.=.#<br>&nbsp;&nbsp;&nbsp;;&nbsp;&nbsp;1b&nbsp;6c&nbsp;7d&nbsp;8a&nbsp;&\=>&nbsp;old(a)<br>&nbsp;&nbsp;&nbsp;;&nbsp;&nbsp;&nbsp;~&nbsp;&nbsp;~&nbsp;&nbsp;~&nbsp;&nbsp;~&nbsp;&nbsp;\=>&nbsp;new(b)<br>&nbsp;&nbsp;&nbsp;\==&/;&/;&/;=/&nbsp;&nbsp;<br></tt>
*'''^ : warp''' (teleport):
#The code pointer will bounce back to the code space boundary in its reverse direction;
#then forward and stop after the first '''^''' it encounter in normal direction.
#Example :
:::==a^A&lt;=cp==B^==&lt;=C^==
::when the code pointer cp heading into A's^, next turn, the code pointer will be in C. if no such ^ in the reverse direction, the code pointer will be in ''a'' next turn.
<syntaxhighlight lang="d">module snud ;
private import std.string, std.random ;


// io interface, which has to be defined in another module
// quick element remove for non-ordered array
interface IIO {
void rem(T)(inout T[] stk, int i) {
void setDebugInput(string s) ;
if(i + 1 < stk.length) stk[i] = stk[$-1] ;
void output(int v) ;
stk.length = stk.length - 1 ;
bool inputReady() ;
int input() ;
}
}


int rnd(int b) { return b < 0 ? (-rand()) % (-b + 1) : rand() % (b + 1) ; }
class Mem { // Memory
// simple stack template
private int[cfloat] cells ;
void push(T)(inout T[] stk, T value) { stk ~= value ; }
static const int MASK = 0xffffff ;
T pop(T)(inout T[] stk, bool discard = true) {
static cfloat i2cf(int x, int y) { return (x & MASK) + (y & MASK)*1fi ; }
T top = stk[$-1] ;
int opIndex(int x, int y) {
auto key = i2cf(x,y) ; int res ;
if(discard) stk.length = stk.length - 1 ;
return top ;
if(key in cells) res = cells[key] ; else cells[key] = res = 0 ;
return res ;
}
void opIndexAssign(int value, int x, int y) { cells[i2cf(x,y)] = value ; }
void reset() { foreach(k, v ; cells) cells[k] = 0 ; }
}
}


// a 2x tuple type
struct CP { // Codes Pointer
struct X(U,V = U) {
int x, y, dx, dy ;
U x ; V y ;
static CP opCall(int h, int v, int hd, int vd)
{ CP c ; with(c) { x = h ; y = v ; dx = hd ; dy = vd ; } ; return c ; }
void to(ref U a, ref V b) { a = x ; b = y ; }
void from(U a, V b) { x = a ; y = b ; }
static CP opCall(string[] codes) {
int v, h = -1 /* _find_ return -1 if not found */;
for(v = 0 ; v < codes.length ; v++) // find location of 1st '$'
if((h = find(codes[v], '$')) != -1) break ;
if (h == -1) { h = 0 ; v = 0 ; } // default 1st char of 1st line
else h++ ; // advanced pass the '$' char found
return CP(h, v, 1, 0) ; // default direction is (1,0)
}
CP dup() { return CP(x,y,dx,dy) ; }
bool hasCode(string[] codes)
{ return !(y < 0 || x < 0 || y >= codes.length || x >= codes[y].length) ; }
string getCode(string[] codes) { return hasCode(codes) ? codes[y][x..x+1] : null ; }
void fwd(int step = 1) { x += dx*step ; y += dy*step ; }
void turn(string t) {
if(t == "\\" || t == "/") {
int sign = t == "/" ? -1 : 1 ;
int tx = dy*sign , ty = dx*sign ;
dx = tx ; dy = ty ;
}
}
}
}
alias X!(int) I2 ; // intxint, used as code pointer, memory pointer & direction
alias X!(I2) ST ; // (intxint)x(intxint), used as cpu state [cp,dp]


enum Mode : uint {CORE = 1, MODULAR, BLOATED, SUPERNATURAL } ;
class Core { // this is _thread_ :)
bool quit = false ;
this(CPU c, CP p, int xm, int ym)
{ cpu = c ; cp = p ; mx = xm ; my = ym ; }


// used to locate '$' and debugInput
void fwd(int step = 1) { cp.fwd(step) ; } // short hand
string pfind(I2* loc, string[] c, string sl, string sr = null) {
void execute() {
int rx, dir = 1 , start = 0 , end = c.length - 1 ;
if(quit) throw new Exception("Execute a dead core") ;
if(sr){ dir = -1 ; start = c.length - 1 ; end = 0 ; }
string code = cp.getCode(cpu.prog) ;
with(*loc)
if(code is null) code = "#" ; // treat as _return_ if out of code space
for(x = -1, y = start ; y*dir <= end*dir ; y += dir)
Mem m = cpu.mem ;
if((x = std.string.find(c[y], sl)) >= 0) {
switch(code) {
case "<" : mx-- ; fwd ; break ;
if(sr && (rx = std.string.rfind(c[y], sr)) >= 0)
case ">" : mx++ ; fwd ; break ;
if(rx > x + sl.length)
case ":" : my-- ; fwd ; break ;
return c[y][x + sl.length..rx] ;
case ";" : my++ ; fwd ; break ;
case "+" : m[mx,my] = m[mx,my] + 1 ; fwd ; break ;
case "-" : m[mx,my] = m[mx,my] - 1 ; fwd ; break ;
case "," :
string ss = cpu.io.get() ;
if (ss.length > 0) {
m[mx,my] = cast(int) ss[0] ;
fwd ;
} // else do nothing and not forward cp, simulate waiting input
break ;
break ;
}
case ".": cpu.io.put(m[mx,my]) ; fwd ; break ;
return null ;
case "!": fwd(2) ; break ; // skip one step == forward x 2
case "?": fwd ; if(m[mx,my] == 0) fwd ; break ;
case "%": m[mx,my] = cpu.rnd(m[mx,my]) ; fwd ; break ;
case "&": // split core/thread
fwd ;
// will run following new core in next turn, with cp, mx, my dulplicated
cpu.addCore(new Core(cpu, cp.dup, mx, my)) ;
fwd ; // old core skip one more step
break ;
case "@": stack.push(cp.dup) ; fwd ; break ;
case "#":
if(stack.empty()) quit = true ; // ready to quit
else { cp = stack.pop() ; fwd(2) ; } // return from subroutine
break ;
case "\\","/": cp.turn(code) ; fwd ; break ;
default: fwd ;
}
}

private :
CPU cpu ;
CP[] stack ; // call stack ;
CP cp ; // current code pointer
int mx, my ; // memory pointer
}
}


// a 2d memory space
class IO {
final class Memory {
private char[] debugInput ;
private int[I2] cells ;
string get() {
void reset() { foreach(k, v ; cells) cells[k] = 0 ; }
if(debugInput.empty())
void opIndexAssign(int value, I2 key) { cells[key] = value ; }
return kbhit() ? [cast(char)getch()] : null ;
int opIndex(I2 key) {
return [debugInput.pop()] ;
int* vp = key in cells ; // get value pointer of the key, null if no such key
}
if(vp is null) { cells[key] = 0 ; return 0 ; } // initialize the value/key pair
void put(int c) { printf("%c", cast(char)c) ; }
return *vp ; // return the already existed value
void getDebugInput(string[] codes) {
for(int i = codes.length - 1 ; i >= 0 ; i--) {
int left = std.string.find(codes[i], "debug[") ;
int right = std.string.rfind(codes[i],"]debug") ;
if(right > left + 6) {
(debugInput = codes[i][left+6..right].dup).reverse ;
break ;
}
}
}
}
}
}


class CPU {
final class CPU {
final class Task {
private string[] program ;
enum {FREE, JOINED, WAITING }
private Mem memory ;
private IO inOut ;
const int id ;
I2 cp, dp, mp ;
private Core[] cores ;
private Core[] newcores ;
int join = FREE ;
bool quit = false ;
private ST[] stack ;
private char curCode ;


this(I2 Cp, I2 Dp, I2 Mp, int joinstate = FREE) {
static int rnd(int b) { // return a random number between and include 0 and b
cp = Cp ; dp = Dp ; mp = Mp ; id = Id++ ;
int a = 0 ;
if(a > b) { a = b ; b = 0 ; }
if((join = joinstate) == JOINED) joinwait++ ;
}
return a + cast(int) (rand() % (b - a + 1)) ;

}
private void fwd(int step = 1) { with(cp) from(x + dp.x*step, y + dp.y*step) ; }
static string[] normalize(string[] codes) {
string[] c = splitlines(join(codes,"\n")) ;
private void rfx(int dir) { with(dp) from(dir*y, dir*x) ; }
// _outer_ is D keyword for an inner class to ref outer class
int lenmax = 0 ;
private bool hasCode() { return this.outer.hasCode(cp) ; }
foreach(l; c) if (l.length > lenmax) lenmax = l.length ;
char getCode() { return this.outer.getCode(cp) ; }
foreach(inout l ; c) l ~= repeat(" ", lenmax - l.length) ;
return c ;
Task execute() {
curCode = getCode ;
if(curCode in acceptCmd) // this control which SNUSP variants is used
switch(curCode) {
case '<' : mp.x-- ; break ;
case '>' : mp.x++ ; break ;
case ':' : mp.y-- ; break ;
case ';' : mp.y++ ; break ;
case '+' : m[mp] = m[mp] + 1 ; break ;
case '-' : m[mp] = m[mp] - 1 ; break ;
case ',' :
if(!io.inputReady()) goto RET ; // wait input
else m[mp] = io.input() ; break ;
case '.' : io.output(m[mp]) ; break ;
case '!' : fwd ; break ;
case '?' : if(m[mp] == 0) fwd ; break ;
case '%' : m[mp] = rnd(m[mp]) ; break ;
case '\\': rfx( 1) ; break ;
case '/' : rfx(-1) ; break ;
case '@' : stack.push(ST(cp, dp)) ; break ; // save caller state
case '#' :
if(stack.length > 0) // return from subroutine
{ stack.pop().to(cp,dp) ; fwd ; break ; }
case '\0': // else process as \0, = quit
quit = true ; goto RET ;
case '&' :
if(tasksMode == Mode.BLOATED) // old task skip immediate code
{ fwd ; queued ~= new Task(cp, dp, mp, join) ; break ; }
else // new task skip immediate code
{ fwd(2) ; queued ~= new Task(cp, dp, mp, join) ; fwd(-2) ; break ; }
case '~' : fwd ; m[mp] = getCode ; break ; // read next code as input
case '*' : // join/wait threads
switch(join) {
case FREE : join = JOINED ; joinwait++ ; break ; // schedule to join
case JOINED : join = WAITING ; joinwait-- ; goto RET ; // start waiting join
case WAITING :
if(joinwait <= 0) { join = FREE ; break ; } // all joined, release all
else goto RET ; // keep waiting
}
break ;
case '^' : // wrap to the boundary in reverse direction then stop after the 1st '^'
while(hasCode) fwd(-1) ; while(getCode != '^') fwd ; break ;
default: //other char is a error command, since it should be seived out
debug throw new Exception("unknown command") ;
}
fwd ; // next code
RET: // directly go here, if waiting input/join, or quit
lastcp = cp ; lastmp = mp ;
if(quit && join == JOINED) joinwait-- ;
return this ;
}
}
}
this() { memory = new Mem ; inOut = new IO ; inOut.getDebugInput(program) ; }
this(string codes) {program = normalize(splitlines(codes)) ; this() ; }
this(string[] codes) { program = normalize(codes) ; this() ; }


this(IIO inputoutput) { m = new Memory ; io = inputoutput ; }
string[] prog() { return program ; }

Mem mem() { return memory ; }
bool hasCode(I2 codePtr)
IO io() { return inOut ; }
{ with(codePtr) return !(x < 0 || y < 0 || x >= width || y >= lines) ; }
char getCode(I2 codePtr)
void addCore(Core c) { newcores ~= c ; }
{ with(codePtr) return hasCode(codePtr) ? src[y][x] : '\0' ; }
void run() {
if(program is null) throw new Exception("No program") ;
string program() { if(prog is null) prog = join(src,"\n") ; return prog ; }
CPU load(string codes) {
cores = [new Core(this, CP(program), 0, 0)] ; // first core
src = splitlines(codes) ; width = 0 ; lines = src.length ; prog = null ;
while(cores.length + newcores.length > 0) { // execute one command for each active core
foreach(k,l; src)
cores ~= newcores ; newcores = null ; // join and clear newcores
{ if ((src[k] = stripr(tr(l,"\0"," "))).length > width) width = src[k].length ; }
// reverse order so _rem_ will not affect those core not yet executed
foreach(k,l; src) src[k] = l ~ repeat(" ", width - l.length) ;
for(int i = cores.length - 1 ; i >= 0 ; i--)
debugInput = pfind(&start, src, "debug[", "]debug") ;
if(cores[i].quit == true) cores.rem(i) ; else cores[i].execute ;
pfind(&start, src, "$") ;
if(start.x < 0) start = I2(0,0) ; else start.x++ ;
return this ;
}
CPU initialize(Mode mode = Mode.SUPERNATURAL, bool useDebugInput = true) {
if(useDebugInput) io.setDebugInput(debugInput) ; else io.setDebugInput("") ;
tasks = [ new Task(start, I2(1,0), I2(0,0)) ] ; // 1st task
tick = 0 ; Id = 0 ; joinwait = 0 ; nTaskLeft = 1 ;
queued = null ; m.reset() ; tasksMode = mode ; acceptCmd = null ;
foreach(c ; join(command[0..mode],"")) acceptCmd[c] = c ;
return this ;
}
int run(string codes,Mode mode = Mode.SUPERNATURAL, bool useDebugInput = true)
{ return load(codes).initialize(mode, useDebugInput).run() ; }
int run() { while(nTaskLeft) run1Tick ; return m[lastmp] ; }
bool run1Tick() {
if(nTaskLeft > 0) {
nTaskLeft = 0 ; tick++ ;
foreach(tsk ; tasks) // execute & update task
if((tasks[nTaskLeft] = tsk.execute).quit == false)
nTaskLeft++ ;
tasks.length = nTaskLeft ;
if(queued) { tasks ~= queued ; queued = null ; } // join queued tasks
nTaskLeft = tasks.length ;
}
}
return nTaskLeft > 0 ;
memory.reset ;
}
}

static const string[] command = ["<>+-,.!?/\\\0","@#",":;&%","~*^"] ;

Memory m ;
IIO io ;

string prog, debugInput ;
Mode tasksMode ;
Task[] tasks, queued ;
I2 start, lastcp, lastmp ;
uint width, lines, tick, joinwait, nTaskLeft ;
private string[] src ;
private char[char] acceptCmd ;
private uint Id = 0 ;
}</syntaxhighlight>
Sample SNUSP using a console io :
<syntaxhighlight lang="d">module rcsnusp ;
import snud ;
import std.stdio, std.file, std.conv ;

extern(C) {
int kbhit() ;
int getch() ;
int printf(in char*,...);
}
}


final class CIO : IIO {
void main(string[] args) {
private string debugInput ;
void setDebugInput(string s) { debugInput = cast(string)s.dup.reverse ; }
void output(int v){ printf("%1c", cast(char) v) ; }
bool inputReady() { return debugInput.length || kbhit() ; }
int input()
{ return cast(int)(debugInput.length ? debugInput.pop() : getch()) ; }
}

int main(string[] args) {
CPU cpu = new CPU(new CIO) ;

int result ;
Mode mode = Mode.SUPERNATURAL ;
bool useDebug = true ;
if(args.length <= 1) { // from stdin
if(args.length <= 1) { // from stdin
string[] p ;
string[] p ;
string b ;
string b ;
while((b = readln()) != null) p ~= chomp(b) ;
while((b = readln()) != null) p ~= std.string.chomp(b) ;
cpu.load(std.string.join(p,"\n")).initialize(mode,useDebug) ;
(new CPU(p)).run ;
} else { // from file names
for(int i = 0 ; i < 10 ; i++) { // do some debug action
foreach(f ; args[1..$])
cpu.run1Tick ;
try {
with (cpu.tasks[0])
writefln( // debug state output
(new CPU(cast(string) std.file.read(f))).run ;
"m(%3d,%3d)=[%4d][%1s] c(%3d,%3d,%3d,%3d)=[%1s](%3d) id<%3d> jc(%1d) quit[%3s]",
}
mp.x, mp.y, cpu.m[mp], cast(char) (cpu.m[mp] >= 32 && cpu.m[mp]<128 ? cpu.m[mp] : '?'),
catch(Exception e) writefln(e.msg) ;
cp.x, cp.y, dp.x, dp.y, getCode, getCode, id, join, quit ? "YES" : "NO") ;
}
result = cpu.run(std.string.join(p,"\n"), mode, useDebug) ;
} else { // from file names
if(args.length > 2 && std.string.isNumeric(args[2]))
mode = cast(Mode) toInt(args[2]) ;
if(mode < Mode.CORE || mode > Mode.SUPERNATURAL)
mode = Mode.SUPERNATURAL ;
if(args.length > 3)
useDebug = std.string.tolower(args[3]) == "no" ? false : true ;
result = cpu.run(cast(string) std.file.read(args[1]), mode, useDebug) ;
}
}
writefln("\ntick:%d", cpu.tick) ;
}</d>

return result ;
}</syntaxhighlight>

Latest revision as of 08:42, 1 September 2022

Execute SNUSP/D is an implementation of SNUSP. Other implementations of SNUSP.
Execute SNUSP/D is part of RCSNUSP. You may find other members of RCSNUSP at Category:RCSNUSP.

This D implementation supports commands from all the three SNUSP variants, as described on the Esolang SNUSP page, plus an extended mode, SUPERNATURAL.

SUPERNATURAL Mode:

  • ~ : code input (materialization):
  1. Read the char in current code pointer as input, assign it to memory currently pointed to by memory pointer.
  • * : join and wait tasks (telepathy):
  1. A task has a state property of free/join/wait;
  2. A task is by default initialized as free-state;
  3. The splited new thread inherites free/join/wait-state from the old thread;
  4. When a free-state task first executes this * command, the task enters into a join-state;
  5. Then if a join-state task executes another * command, the task enters into a wait-state;
  6. A wait-state task stops its execution, and waits until all alive join-state tasks are turning into a wait-state, which then all these wait-state tasks are return to free-state;
  7. This command enables global synchronization of the tasks.
  8. Difference to original specification:
  • The original specification does not require threads execution in a specific order. For this command to be useful, the order of execution of tasks(threads) becomes important;
  • In this implementation, the order of execution is first-created-first-executed;
  • The original specification specifies that (&)SPLIT-ed old thread skips the immediate code (see below a->b->c->d example), which may lead to anti-intuition codes (which is good for an esoteric language :). This implementation retain old-thread-skips-immediate-code behavior in BLOATED mode, but new-thread-skips-immediate-code in SUPERNATURAL mode ( A->B->C->D example). 1->2->...->8 is thread creation order in SUPERNATURAL mode.
$*&\==&\:&\:&\:=\
   ;   ~  ~  ~  ~ &\=> new(B)
   ;  2A 3B 4C 5D  \=> old(A)
   ;   \=!\=!\=!\===*..=.#
   ;   /=!/=!/=!/===*.=.=.#
   ;  1b 6c 7d 8a &\=> old(a)
   ;   ~  ~  ~  ~  \=> new(b)
   \==&/;&/;&/;=/  
  • ^ : warp (teleport):
  1. The code pointer will bounce back to the code space boundary in its reverse direction;
  2. then forward and stop after the first ^ it encounter in normal direction.
  3. Example :
==a^A<=cp==B^==<=C^==
when the code pointer cp heading into A's^, next turn, the code pointer will be in C. if no such ^ in the reverse direction, the code pointer will be in a next turn.
module snud ;
private import std.string, std.random ;

// io interface, which has to be defined in another module
interface IIO {
  void setDebugInput(string s) ;
  void output(int v) ;
  bool inputReady() ;
  int input() ;
}

int rnd(int b)  { return b < 0 ? (-rand()) % (-b + 1) : rand() % (b + 1) ; }
// simple stack template
void push(T)(inout T[] stk, T value) { stk ~= value ; }
T pop(T)(inout T[] stk, bool discard = true) {
  T top = stk[$-1] ;
  if(discard) stk.length = stk.length - 1 ;
  return top ;
}

// a 2x tuple type
struct X(U,V = U) {
    U x ; V y ;
    void to(ref U a, ref V b) { a = x ; b = y ; }
    void from(U a, V b) { x = a ; y = b ; }
}
alias X!(int) I2 ; // intxint, used as code pointer, memory pointer & direction
alias X!(I2)  ST ; // (intxint)x(intxint), used as cpu state [cp,dp]

enum Mode : uint {CORE = 1, MODULAR, BLOATED, SUPERNATURAL } ;

// used to locate '$' and debugInput
string pfind(I2* loc, string[] c, string sl, string sr = null) {
  int rx, dir =  1 , start = 0 , end = c.length - 1 ;
  if(sr){ dir = -1 ; start = c.length - 1 ; end = 0 ; }
  with(*loc)
    for(x = -1, y = start ; y*dir <= end*dir ; y += dir)
      if((x = std.string.find(c[y], sl)) >= 0) {
        if(sr && (rx = std.string.rfind(c[y], sr)) >= 0)
          if(rx > x + sl.length)
            return c[y][x + sl.length..rx] ;
        break ;
      }
  return null ;
}

// a 2d memory space
final class Memory {
  private int[I2] cells ;
  void reset() { foreach(k, v ; cells) cells[k] = 0 ; }
  void opIndexAssign(int value, I2 key) { cells[key] = value ; }
  int opIndex(I2 key) {
    int* vp = key in cells ;  // get value pointer of the key, null if no such key
    if(vp is null) { cells[key] = 0 ; return 0 ; } // initialize the value/key pair
    return *vp ;              // return the already existed value
  }
}

final class CPU {
  final class Task {
    enum {FREE, JOINED, WAITING }
    const int id ;
    I2 cp, dp, mp ;
    int join = FREE ;
    bool quit = false ;
    private ST[] stack ;
    private char curCode ;

    this(I2 Cp, I2 Dp, I2 Mp, int joinstate = FREE) { 
      cp = Cp ; dp = Dp ; mp = Mp ; id = Id++ ; 
      if((join = joinstate) == JOINED) joinwait++ ;
    }

    private void fwd(int step = 1) { with(cp) from(x + dp.x*step, y + dp.y*step) ; }
    private void rfx(int dir) { with(dp) from(dir*y, dir*x) ; }
    // _outer_ is D keyword for an inner class to ref outer class
    private bool hasCode() { return this.outer.hasCode(cp) ; }
    char getCode() { return this.outer.getCode(cp) ; }
    Task execute() {
      curCode = getCode ;
      if(curCode in acceptCmd) // this control which SNUSP variants is used
      switch(curCode) {
        case '<' : mp.x-- ; break ;
        case '>' : mp.x++ ; break ;
        case ':' : mp.y-- ; break ;
        case ';' : mp.y++ ; break ;
        case '+' : m[mp] = m[mp] + 1 ; break ;
        case '-' : m[mp] = m[mp] - 1 ; break ;
        case ',' :
          if(!io.inputReady()) goto RET ; // wait input
          else m[mp] = io.input() ; break ;
        case '.' : io.output(m[mp]) ; break ;
        case '!' : fwd ; break ;
        case '?' : if(m[mp] == 0) fwd ; break ;
        case '%' : m[mp] = rnd(m[mp]) ; break ;
        case '\\': rfx( 1) ; break ;
        case '/' : rfx(-1) ; break ;
        case '@' : stack.push(ST(cp, dp)) ; break ; // save caller state
        case '#' :
          if(stack.length > 0) // return from subroutine
            { stack.pop().to(cp,dp) ; fwd ; break ; }
        case '\0':             // else process as \0, = quit
          quit = true ; goto RET ;
        case '&' :
          if(tasksMode == Mode.BLOATED) // old task skip immediate code
            { fwd    ; queued ~= new Task(cp, dp, mp, join) ; break ; }
          else                            // new task skip immediate code
            { fwd(2) ; queued ~= new Task(cp, dp, mp, join) ; fwd(-2) ; break ; }
        case '~' : fwd ; m[mp] = getCode ; break ; // read next code as input
        case '*' : // join/wait threads
          switch(join) {
            case FREE    : join = JOINED ;  joinwait++ ; break ;    // schedule to join
            case JOINED  : join = WAITING ; joinwait-- ; goto RET ; // start waiting join
            case WAITING :
              if(joinwait <= 0) { join = FREE ; break ; } // all joined, release all
              else goto RET ; // keep waiting
          }
          break ;
        case '^' : // wrap to the boundary in reverse direction then stop after the 1st '^'
          while(hasCode) fwd(-1) ; while(getCode != '^') fwd ; break ;
        default: //other char is a error command, since it should be seived out
          debug throw new Exception("unknown command") ;
      }
      fwd ;      // next code
  RET:           // directly go here, if waiting input/join, or quit
      lastcp = cp ; lastmp = mp ;
      if(quit && join == JOINED) joinwait-- ;
      return this ;
    }
  }

  this(IIO inputoutput) { m = new Memory ; io = inputoutput ; }

  bool hasCode(I2 codePtr)
    { with(codePtr) return !(x < 0 || y < 0 || x >= width || y  >= lines) ; }
  char getCode(I2 codePtr)
    { with(codePtr) return hasCode(codePtr) ? src[y][x] : '\0' ; }
  string program() { if(prog is null) prog = join(src,"\n") ; return prog ; }
  CPU load(string codes) {
    src = splitlines(codes) ; width = 0 ; lines = src.length ; prog = null ;
    foreach(k,l; src)
      { if ((src[k] = stripr(tr(l,"\0"," "))).length > width) width = src[k].length ; }
    foreach(k,l; src) src[k] = l ~ repeat(" ", width - l.length) ;
    debugInput = pfind(&start, src, "debug[", "]debug") ;
    pfind(&start, src, "$") ;
    if(start.x < 0) start = I2(0,0) ; else start.x++ ;
    return this ;
  }
  CPU initialize(Mode mode = Mode.SUPERNATURAL, bool useDebugInput = true) {
    if(useDebugInput) io.setDebugInput(debugInput) ; else io.setDebugInput("") ;
    tasks = [ new Task(start, I2(1,0), I2(0,0)) ] ;    // 1st task
    tick = 0 ; Id = 0 ; joinwait = 0 ; nTaskLeft = 1 ;
    queued = null ; m.reset() ; tasksMode = mode ; acceptCmd = null ;
    foreach(c ; join(command[0..mode],"")) acceptCmd[c] = c ;
    return this ;
  }
  int run(string codes,Mode mode = Mode.SUPERNATURAL, bool useDebugInput = true)
    { return load(codes).initialize(mode, useDebugInput).run() ; }
  int run() { while(nTaskLeft) run1Tick ; return m[lastmp] ; }
  bool run1Tick() {
    if(nTaskLeft > 0) {
      nTaskLeft = 0 ; tick++ ;
      foreach(tsk ; tasks) // execute & update task
        if((tasks[nTaskLeft] = tsk.execute).quit == false)
          nTaskLeft++ ;
      tasks.length = nTaskLeft ;
      if(queued) { tasks ~= queued ; queued = null ; } // join queued tasks
      nTaskLeft = tasks.length ;
    }
    return nTaskLeft > 0 ;
  }

  static const string[] command = ["<>+-,.!?/\\\0","@#",":;&%","~*^"] ;

  Memory m ;
  IIO io ;

  string prog, debugInput ;
  Mode tasksMode ;
  Task[] tasks, queued ;
  I2 start, lastcp, lastmp ;
  uint width, lines, tick, joinwait, nTaskLeft ;
  private string[] src ;
  private char[char] acceptCmd ;
  private uint Id = 0 ;
}

Sample SNUSP using a console io :

module rcsnusp ;
import snud ;
import std.stdio, std.file, std.conv ;

extern(C) {
  int kbhit() ;
  int getch() ;
  int printf(in char*,...);
}

final class CIO : IIO {
  private string debugInput ;
  void setDebugInput(string s) { debugInput = cast(string)s.dup.reverse ; }
  void output(int v){ printf("%1c", cast(char) v) ; }
  bool inputReady() { return debugInput.length || kbhit() ; }
  int input()
    { return cast(int)(debugInput.length ? debugInput.pop() : getch()) ; }
}

int main(string[] args) {
  CPU cpu = new CPU(new CIO) ;

  int result ;
  Mode mode = Mode.SUPERNATURAL ;
  bool useDebug = true ;
  if(args.length <= 1) { // from stdin
    string[] p ;
    string b ;
    while((b = readln()) != null) p ~= std.string.chomp(b)  ;
    cpu.load(std.string.join(p,"\n")).initialize(mode,useDebug) ;
    for(int i = 0 ; i < 10 ; i++) { // do some debug action
      cpu.run1Tick ;
      with (cpu.tasks[0])
      writefln( // debug state output 
        "m(%3d,%3d)=[%4d][%1s] c(%3d,%3d,%3d,%3d)=[%1s](%3d) id<%3d> jc(%1d) quit[%3s]", 
        mp.x, mp.y, cpu.m[mp], cast(char) (cpu.m[mp] >= 32 && cpu.m[mp]<128 ? cpu.m[mp] : '?'), 
        cp.x, cp.y, dp.x, dp.y, getCode, getCode, id, join, quit ? "YES" : "NO") ;
    }
    result = cpu.run(std.string.join(p,"\n"), mode, useDebug) ;
  } else {               // from file names
    if(args.length > 2 && std.string.isNumeric(args[2])) 
      mode = cast(Mode) toInt(args[2]) ;
    if(mode < Mode.CORE || mode > Mode.SUPERNATURAL) 
      mode = Mode.SUPERNATURAL ;
    if(args.length > 3) 
      useDebug = std.string.tolower(args[3]) == "no" ? false : true ;
    result = cpu.run(cast(string) std.file.read(args[1]), mode, useDebug) ;
  }
  writefln("\ntick:%d", cpu.tick) ;

  return result ;
}