Последняя активность 3 weeks ago

tinscript.js Исходник Playground
1
2function 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
15var 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, "&lt;").
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'>&lt;" + typeof(stack[i]) + "&gt;</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
125window.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
164function 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
257function pop(stack) {
258 if (stack.length === 0) { throw { message: "Data underflow" } }
259 return stack.pop();
260}
261
262function 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
268function 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
459function 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