tinscript.js
· 12 KiB · JavaScript
Исходник
Playground
function copyText(text) {
var tempInput = document.createElement("textarea");
tempInput.style = "position: absolute; left: -1000px; top: -1000px";
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
}
var app = (function(){
var exported = {};
var stack = [];
var env = {};
var vals = {};
var codeElem,
inputElem,
runCodeElem,
stackOutput,
errElem;
exported.load = function() {
codeElem = elmID("current-code");
inputElem = elmID("repl");
runCodeElem = elmID("run-code");
stackOutput = elmID("stack-output");
errElem = elmID("error-output");
codeElem.value = localStorage.getItem("code") || "";
// Clear loading indicators
codeElem.className = "";
runCodeElem.className = "";
runCodeElem.value = "Run Code";
runCodeElem.onclick = function() {
localStorage.setItem("code", codeElem.value);
runCode(codeElem.value);
return false;
};
inputElem.onkeypress = function(event) {
if (event.keyCode != 13 /*Enter*/) {
return;
}
var code = inputElem.value;
inputElem.value = "Executing code...";
runCode(code);
inputElem.value = "";
return false;
};
}
function runCode(code) {
errElem.innerHTML = "";
errElem.className = "hidden";
try {
tin_run(code, stack, env, vals);
} catch(ex) {
errElem.innerHTML = ex.message;
errElem.className = "error";
}
displayStack();
}
function cleanStackDisplay(str) {
return str.
replace(/>/g, ">").
replace(/</g, "<").
replace(/\n/g, "<br/>");
}
function displayStack() {
var builtHTML = "";
for (var i = 0; i < stack.length; i++) {
var dropNum = i;
builtHTML +=
"<tr><td class='col'>" + cleanStackDisplay((stack[i] || "falsy").toString()) + "</td>" +
"<td class='col-alt'><" + typeof(stack[i]) + "></td>" +
'<td class="col"><a href="javascript:app.removeStackElem(' + dropNum + ')">Remove</a></td>' +
'<td class="col"><a href="javascript:app.copyStackElem('+ dropNum +')">Copy</a></td>'
"</tr>";
}
stackOutput.innerHTML = builtHTML;
}
exported.clearStack = function() {
stack = [];
displayStack();
}
exported.copyStackElem = function(idx) {
var valToCopy = stack[idx].toString();
copyText(valToCopy);
}
exported.removeStackElem = function(idx) {
stack.splice(idx, 1);
displayStack();
}
function elmID(id) {
return document.getElementById(id);
}
return exported;
})();
window.onload = app.load;
</script>
<script>
function isSpace(char) {
return char === " " || char === "\t" || char === "\r" || char === "\n";
}
function assert(condition, message) {
if (!condition) {
message = message || "Assertion failed";
if (typeof Error !== "undefined") {
throw new Error(message);
}
throw message;
}
}
function hasSpaces(str) {
for (var i = 0; i < str.length; i++) {
if (isSpace(str[i])) {
return true;
}
}
return false;
}
function list_of_string(code) {
var next = tokenize(code);
var list = [];
var tok = next();
while (! tok.message) {
list.push(tok);
tok = next();
}
assert(tok.message === "EOF", tok);
return list;
}
function tokenize(code) {
if (Array.isArray(code)) {
var i = 0;
return function (msg) {
if (i >= code.length) { return { message: msg || "EOF" }; }
var retVal = code[i];
i++;
return retVal;
}
}
if (code.message) {
throw code;
}
assert(typeof(code) === "string", "Can only tokenize a string or an array!");
// Current token
var tok = "";
// Build up current token until we hit a space, a [ or a "
var idx = 0;
function eatSpace() {
if (idx >= code.length) {
return;
}
while (isSpace(code[idx])) {
if (idx >= code.length) { return; }
idx++;
}
}
// next token
return function(msg) {
// Eat spaces
eatSpace();
if (idx >= code.length) { return { message: msg || "EOF" }; }
var char = code[idx];
if (char === "[") {
var seekIdx = idx;
var depth = 1;
while(depth > 0) {
seekIdx++;
if (seekIdx >= code.length) { throw "Unexpected EOF!"; }
if (code[seekIdx] === "[") { depth++; }
if (code[seekIdx] === "]") { depth--; }
if (depth < 0) {
throw "Unexpected ]";
}
}
var retVal = code.slice(idx + 1, seekIdx);
idx = seekIdx + 1;
return retVal;
}
if (char === "\"") {
var seekIdx = idx + 1;
while(code[seekIdx] != "\"") {
seekIdx++;
if (seekIdx >= code.length) {
throw "Unterminated string!";
}
}
var retVal = code.slice(idx + 1, seekIdx);
idx = seekIdx + 1;
return retVal;
}
var seekIdx = idx + 1;
while(!isSpace(code[seekIdx])) {
seekIdx++;
if (seekIdx >= code.length) {
var retVal = code.slice(idx, seekIdx);
idx = seekIdx;
return retVal;
}
}
var retVal = code.slice(idx, seekIdx);
idx = seekIdx + 1;
return retVal;
}
}
function pop(stack) {
if (stack.length === 0) { throw { message: "Data underflow" } }
return stack.pop();
}
function eval_or_push(tok, env, stack, next, vals) {
if (typeof(env[tok]) === "function") { env[tok](next, vals); }
else { stack.push(tok); }
}
function tin_run(code, stack, env, vals) {
var env = env || {};
var next = tokenize(code);
var stack = stack || [];
var vals = vals || {};
function num_op(fn) {
var a = pop(stack);
var b = pop(stack);
stack.push(fn(Number(b), Number(a)));
}
env["+"] = env["+"] || function() {
num_op(function(a,b) {return a + b});
};
env["*"] = env["*"] || function() {
num_op(function(a,b) {return a * b});
};
env["/"] = env["/"] || function() {
num_op(function(a,b) {return a / b});
};
env["-"] = env["-"] || function() {
num_op(function(a,b) { return a - b});
};
env["concat"] = env["concat"] || function() {
var b = pop(stack).toString();
var a = pop(stack).toString();
stack.push(a + b);
}
env["rand"] = env["rand"] || function() {
var max = Number(pop(stack));
// Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
stack.push(Math.floor(Math.random() * Math.floor(max)));
};
env["str:"] = env["str:"] || function(next, vals) {
var str = next("str: expected a string literal");
stack.push(str);
};
env["split"] = env["split"] || function(next, vals) {
var sep = pop(stack);
var str = pop(stack);
stack.push(str.toString().split(sep));
};
env["eval"] = env["eval"] || function(next, vals) {
var fragment = pop(stack);
tin_exec(tokenize(fragment), stack, env, vals);
};
env["dup"] = env["dup"] || function() { stack.push(stack[stack.length - 1]); };
env["swap"] = env["swap"] || function() {
var b = pop(stack);
var a = pop(stack);
stack.push(b);
stack.push(a);
}
env["drop"] = env["drop"] || function() { pop(stack); };
env["print"] = env["print"] || function() { console.log(pop(stack)); };
env["vec"] = env["vec"] || function(next, vals) {
var literal = pop(stack);
assert(literal, "No data on stack");
stack.push(tin_exec(tokenize(literal), [], env, vals));
};
env["set"] = function() {
var name = pop(stack).toString();
var value = pop(stack);
vals[name] = value;
};
env["get"] = function() {
var name = pop(stack).toString();
stack.push(vals[name]);
}
env["t"] = function() { stack.push(true); };
env["f"] = function() { stack.push(false); };
env["and"] = function() {
var b = pop(stack);
var a = pop(stack);
stack.push(a && b);
}
env["or"] = function() {
var b = pop(stack);
var a = pop(stack);
stack.push(a || b);
}
function timeFromString(time_str) {
var parser = /(\d+):(\d+)(a|p|am|pm)/i;
var parts = parser.exec(time_str);
var hour = Number.parseFloat(parts[1], 10);
var minutes = Number.parseFloat(parts[2], 10);
if (hour < 12 && (parts[3] === "p" || parts[3] === "pm")) {
hour += 12;
}
return new Date(2019,1,1, hour, minutes);
}
env["duration"] = function() {
var b = pop(stack);
var a = pop(stack);
var a_time = timeFromString(a);
var b_time = timeFromString(b);
stack.push((b_time.getHours() + (b_time.getMinutes() / 60) ) - (a_time.getHours() + a_time.getMinutes() / 60));
}
env["not"] = function() { stack.push(!pop(stack)); }
env["when:"] = function(next, vals) {
var cond = pop(stack);
if (cond) {
tin_exec(tokenize(next("when: expected a string")), stack, env, vals);
}
};
env["if:"] = function(next, vals) {
var cond = pop(stack);
var t_branch = list_of_string(next("if: expected a true branch"));
var f_branch = list_of_string(next("if: expected a false branch"));
if (cond) {
tin_exec(tokenize(t_branch), stack, env, vals);
} else {
tin_exec(tokenize(f_branch), stack, env, vals);
}
};
/*
env["while-do"] = env["while-do"] || function(next, vals) {
var cond = list_of_string(pop(stack));
var body = list_of_string(next("while-do: expects a body"));
var toCheck = tin_exec(tokenize(cond), stack, env, vals);
while(toCheck[0]) {
tin_exec(tokenize(body), stack, env, vals);
toCheck = tin_exec(tokenize(cond), stack, env, vals);
}
}*/
env["times:"] = env["times:"] || function(next, vals) {
// debugger;
var num_times = Number(pop(stack));
var body = list_of_string(next("times: expects an expression to repeat"));
for (var i = 0; i < num_times; i++) {
tin_exec(tokenize(body), stack, env, vals);
}
}
env["def:"] = env["def:"] || function(next, vals) {
// A spec is a name and a list of arguments
var spec = list_of_string(next("def: expects a function specification"));
assert(spec.length > 0, "The spec for a function *must* have a name!");
var name = spec[0];
var def = list_of_string(next("def: expects a function body"));
env[name] = function(next, vals) {
var old_vals = vals;
vals = {};
tin_exec(tokenize(def), stack, env, vals);
vals = old_vals;
}
};
tin_exec(next, stack, env, vals);
}
function tin_exec(next, stack, env, vals) {
stack = stack || [];
vals = vals || {};
var tok = next();
while (!tok.message) {
eval_or_push(tok, env, stack, next, vals);
tok = next();
}
return stack;
}
| 1 | |
| 2 | function copyText(text) { |
| 3 | var tempInput = document.createElement("textarea"); |
| 4 | tempInput.style = "position: absolute; left: -1000px; top: -1000px"; |
| 5 | tempInput.value = text; |
| 6 | |
| 7 | document.body.appendChild(tempInput); |
| 8 | tempInput.select(); |
| 9 | |
| 10 | document.execCommand("copy"); |
| 11 | document.body.removeChild(tempInput); |
| 12 | } |
| 13 | |
| 14 | |
| 15 | var app = (function(){ |
| 16 | var exported = {}; |
| 17 | var stack = []; |
| 18 | var env = {}; |
| 19 | var vals = {}; |
| 20 | |
| 21 | var codeElem, |
| 22 | inputElem, |
| 23 | runCodeElem, |
| 24 | stackOutput, |
| 25 | errElem; |
| 26 | |
| 27 | |
| 28 | exported.load = function() { |
| 29 | codeElem = elmID("current-code"); |
| 30 | inputElem = elmID("repl"); |
| 31 | runCodeElem = elmID("run-code"); |
| 32 | stackOutput = elmID("stack-output"); |
| 33 | errElem = elmID("error-output"); |
| 34 | codeElem.value = localStorage.getItem("code") || ""; |
| 35 | // Clear loading indicators |
| 36 | codeElem.className = ""; |
| 37 | runCodeElem.className = ""; |
| 38 | runCodeElem.value = "Run Code"; |
| 39 | |
| 40 | |
| 41 | runCodeElem.onclick = function() { |
| 42 | localStorage.setItem("code", codeElem.value); |
| 43 | runCode(codeElem.value); |
| 44 | return false; |
| 45 | }; |
| 46 | |
| 47 | |
| 48 | inputElem.onkeypress = function(event) { |
| 49 | |
| 50 | if (event.keyCode != 13 /*Enter*/) { |
| 51 | return; |
| 52 | } |
| 53 | |
| 54 | var code = inputElem.value; |
| 55 | inputElem.value = "Executing code..."; |
| 56 | runCode(code); |
| 57 | inputElem.value = ""; |
| 58 | |
| 59 | return false; |
| 60 | |
| 61 | }; |
| 62 | |
| 63 | } |
| 64 | |
| 65 | |
| 66 | function runCode(code) { |
| 67 | errElem.innerHTML = ""; |
| 68 | errElem.className = "hidden"; |
| 69 | try { |
| 70 | tin_run(code, stack, env, vals); |
| 71 | } catch(ex) { |
| 72 | errElem.innerHTML = ex.message; |
| 73 | errElem.className = "error"; |
| 74 | } |
| 75 | displayStack(); |
| 76 | } |
| 77 | |
| 78 | |
| 79 | function cleanStackDisplay(str) { |
| 80 | return str. |
| 81 | replace(/>/g, ">"). |
| 82 | replace(/</g, "<"). |
| 83 | replace(/\n/g, "<br/>"); |
| 84 | } |
| 85 | |
| 86 | |
| 87 | function displayStack() { |
| 88 | |
| 89 | var builtHTML = ""; |
| 90 | for (var i = 0; i < stack.length; i++) { |
| 91 | var dropNum = i; |
| 92 | builtHTML += |
| 93 | "<tr><td class='col'>" + cleanStackDisplay((stack[i] || "falsy").toString()) + "</td>" + |
| 94 | "<td class='col-alt'><" + typeof(stack[i]) + "></td>" + |
| 95 | '<td class="col"><a href="javascript:app.removeStackElem(' + dropNum + ')">Remove</a></td>' + |
| 96 | '<td class="col"><a href="javascript:app.copyStackElem('+ dropNum +')">Copy</a></td>' |
| 97 | "</tr>"; |
| 98 | } |
| 99 | |
| 100 | stackOutput.innerHTML = builtHTML; |
| 101 | } |
| 102 | |
| 103 | exported.clearStack = function() { |
| 104 | stack = []; |
| 105 | displayStack(); |
| 106 | } |
| 107 | |
| 108 | exported.copyStackElem = function(idx) { |
| 109 | var valToCopy = stack[idx].toString(); |
| 110 | copyText(valToCopy); |
| 111 | } |
| 112 | |
| 113 | exported.removeStackElem = function(idx) { |
| 114 | stack.splice(idx, 1); |
| 115 | displayStack(); |
| 116 | } |
| 117 | |
| 118 | function elmID(id) { |
| 119 | return document.getElementById(id); |
| 120 | } |
| 121 | |
| 122 | return exported; |
| 123 | })(); |
| 124 | |
| 125 | window.onload = app.load; |
| 126 | </script> |
| 127 | <script> |
| 128 | function isSpace(char) { |
| 129 | return char === " " || char === "\t" || char === "\r" || char === "\n"; |
| 130 | } |
| 131 | |
| 132 | function assert(condition, message) { |
| 133 | if (!condition) { |
| 134 | message = message || "Assertion failed"; |
| 135 | if (typeof Error !== "undefined") { |
| 136 | throw new Error(message); |
| 137 | } |
| 138 | throw message; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | function hasSpaces(str) { |
| 143 | for (var i = 0; i < str.length; i++) { |
| 144 | if (isSpace(str[i])) { |
| 145 | return true; |
| 146 | } |
| 147 | } |
| 148 | return false; |
| 149 | } |
| 150 | |
| 151 | function list_of_string(code) { |
| 152 | var next = tokenize(code); |
| 153 | var list = []; |
| 154 | var tok = next(); |
| 155 | while (! tok.message) { |
| 156 | list.push(tok); |
| 157 | tok = next(); |
| 158 | } |
| 159 | assert(tok.message === "EOF", tok); |
| 160 | return list; |
| 161 | } |
| 162 | |
| 163 | |
| 164 | function tokenize(code) { |
| 165 | if (Array.isArray(code)) { |
| 166 | var i = 0; |
| 167 | |
| 168 | return function (msg) { |
| 169 | if (i >= code.length) { return { message: msg || "EOF" }; } |
| 170 | var retVal = code[i]; |
| 171 | i++; |
| 172 | return retVal; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | if (code.message) { |
| 177 | throw code; |
| 178 | } |
| 179 | |
| 180 | assert(typeof(code) === "string", "Can only tokenize a string or an array!"); |
| 181 | |
| 182 | // Current token |
| 183 | var tok = ""; |
| 184 | |
| 185 | // Build up current token until we hit a space, a [ or a " |
| 186 | var idx = 0; |
| 187 | function eatSpace() { |
| 188 | if (idx >= code.length) { |
| 189 | return; |
| 190 | } |
| 191 | |
| 192 | while (isSpace(code[idx])) { |
| 193 | if (idx >= code.length) { return; } |
| 194 | idx++; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | // next token |
| 199 | return function(msg) { |
| 200 | // Eat spaces |
| 201 | eatSpace(); |
| 202 | |
| 203 | if (idx >= code.length) { return { message: msg || "EOF" }; } |
| 204 | |
| 205 | var char = code[idx]; |
| 206 | |
| 207 | if (char === "[") { |
| 208 | var seekIdx = idx; |
| 209 | var depth = 1; |
| 210 | while(depth > 0) { |
| 211 | seekIdx++; |
| 212 | if (seekIdx >= code.length) { throw "Unexpected EOF!"; } |
| 213 | if (code[seekIdx] === "[") { depth++; } |
| 214 | if (code[seekIdx] === "]") { depth--; } |
| 215 | if (depth < 0) { |
| 216 | throw "Unexpected ]"; |
| 217 | } |
| 218 | } |
| 219 | var retVal = code.slice(idx + 1, seekIdx); |
| 220 | idx = seekIdx + 1; |
| 221 | return retVal; |
| 222 | } |
| 223 | |
| 224 | if (char === "\"") { |
| 225 | |
| 226 | var seekIdx = idx + 1; |
| 227 | while(code[seekIdx] != "\"") { |
| 228 | seekIdx++; |
| 229 | if (seekIdx >= code.length) { |
| 230 | throw "Unterminated string!"; |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | var retVal = code.slice(idx + 1, seekIdx); |
| 235 | idx = seekIdx + 1; |
| 236 | return retVal; |
| 237 | } |
| 238 | |
| 239 | var seekIdx = idx + 1; |
| 240 | |
| 241 | while(!isSpace(code[seekIdx])) { |
| 242 | seekIdx++; |
| 243 | if (seekIdx >= code.length) { |
| 244 | var retVal = code.slice(idx, seekIdx); |
| 245 | idx = seekIdx; |
| 246 | return retVal; |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | var retVal = code.slice(idx, seekIdx); |
| 251 | idx = seekIdx + 1; |
| 252 | return retVal; |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | |
| 257 | function pop(stack) { |
| 258 | if (stack.length === 0) { throw { message: "Data underflow" } } |
| 259 | return stack.pop(); |
| 260 | } |
| 261 | |
| 262 | function eval_or_push(tok, env, stack, next, vals) { |
| 263 | if (typeof(env[tok]) === "function") { env[tok](next, vals); } |
| 264 | else { stack.push(tok); } |
| 265 | } |
| 266 | |
| 267 | |
| 268 | function tin_run(code, stack, env, vals) { |
| 269 | var env = env || {}; |
| 270 | var next = tokenize(code); |
| 271 | var stack = stack || []; |
| 272 | var vals = vals || {}; |
| 273 | |
| 274 | function num_op(fn) { |
| 275 | var a = pop(stack); |
| 276 | var b = pop(stack); |
| 277 | stack.push(fn(Number(b), Number(a))); |
| 278 | } |
| 279 | |
| 280 | |
| 281 | env["+"] = env["+"] || function() { |
| 282 | num_op(function(a,b) {return a + b}); |
| 283 | }; |
| 284 | |
| 285 | env["*"] = env["*"] || function() { |
| 286 | num_op(function(a,b) {return a * b}); |
| 287 | }; |
| 288 | |
| 289 | env["/"] = env["/"] || function() { |
| 290 | num_op(function(a,b) {return a / b}); |
| 291 | }; |
| 292 | |
| 293 | env["-"] = env["-"] || function() { |
| 294 | num_op(function(a,b) { return a - b}); |
| 295 | }; |
| 296 | |
| 297 | env["concat"] = env["concat"] || function() { |
| 298 | var b = pop(stack).toString(); |
| 299 | var a = pop(stack).toString(); |
| 300 | |
| 301 | stack.push(a + b); |
| 302 | } |
| 303 | |
| 304 | env["rand"] = env["rand"] || function() { |
| 305 | var max = Number(pop(stack)); |
| 306 | // Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random |
| 307 | stack.push(Math.floor(Math.random() * Math.floor(max))); |
| 308 | }; |
| 309 | |
| 310 | env["str:"] = env["str:"] || function(next, vals) { |
| 311 | var str = next("str: expected a string literal"); |
| 312 | stack.push(str); |
| 313 | }; |
| 314 | |
| 315 | env["split"] = env["split"] || function(next, vals) { |
| 316 | var sep = pop(stack); |
| 317 | var str = pop(stack); |
| 318 | stack.push(str.toString().split(sep)); |
| 319 | }; |
| 320 | |
| 321 | env["eval"] = env["eval"] || function(next, vals) { |
| 322 | var fragment = pop(stack); |
| 323 | tin_exec(tokenize(fragment), stack, env, vals); |
| 324 | }; |
| 325 | |
| 326 | env["dup"] = env["dup"] || function() { stack.push(stack[stack.length - 1]); }; |
| 327 | |
| 328 | env["swap"] = env["swap"] || function() { |
| 329 | var b = pop(stack); |
| 330 | var a = pop(stack); |
| 331 | |
| 332 | stack.push(b); |
| 333 | stack.push(a); |
| 334 | } |
| 335 | |
| 336 | env["drop"] = env["drop"] || function() { pop(stack); }; |
| 337 | env["print"] = env["print"] || function() { console.log(pop(stack)); }; |
| 338 | env["vec"] = env["vec"] || function(next, vals) { |
| 339 | var literal = pop(stack); |
| 340 | assert(literal, "No data on stack"); |
| 341 | stack.push(tin_exec(tokenize(literal), [], env, vals)); |
| 342 | |
| 343 | }; |
| 344 | |
| 345 | env["set"] = function() { |
| 346 | var name = pop(stack).toString(); |
| 347 | var value = pop(stack); |
| 348 | vals[name] = value; |
| 349 | }; |
| 350 | |
| 351 | env["get"] = function() { |
| 352 | var name = pop(stack).toString(); |
| 353 | stack.push(vals[name]); |
| 354 | } |
| 355 | |
| 356 | env["t"] = function() { stack.push(true); }; |
| 357 | env["f"] = function() { stack.push(false); }; |
| 358 | |
| 359 | env["and"] = function() { |
| 360 | var b = pop(stack); |
| 361 | var a = pop(stack); |
| 362 | stack.push(a && b); |
| 363 | } |
| 364 | |
| 365 | env["or"] = function() { |
| 366 | var b = pop(stack); |
| 367 | var a = pop(stack); |
| 368 | stack.push(a || b); |
| 369 | } |
| 370 | |
| 371 | function timeFromString(time_str) { |
| 372 | var parser = /(\d+):(\d+)(a|p|am|pm)/i; |
| 373 | var parts = parser.exec(time_str); |
| 374 | var hour = Number.parseFloat(parts[1], 10); |
| 375 | var minutes = Number.parseFloat(parts[2], 10); |
| 376 | |
| 377 | if (hour < 12 && (parts[3] === "p" || parts[3] === "pm")) { |
| 378 | hour += 12; |
| 379 | } |
| 380 | |
| 381 | return new Date(2019,1,1, hour, minutes); |
| 382 | } |
| 383 | |
| 384 | env["duration"] = function() { |
| 385 | |
| 386 | var b = pop(stack); |
| 387 | var a = pop(stack); |
| 388 | var a_time = timeFromString(a); |
| 389 | var b_time = timeFromString(b); |
| 390 | |
| 391 | stack.push((b_time.getHours() + (b_time.getMinutes() / 60) ) - (a_time.getHours() + a_time.getMinutes() / 60)); |
| 392 | |
| 393 | } |
| 394 | |
| 395 | env["not"] = function() { stack.push(!pop(stack)); } |
| 396 | |
| 397 | env["when:"] = function(next, vals) { |
| 398 | var cond = pop(stack); |
| 399 | if (cond) { |
| 400 | tin_exec(tokenize(next("when: expected a string")), stack, env, vals); |
| 401 | |
| 402 | } |
| 403 | |
| 404 | }; |
| 405 | |
| 406 | env["if:"] = function(next, vals) { |
| 407 | var cond = pop(stack); |
| 408 | var t_branch = list_of_string(next("if: expected a true branch")); |
| 409 | var f_branch = list_of_string(next("if: expected a false branch")); |
| 410 | |
| 411 | if (cond) { |
| 412 | tin_exec(tokenize(t_branch), stack, env, vals); |
| 413 | } else { |
| 414 | tin_exec(tokenize(f_branch), stack, env, vals); |
| 415 | } |
| 416 | }; |
| 417 | |
| 418 | /* |
| 419 | env["while-do"] = env["while-do"] || function(next, vals) { |
| 420 | var cond = list_of_string(pop(stack)); |
| 421 | var body = list_of_string(next("while-do: expects a body")); |
| 422 | var toCheck = tin_exec(tokenize(cond), stack, env, vals); |
| 423 | while(toCheck[0]) { |
| 424 | tin_exec(tokenize(body), stack, env, vals); |
| 425 | toCheck = tin_exec(tokenize(cond), stack, env, vals); |
| 426 | } |
| 427 | }*/ |
| 428 | |
| 429 | env["times:"] = env["times:"] || function(next, vals) { |
| 430 | // debugger; |
| 431 | var num_times = Number(pop(stack)); |
| 432 | var body = list_of_string(next("times: expects an expression to repeat")); |
| 433 | for (var i = 0; i < num_times; i++) { |
| 434 | tin_exec(tokenize(body), stack, env, vals); |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | env["def:"] = env["def:"] || function(next, vals) { |
| 439 | |
| 440 | // A spec is a name and a list of arguments |
| 441 | var spec = list_of_string(next("def: expects a function specification")); |
| 442 | assert(spec.length > 0, "The spec for a function *must* have a name!"); |
| 443 | var name = spec[0]; |
| 444 | |
| 445 | var def = list_of_string(next("def: expects a function body")); |
| 446 | env[name] = function(next, vals) { |
| 447 | var old_vals = vals; |
| 448 | vals = {}; |
| 449 | tin_exec(tokenize(def), stack, env, vals); |
| 450 | vals = old_vals; |
| 451 | } |
| 452 | }; |
| 453 | tin_exec(next, stack, env, vals); |
| 454 | |
| 455 | } |
| 456 | |
| 457 | |
| 458 | |
| 459 | function tin_exec(next, stack, env, vals) { |
| 460 | stack = stack || []; |
| 461 | vals = vals || {}; |
| 462 | |
| 463 | var tok = next(); |
| 464 | |
| 465 | while (!tok.message) { |
| 466 | eval_or_push(tok, env, stack, next, vals); |
| 467 | tok = next(); |
| 468 | } |
| 469 | return stack; |
| 470 | } |
| 471 |