Execute SNUSP/JavaScript

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

This JavaScript code is the core of a Modular SNUSP interpreter and debugger designed to be embedded on a web page.

// to be run from a page having fields with these ids:
// raw: large textarea for code input
// code: empty PRE block for showing the program execution
// input: single line, from which ',' gets input
// eof: checkbox, if checked return 0 on EOF else halt
// output: PRE block, destination for output of '.'
// mem: single line, final contents of memory
 
// buttons can execute these commands:
// format: copy user input in raw to code
// snusp: run loop that runs to completion
// step: single step and highlight current position
// uses number from field id "repeat" to step n times
// out: step until we unnest (hit '#' character)
// slow: toggle running steps on a timer
// sets milliseconds per step from field id "speed"
 
var code = "#"; // formatted code
var width; // length of each line in code
var ip = 0; // current instruction within code (mod by $, ?, !, #)
var dir = 1; // current direction (mod by /, \, #)
 
var data = [0]; // data array (mod by +, -)
var dp = 0; // index into data (mod by <, >)
 
var inp = 0; // current input character (fetch with ,)
var quit = 0; // termination flag
 
var stack = []; // call stack (mod by @, #)
 
var commands = {
'>':function() { if (++dp >= data.length) data[dp]=0 },
'<':function() { if (--dp < 0) quit++ },
'+':function() { ++data[dp] },
'-':function() { --data[dp] },
'/':function() { dir = -width / dir },
'\\':function() { dir = width / dir },
'!':function() { ip += dir },
'?':function() { if (!data[dp]) ip += dir },
',':function() {
data[dp] = document.getElementById("input").value.charCodeAt(inp++);
if (isNaN(data[dp])) { // EOF
--inp;
data[dp] = 0;
if (!document.getElementById("eof").checked) quit++;
}
},
'.':function() {
var s = document.getElementById("output").innerHTML
+ String.fromCharCode(data[dp]);
s = s.replace(/\n/g,"<br>").replace(/ /g,"&nbsp;");
document.getElementById("output").innerHTML = s;
},
'@':function() { stack.push(ip+dir, dir) },
'#':function() { if (stack.length) { dir=stack.pop(); ip=stack.pop(); } else quit++; },
'\n':function() { quit++ } // left or right edge
};
 
var spaces = " ";
function format(id) {
// format the buffer to evenly pad the line lengths
var el = document.getElementById(id);
var value = el.value || el.textContent || el.innerText;
var lines = value.split('\n');
width = 0;
for (var i in lines)
width = Math.max(width, lines[i].length);
while (spaces.length < width)
spaces += spaces;
for (var i in lines)
lines[i] += spaces.substring(0, width - lines[i].length);
code = lines.join('\n');
width++;
 
// show the formatted code in a PRE block (id=code)
init();
dump();
window.scroll(0,document.getElementById("code").offsetTop);
}
 
function init() {
inp = 0; quit = 0; dp = 0; dir = 1;
data = [0];
stack = [];
ip = code.indexOf('$'); if (ip<0) ip=0;
document.getElementById("output").innerHTML = "";
}
 
function done() {
return quit || ip<0 || ip>=code.length;
}
 
function body() {
var fn = commands[code.charAt(ip)];
if (fn) fn();
ip += dir;
}
 
function encode(s) {
var e = s.replace(/&/g, "&amp;");
e = e.replace(/</g, "&lt;");
e = e.replace(/>/g, "&gt;");
e = e.replace(/ /g, "&nbsp;");
return e.replace(/\n/g, "<br>");
}
function dump() {
//TODO: go through stack and color each return point green
document.getElementById("code").innerHTML
= encode(code.substring(0,ip))
+ '<span style="background: pink">'
+ encode(code.charAt(ip))
+ "</span>"
+ encode(code.substring(ip+1));
document.getElementById("mem").value = data.join();
}
 
var tid = 0;
function step() {
var n = document.getElementById("repeat").value - 0;
 
if (done()) init();
while (--n>=0 && !done())
body();
 
if (done() && tid) slow(); // stop timer
dump();
}
 
function out() {
var d = stack.length;
while (stack.length>=d && !done())
body();
dump();
}
 
function slow() {
if (tid) {
clearInterval(tid);
tid = 0;
document.getElementById("slow").value = "Slow";
} else {
var n = document.getElementById("speed").value - 0;
tid = setInterval(step, Math.max(n,10));
document.getElementById("slow").value = "Stop";
}
}
 
function run() {
while (!done())
body();
dump();
}