var code = "";
var ahead = []; // cache for scanning blocks,strings,comments
var ip = 0;
var stack = []; // stack.push(n); n=stack.pop();
var ret = []; // ret.push(ip); ip = ret.pop();
var vars = {}; // cells and pointers, contains 'a'..'z' and 0..
var ini = 0; // index into input
// space is cheap, only have cell addressing, char == cell
// i.e. mandate the JavaScript side effect: 0: 3;
// a-z are disjoint, only non-negative indices supported
// up to user to track here (e.g. use h):
// h;$"string"h:{str}
// create: {var}h;\:
// comma: {d}h;: h;1+h:
// allot: {n}h;+h:
//
// string:
// {addr}"string"{addr+len} lays down a string at given address
// so string mode parse(c) { var i=stack.pop(); vars[i] = c; stack.push(++i) }
// store uncounted:
// {base}$"string"^-{str len}
// store counted:
// {base}$1+"string"{base end}^-1-\:
// count: {cstr}$1+\;{str len}
// type: {str len}[$][\$;,1+\1-]#%%
// type: {str end}\[^^>][$;,1+]#%%
//
// array:
// set {d a i}+:
// get {a i}+;{d}
// TODO: since = isn't needed (=[t][f]? same as -[f][t]?)
// use = for enhanced variables
// 1234=z
// z {1234}
// [block]=f
// f! {execute block}
Array.prototype.pick = function(n) { return this[this.length-n-1] }
function encode(s) {
var e = s.replace(/&/g, "&");
e = e.replace(//g, ">");
e = e.replace(/ /g, " ");
return e.replace(/\n/g, "
");
}
function dump() {
var s = "";
for (var p in vars)
s += p + '=' + vars[p] + ' ';
document.getElementById("vars").value = s;
document.getElementById("stack").value = stack.join();
s = [];
for (var i=0; i=0 && ret[i]'
+ encode(code.charAt(ip))
+ ""
+ encode(code.substring(ip+1));
}
function seek(c) {
if (!ahead[ip])
ahead[ip] = ip + code.substring(ip).indexOf(c);
return ahead[ip];
}
var braces = {
"{":function() { return seek("}") },
'"':function() { ++ip; return seek('"') },
"'":function() { return ip+1 },
"[":matching_brace
}
function matching_brace() {
var start = ip;
if (!ahead[start]) {
while (ip < code.length) {
var c = code.charAt(++ip);
if (c == ']')
break;
if (braces[c])
ip = braces[c]();
}
ahead[start] = ip;
}
return ahead[start];
}
function put(s) {
var text = document.getElementById("output").innerHTML + s;
text = text.replace(/\n/g,"
").replace(/ /g," ");
document.getElementById("output").innerHTML = text;
}
function getc() {
var s = document.getElementById("input").value;
return ini < s.length ? s.charCodeAt(ini++) : -1;
}
var commands = {
"%":function() { stack.pop() },
"$":function() { stack.push(stack.pick(0)) },
"^":function() { stack.push(stack.pick(1)) }, // DUP: over
"\\":function() { var t=stack.length-2; stack.splice(t,2,stack[t+1],stack[t]) },
"@":function() { var t=stack.length-3; stack.splice(t,3,stack[t+1],stack[t+2],stack[t]) },
"\xF8":function() { stack.push(stack.pick(stack.pop())) }, // "ø" pick
"(":function() { ret.push(stack.pop()) }, // DUP: >r
")":function() { stack.push(ret.pop()) }, // DUP: r>
"+":function() { stack.push(stack.pop()+stack.pop()) },
"-":function() { stack.push(-stack.pop()+stack.pop()) },
"*":function() { stack.push(stack.pop()*stack.pop()) },
"/":function() { // DUP: /mod
var d = stack.pop(), n = stack.pop(), r = n/d;
stack.push(n%d, r<0?Math.ceil(r):Math.floor(r));
},
"_":function() { stack.push(-stack.pop()) },
"«":function() { var s=stack.pop(); stack.push(stack.pop()<>>s) }, // DUP: rshift
"&":function() { stack.push(stack.pop()&stack.pop()) },
"|":function() { stack.push(stack.pop()^stack.pop()) }, // DUP: xor
"~":function() { stack.push(~stack.pop()) },
"<":function() { stack.push(-(stack.pop()>stack.pop())) }, // DUP: less than
"=":function() { stack.push(-(stack.pop()==stack.pop())) },
">":function() { stack.push(-(stack.pop()=0 && code.charAt(ret[n]) == '#') {
if (stack.pop()) {
ret.push(ret[n+1], ret[n+2]); // continue loop
} else {
ret.pop(); ret.pop(); // exit loop
}
};
ip = ret.pop();
},
"!":function() { ret.push(ip); ip = stack.pop(); },
"?":function() { // DUP: was just if-true in FALSE
var f = stack.pop(), t = stack.pop();
ret.push(ip); ip = stack.pop() ? t : f ;
},
"#":function() {
ret.push(ip, stack.pick(1), stack.pop()); // ip, test, body
// ip points to #, signals that we're doing a loop
ip = stack.pop(); // execute test first
},
"⇒": function() { // DUP: operator defining operator
var op = stack.pop();
commands[code.charAt(++ip)] = function() { ret.push(ip); ip = op; }
}
}
function eval() {
var c = code.charAt(ip);
if (commands[c]) {
commands[c]();
} else
if (/\d/.test(c)) {
var sn = code.substring(ip);
var num = sn.match(/\d+/)[0];
stack.push(parseInt(num,10));
ip += num.length;
return;
} else
if (/\s/.test(c)) {
var sn = code.substring(ip);
var sp = sn.match(/\s+/)[0];
ip += sp.length;
return;
} else
stack.push(c); // allows any non-number/operator as a variable; Unicode is your oyster!
//stack.push(vars[c]); // experiment: values instead of variables
//else alert("Bad char '" + c + "': " + code.charCodeAt(ip));
++ip;
}
function init() {
ahead = [];
ip = 0; ret=[]; stack=[]; vars={}; ini=0;
document.getElementById("output").innerHTML = "";
dump();
}
function format(id) {
var el = document.getElementById(id);
code = el.value || el.textContent || el.innerText;
init();
window.scroll(0,document.getElementById("code").offsetTop);
}
function sstep() {
if (ip >= code.length) init(); else eval();
dump();
}
function over() {
var d = ret.length;
do { eval() } while (d < ret.length);
dump();
}
function up() {
var d = ret.length;
do { eval() } while (d <= ret.length);
dump();
}
//TODO: slow(), runs on timer events so it is interruptable
function run() {
//var start = new Date();
if (ip >= code.length)
init();
while (ip < code.length)
eval();
//var elapsed = (new Date()).getTime() - start.getTime();
dump();
//alert("Elapsed time: " + elapsed + " ms");
}
//TODO: recode contact in DUP
function contact() {
document.getElementById("src").value =
'"moc.retskriuq@onai"'.split('').reverse().join('');
format("src");run();
}