Execute SNUSP/D: Difference between revisions
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> ; ~ ~ ~ ~ &\=> new(B)<br> ; 2A 3B 4C 5D \=> old(A)<br> ; \=!\=!\=!\===*..=.#<br> ; /=!/=!/=!/===*.=.=.#<br> ; 1b 6c 7d 8a &\=> old(a)<br> ; ~ ~ ~ ~ \=> new(b)<br> \==&/;&/;&/;=/ <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<=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. |
|||
<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) { |
|||
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) |
|||
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) { |
|||
if(sr && (rx = std.string.rfind(c[y], sr)) >= 0) |
|||
if(rx > x + sl.length) |
|||
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 ; |
|||
const int id ; |
|||
I2 cp, dp, mp ; |
|||
private Core[] cores ; |
|||
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( |
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) { |
|||
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) ; |
|||
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( |
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 ; |
|||
for(int i = 0 ; i < 10 ; i++) { // do some debug action |
|||
cpu.run1Tick ; |
|||
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):
- Read the char in current code pointer as input, assign it to memory currently pointed to by memory pointer.
- * : join and wait tasks (telepathy):
- A task has a state property of free/join/wait;
- 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.
- $*&\==&\:&\:&\:=\
; ~ ~ ~ ~ &\=> new(B)
; 2A 3B 4C 5D \=> old(A)
; \=!\=!\=!\===*..=.#
; /=!/=!/=!/===*.=.=.#
; 1b 6c 7d 8a &\=> old(a)
; ~ ~ ~ ~ \=> new(b)
\==&/;&/;&/;=/
- ^ : 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<=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 ;
}