This is a parser for a variant of TCL that I created.
I've created two demos that are interesting. One is a version of snake that you can control with A and D, and one is a cube that you can rotate with the W, A, S, and D keys.
The program can be swapped using the dropdown next to the run and stop buttons, and ran with the run button.
The full list of functionality of this language is documented here.
Program:
Check the checkbox to show the code.
let standard_library = `
# compute POWER of x to the y
proc pow {x y} {
if {$y 0 =} {
return 1
}
set res $x
for {set i 1} {$i $y <} {incr i} {
set res [expr $res $x *]
}
return $res
}
proc factorial {x} {
set fact 1
for {} {$x 1 >} {set x [expr $x 1 -]} {
set fact [expr $fact $x *]
}
return $fact
}
# compute sqrt of n
# this is a poor algorithm for it, it just gets it approximately correct in 10 iterations
proc sqrt {n} {
# run for 10 iterations
set est 0
set cness 0
for {set i 0} {$i 10 <} {incr i} {
set cness [pow $est 2]
# negative diff is too high
# positive diff is too low
set diff [expr $n $cness -]
set smalldiff [expr $diff 10 /]
#puts $smalldiff
set est [expr $est $smalldiff +]
#puts $est
}
return $est
}
# arctangent of x using the taylor series
proc arctan {x} {
set xsum 0
set flip 0
for {set i 0} {$i 50 <} {incr i} {
set pw [expr $i 2 *]
set pw [expr $pw 1 +]
set xt [pow $x $pw]
set xd [expr $xt $pw /]
if {$flip 0 =} {
set xsum [expr $xsum $xd +]
set flip 1
} {
set xsum [expr $xsum $xd -]
set flip 0
}
#puts $xd
}
return $xsum
}
proc PI {} {
return 3.14159265358979323846264338327950288
}
proc PI2 {} {
return [expr [PI] 2 *]
}
proc sin {x} {
set x [expr $x [PI2] %]
set xsum 0
set flip 0
for {set i 0} {$i 10 <} {incr i} {
set pw [expr $i 2 *]
set pw [expr $pw 1 +]
set fact [factorial $pw]
set xt [pow $x $pw]
set xd [expr $xt $fact /]
if {$flip 0 =} {
set xsum [expr $xsum $xd +]
set flip 1
} {
set xsum [expr $xsum $xd -]
set flip 0
}
#puts $xd
}
return $xsum
}
proc cos {x} {
set halfpi [PI]
set halfpi [expr $halfpi 2 /]
set sum [expr $halfpi $x +]
return [sin $sum]
}
proc tan {x} {
set s [sin $x]
set c [cos $x]
return [expr $s $c /]
}
proc incr {x} {
set listexpr [uplevel [list expr "\\$$x" 1 +]]
uplevel [list set $x $listexpr]
}
proc decr {x} {
set listexpr [uplevel [list expr "\\$$x" 1 -]]
uplevel [list set $x $listexpr]
}
proc trunc {n} {
return [expr $n [expr $n 1 %] -]
}
proc rand {state} {
set res [expr $state 48271 *]
set res [expr $res 65536 %]
uplevel [list set rngstate $res]
return $res
}
`;
// holds the default program
let tcsr = `setpixsz 16 16
proc getBlockFromIndex {index} {
set block(0) "11111001100110011111"
set block(1) "00100110001000100111"
set block(2) "11110001111110001111"
set block(3) "11110001111100011111"
set block(4) "10011001111100010001"
set block(5) "11111000111100011111"
set block(6) "11111000111110011111"
set block(7) "11110001001001000100"
set block(8) "11111001111110011111"
set block(9) "11111001111100011111"
set block(A) "11111001111110011001"
set block(B) "11101001111010011110"
set block(C) "11111000100010001111"
set block(D) "11101001100110011110"
set block(E) "11111000111110001111"
set block(F) "11111000111110001000"
set block(M) "11111001100100001111"
set block(-) "00000000111100000000"
set block(P) "11111001111110001000"
set block(L) "10001000100010001111"
set block(Y) "10011001111100011111"
set block(N) "11111001100110011001"
set block("?") "01101001001000000010"
return $block($index)
}
proc drawLetterBlock {index offsetx offsety} {
set blk [getBlockFromIndex $index]
set y 0
set i 0
while {$i 20 <} {
set x [expr $i 4 %]
if {0 $x =} {
if {$i 0 =} {} {
incr y
}
}
set color [expr 255 [expr $blk($i) 255 *] -]
putpixel [expr $x $offsetx +] [expr $y $offsety +] $color $color $color
incr i
}
}
proc drawFrame {} {
fillcolor 0 0 16 16 0 0 0
}
proc drawBackground {} {
fillcolor 1 1 14 14 0 0 255
}
proc drawSnake {sn} {
set len [expr $sn(1) 2 +]
set i 2
while {$i $len <} {
set seg $sn($i)
putpixel $seg(0) $seg(1) 0 255 0
incr i
}
}
proc addSnakeSegment {snk x y} {
set nextSnakeInd [expr $snk(1) 2 +]
set snk($nextSnakeInd) [asarray "$x $y"]
set snk(1) [expr $snk(1) 1 +]
return $snk
}
proc moveSnake {snk} {
set dir $snk(0)
set len $snk(1)
set orig $snk(2)
# shift snake
set i 0
while {$i [expr $len 1 -] <} {
set ind [expr $i 2 +]
set next $snk([expr $ind 1 +])
set snk([expr $ind 1 +]) $snk($ind)
set snk($ind) $next
incr i
}
# move snake head
if {$snk(0) 0 =} {
set orig(0) [expr $orig(0) 1 +]
}
if {$snk(0) 2 =} {
set orig(0) [expr $orig(0) 1 -]
}
if {$snk(0) 1 =} {
set orig(1) [expr $orig(1) 1 +]
}
if {$snk(0) 3 =} {
set orig(1) [expr $orig(1) 1 -]
}
set snk(2) $orig
#putsdbg $snk
return $snk
}
proc getRandomFoodPos {lastpos} {
set x $lastpos(0)
set y $lastpos(1)
set randx [expr [rand [time]] 14 %]
set randy [expr [rand $x] 14 %]
set randx [expr $randx 1 +]
set randy [expr $randy 1 +]
return [asarray "[trunc $randx] [trunc $randy]"]
}
proc ShowGameOver {} {
setpixsz 64 64
drawLetterBlock 6 4 10
drawLetterBlock 0 12 10
drawLetterBlock 0 20 10
drawLetterBlock D 28 10
drawLetterBlock 6 12 20
drawLetterBlock A 20 20
drawLetterBlock M 28 20
drawLetterBlock E 36 20
drawLetterBlock - 2 25
drawLetterBlock - 10 25
drawLetterBlock - 18 25
drawLetterBlock - 26 25
drawLetterBlock - 34 25
drawLetterBlock - 42 25
drawLetterBlock - 50 25
drawLetterBlock P 4 35
drawLetterBlock L 12 35
drawLetterBlock A 20 35
drawLetterBlock Y 28 35
drawLetterBlock A 4 45
drawLetterBlock 6 12 45
drawLetterBlock A 20 45
drawLetterBlock 1 28 45
drawLetterBlock N 36 45
drawLetterBlock "?" 44 45
}
proc checkForBodyCollision {snake} {
set snakehead $snake(2)
set len $snake(1)
set i 1
while {$i $len <} {
set act [expr $i 2 +]
set snakepiece $snake($act)
if {[expr $snakehead(0) $snakepiece(0) =] [expr $snakehead(1) $snakepiece(1) =] &} {
return "1"
}
incr i
}
return "0"
}
# a piece of food at a random-ish spot
set foodpiece [getRandomFoodPos [asarray "8 5"]]
# snake direction, length, segments
set snake [asarray "0 0"]
set snake [addSnakeSegment $snake 6 8]
set snake [addSnakeSegment $snake 5 8]
set ltime [time]
# goal delay of 150ms
set goal_delay 150
set real_delay $goal_delay
while {1 1 =} {
set key [keyin]
drawFrame
drawBackground
drawSnake $snake
putpixel $foodpiece(0) $foodpiece(1) 255 0 0
# key input for movement
# d
if {$key 100 =} {
set snake(0) [expr $snake(0) 1 +]
}
# a
if {$key 97 =} {
set snake(0) [expr $snake(0) 1 -]
}
if {$snake(0) 3 >} {
set snake(0) 0
}
if {$snake(0) 0 <} {
set snake(0) 3
}
set snake [moveSnake $snake]
set snakehead $snake(2)
if {[expr $snakehead(0) $foodpiece(0) =] [expr $snakehead(1) $foodpiece(1) =] &} {
# eat food
set len $snake(1)
set snaketail $snake([expr $len 1 +])
set snake [addSnakeSegment $snake $snaketail(0) $snaketail(1)]
set foodpiece [getRandomFoodPos [asarray "$foodpiece(0) $foodpiece(1)"]]
}
if {[checkForBodyCollision $snake] 1 =} {
ShowGameOver
break
}
if {$snakehead(0) 15 >} {
ShowGameOver
break
}
if {$snakehead(1) 15 >} {
ShowGameOver
break
}
if {$snakehead(0) 0 <} {
ShowGameOver
break
}
if {$snakehead(1) 0 <} {
ShowGameOver
break
}
set deltatime [expr [time] $ltime -]
set ltime [time]
set dtms [expr $deltatime 1000 *]
set diff [expr $dtms $goal_delay -]
if {$diff 0 >} {
decr real_delay
}
if {$diff 0 <} {
incr real_delay
}
sleep $real_delay
}`;
// holds a character that makes up a word, was originally
// alphanumerics but became other characters
function isAlphaNum(s) {
return s.match(/^[a-z0-9+-/*.=><()$#%_&]+$/i);
}
// an async way to sleep in javascript without eating the browser thread
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// canvas for screen output
let canvas = document.getElementById('screenout');
let ctx = canvas.getContext('2d');
// global variables
let varstack = []; // the current variable stack
let procstack = {}; // the current process list
let exec_level = 0; // the level of the varstack in the current scope
varstack.push({}); // add scope level 0
// holds the code of the last key pressed for keypress functionality
let last_key_pressed = 0;
function resetS() { // reset interpreter state
varstack = [];
procstack = {};
exec_level = 0;
varstack.push({});
}
function deepcopy(a) { // handles deepcopy of arrays for passing to procedures so unwanted side effects don't happen with immutable variables
return JSON.parse(JSON.stringify(a));
}
// handle substitutions of words
async function runSub(w) {
if(w[0] == '{') { // brace substitution
return w.substring(1, w.length - 1);
}
if(w[0] == '$') { // variable substitution
w = w.substring(1, w.length);
if(w[w.length - 1] == ')') {
let spl = w.split('(');
let name = spl[0];
let index = await runSub(spl[1].substring(0, spl[1].length - 1));
if(varstack[exec_level][name] == undefined || varstack[exec_level][name][index] == undefined) {
return `error var ${name} not initialized`;
}
return deepcopy(varstack[exec_level][name])[index];
} else {
if(varstack[exec_level][w] == undefined) {
return `error var ${w} not initialized`;
}
return deepcopy(varstack[exec_level][w]);
}
}
if(w[0] == '[') { // bracket substitution
w = w.substring(1, w.length - 1);
return await runTCL(w, 'BRACKET');
}
if(w[0] == '"') { // quote sub, works different than typical substitution
let ret_str = '';
let sb_b = '';
for(let i = 0;i < w.length;i++) {
if(w[i] == '\\') {
ret_str += w[++i];
continue;
}
if(w[i] == '$') {
while(w[i] != ' ' && w[i] != '\t' && w[i] != '\n' && i < w.length - 1) {
sb_b += w[i++];
}
ret_str += await runSub(sb_b) + w[i];
sb_b = '';
} else if(w[i] == '[') {
sb_b += w[i];
let watch = 1;
i++;
if(w[i] == ']') {
watch--;
}
while(watch != 0) {
sb_b += w[i];
i++;
if(w[i] == ']') {
watch--;
} else if(w[i] == '[') {
watch++;
}
}
sb_b += w[i];
ret_str += await runSub(sb_b);
sb_b = '';
}
else {
ret_str += w[i];
}
}
return ret_str.substring(1, ret_str.length - 1);
}
return w; // no sub can be performed
}
// debug function to log something to the chrome debug console in color
function logColor(txt, color) {
console.log(`%c${exec_level}>${txt}`, `background: ${color}`);
}
const debug = false; // should the runCmd print debug info?
// handles definitions for all commands that need running
async function runCmd(cl) {
if (debug) logColor(cl, '#E66');
for(let i = 0;i < cl.length;i++) { // handle substitutions such as variable substitutions and command subs
cl[i] = await runSub(cl[i]);
if(typeof(cl[i]) == 'string' && cl[i].startsWith('error')) { // bubble errors
return cl[i];
}
}
if (debug) logColor(cl, '#BBB');
// if it's a process
if (cl[0] in procstack) {
let proc = procstack[cl[0]];
let args = proc['procargs'].split(' ');
if(proc['procargs'] == '') {
args = [];
}
if(cl.length - 1 < args.length) {
return `error not enough args passed to proc ${cl[0]}`;
}
exec_level++; // advance to a nested lexical scope
varstack.push({}); // new variable stack for this scope
for(let i = 0;i < args.length;i++) { // set each variable to the passed in arguments
varstack[exec_level][args[i]] = cl[i + 1];
}
// run the procedure and get the result
let proc_result = await runTCL(proc['procbody'], cl[0]);
// remove the old varstack
varstack.pop();
// step out of the lexical scope
exec_level--;
// return either a return statement or the result of the procedure
return (typeof(proc_result) == 'object' && 'return' in proc_result) ? proc_result['return'] : proc_result;
}
// any other non special procedure command
switch(cl[0]) {
case 'set': // set var [val]
// sets a variable to a value, including arrays
if(cl[1][cl[1].length - 1] == ')') {
let spl = cl[1].split('(');
let name = spl[0];
let ind = await runSub(spl[1].substring(0, spl[1].length - 1));
if(varstack[exec_level][name] == undefined) {
varstack[exec_level][name] = {};
}
varstack[exec_level][name][ind] = cl[2];
} else {
varstack[exec_level][cl[1]] = cl[2];
}
break;
case 'proc': // proc name {args} {body}
// define a procedure
procstack[cl[1]]= {procargs: cl[2], procbody: cl[3]};
break;
case 'puts': // puts value
// print a value to the result box
logColor('\t\n\t' + cl[1], '#BAF');
addToResultBox('\=> ' + cl[1]);
break;
case 'putpixel': // putpixel x y r g b
// print a pixel to the canvas of color rgb
//logColor(`\t\n\t ${cl[1]} ${cl[2]} ${cl[3]} ${cl[4]} ${cl[5]}`, '#F00');
let coordx = cl[1];
let coordy = cl[2];
let r = cl[3];
let g = cl[4];
let b = cl[5];
ctx.fillStyle = `rgb(${r},${g},${b})`
ctx.fillRect(parseFloat(coordx), parseFloat(coordy), 1, 1);
break;
case 'prntstack': // DEBUG, prints the current variable stack
console.log(varstack);
break;
case 'putsdbg': // DEBUG, puts the memory representation of the current command
console.log(cl);
break;
case 'fillcolor': // fillcolor x y w h r g b
// fill a rectangle at x, y with width and height with a color rgb
ctx.fillStyle = `rgb(${cl[5]},${cl[6]},${cl[7]})`
ctx.fillRect(parseFloat(cl[1]), parseFloat(cl[2]), parseFloat(cl[3]), parseFloat(cl[4]));
break;
case 'asarray': // turns a list of arguments into an array representation
// useful for many things such as matrix ops
let spl = cl[1].split(' ');
let nw = {};
for(let s = 0;s < spl.length;s++) {
nw[s] = spl[s]
}
return nw;
break;
case 'sleep': // sleep for x amount of time
await sleep(parseFloat(cl[1]));
break;
case 'time': // get the current timing as a float with seconds and decimal subseconds
return '' + (Date.now() / 1000)
break;
case 'setpixsz': // set the canvas width and height
canvas.width = cl[1];
canvas.height = cl[2];
break;
case 'keyin': // return the last key pressed and clear the buffer
let kp = last_key_pressed;
last_key_pressed = '';
return '' + kp;
break;
case 'if': // if {condition} {body} [{else} {body}]
// a classic if statement
let ifstatement = cl[1];
let ifbody = cl[2];
let elsebody = cl[3];
let res = '';
// run the if statement condition using expr
if(await runTCL('expr ' + ifstatement, 'IFSTATE') == 'true') {
res = await runTCL(ifbody, 'IFBODY'); // if true run the body
} else if(elsebody != undefined) {
res = await runTCL(elsebody, 'ELSEBODY'); // if false run the alt body if it exists
}
// handle return and break statements in procedures
if(typeof(res) == 'object') {
if('return' in res) {
return res;
} else if (res[0] == 'break') {
return res;
} else {
//console.log(res);
return 'error got strange object in if result';
}
}
// bubble error if an error occurs
if(res.startsWith('error')) return res;
break;
case 'while': // while loop
let whilestatement = cl[1];
let whilebody = cl[2];
//let loopkill = 0;
// while the expr of the while statement is true
while(await runTCL('expr ' + whilestatement, 'WHILESTATE') == 'true') {
// then run the whilebody code
let res = await runTCL(whilebody, 'WHILEBODY');
// if a return or a break, kill the loop or procedure
if(typeof(res) == 'object' && 'return' in res) return res;
if(typeof(res) == 'object' && res[0] == 'break') break;
if(res.startsWith('error')) return res; // if an error occurs bubble it up
/*if(++loopkill == 200) {
return 'error loop took too long';
}*/
}
break;
case 'break': // standard non-returning break statement to break loops
return ['break'];
break;
case 'uplevel': // access the variables in a procedure above the current lexical scope
if (debug) logColor('UPLEVEL START', '#FF0');
exec_level--;
let tc_res = await runTCL(cl[1], 'UPLEVEL');
exec_level++;
//console.log(exec_name);
if (debug) logColor('UPLEVEL END', '#FF0');
return tc_res;
break;
case 'return': // return a value from a procedure
return {'return':cl[1]};
break;
case 'list': // removes first argument and then returns a string separated by spaces
let cll = cl.slice(1, cl.length);
return cll.join(' ');
break;
case 'expr': // run a math or boolean expression of some sort, uses reverse polish notation
let math_stack = []; // set up the math stack
for(let i = 1;i < cl.length;i++) {
if(!isNaN(parseFloat(cl[i]))) {
math_stack.push(parseFloat(cl[i]));
}
else if(cl[i] == 'true' || cl[i] == 'false') {
math_stack.push(cl[i]);
} else {
switch(cl[i]) {
case '+':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] + math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '-':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] - math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '*':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] * math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '/':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] / math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '=':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] == math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '&':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] == 'true' && math_stack[math_stack.length - 1] == 'true';
math_stack.pop();
break;
case '>':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] > math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '<':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] < math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '>=':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] >= math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '<=':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] <= math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '!=':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] != math_stack[math_stack.length - 1];
math_stack.pop();
break;
case '%':
math_stack[math_stack.length - 2] = math_stack[math_stack.length - 2] % math_stack[math_stack.length - 1];
math_stack.pop();
break;
}
}
}
if(math_stack.length == 0) {
return cl[1];
}
return '' + math_stack[0]; // return the result as a string because it is friendly for the interpreter
break;
default:
return `error unknown command ${cl[0]}`;
}
return '';
}
// invoke the TCL interpreter
async function runTCL(tcs, name) {
// if not running then break out of the interpreter loop
if(!running) {
return 'error execution halted'; // currently this sometimes misses if an if statement is running an expr
}
// string builder to build up commands from characters
let sb = '';
// a list of the parsed command words
let cmd_list = [];
// linecount of the program
let mylinecount = 0;
// the last return value to return from the interpreter
let lastval = '';
// for each char in the input
for(let i = 0;i < tcs.length;i++) {
// if the end of a word or line
if (tcs[i] == ' ' || tcs[i] == '\t' || tcs[i] == '\n' || tcs[i] == ';') {
// if the stringbuffer is not empty
if(sb != '') {
cmd_list.push(sb); // then add it to the word list and empty the sb
sb = '';
}
// if the end of a command and there is words built up in the command list
if((tcs[i] == '\n' || tcs[i] == ';') && cmd_list.length > 0) {
// if the command is a comment then skip that line
if(cmd_list[0][0] == '#') {
cmd_list = [];
mylinecount++;
continue;
}
// otherwise run the command and get the result
let cmd_result = await runCmd(cmd_list);
// if the command has an error
if(typeof(cmd_result) == 'string' && cmd_result.startsWith('error')) {
// print it and bubble the error back up
let error_build = `${cmd_result} in ${name} on line ${mylinecount}`;
addToResultBox(error_build);
return cmd_result;
} else {
// otherwise, if there is a return statement or a break statement
if(typeof(cmd_result) == 'object' && 'return' in cmd_result || cmd_result[0] == 'break') {
return cmd_result; // immediately leave the interpreter
}
lastval = cmd_result; // otherwise just set the last value to the result of the command
}
//console.log(cmd_list);
cmd_list = []; // empty the command list when done
}
// for each newline advance the line count
if(tcs[i] == '\n') mylinecount++;
continue;
}
// if there is a value in the character list that is valid for a word
if(isAlphaNum(tcs[i])) { // build a word
sb += tcs[i];
} else if(tcs[i] == '[') { // brackets are a special word
sb += tcs[i];
let watch = 1;
i++;
if(tcs[i] == ']') {
watch--;
}
while(watch != 0 && i < tcs.length) {
if(tcs[i] == '\n') mylinecount++;
sb += tcs[i];
i++;
if(tcs[i] == ']') {
watch--;
} else if(tcs[i] == '[') {
watch++;
}
}
if(tcs[i] != ']') {
return 'error start square bracket without end square bracket';
}
sb += tcs[i];
} else if(tcs[i] == '{') { // braces are a special word
sb += tcs[i];
let watch = 1;
i++;
if(tcs[i] == '}') {
watch--;
}
while(watch != 0 && i < tcs.length) {
if(tcs[i] == '\n') mylinecount++;
sb += tcs[i];
i++;
if(tcs[i] == '}') {
watch--;
} else if(tcs[i] == '{') {
watch++;
}
}
if(tcs[i] != '}') {
return 'error start curly bracket without end curly bracket';
}
sb += tcs[i];
} else if(tcs[i] == '"') { // quotes are a special word
sb += tcs[i];
i++;
while(tcs[i] != '"' && i < tcs.length) {
if(tcs[i] == '\n') mylinecount++;
sb += tcs[i];
i++;
}
if(tcs[i] != '"') {
return 'error start quote without end quote';
}
sb += tcs[i];
} else if(tcs[i] == '\\') { // backslash escape
sb += tcs[++i];
}
else { // if none of these match report an error that none match
return `error invalid char ${tcs[i]}`;
//break;
}
}
// after the loop,
// if there's anything left in the stringbuffer process it
if(sb != '') {
cmd_list.push(sb);
sb = '';
}
// run leftover command
if(cmd_list.length > 0) {
if(cmd_list[0][0] == '#') {
cmd_list = [];
mylinecount++;
return lastval;
}
let cmd_result = await runCmd(cmd_list);
if(typeof(cmd_result) == 'string' && cmd_result.startsWith('error')) {
let error_build = `${cmd_result} in ${name} on line ${mylinecount}`;
addToResultBox(error_build);
return cmd_result;
} else {
lastval = cmd_result;
}
}
// return the last value
return lastval;
}
// set the code editor to the default value initially
document.getElementById('code-editor').getElementsByClassName('codes')[0].value = tcsr;
// add a log line to the result box
function addToResultBox(s) {
let c_res = document.getElementById('result').getElementsByClassName('resbox')[0];
c_res.value += s + '\n';
c_res.scrollTop = c_res.scrollHeight;
}
// running is used to prevent the button from starting again
// stopped is used to track when the previous thread has actually exited the loops it is processing
// aka running
// this setup is needed to sync the asynchronous code
let running = false;
let stopped = true;
async function runTclButton() {
if (running) {
running = false;
}
if(!stopped) {
setTimeout(runTclButton, 100);
return;
}
// preset the canvas width and height to 64x64
// this preset can be changed later by TCL
canvas.width = 64;
canvas.height = 64;
// clear the canvas to solid white by default
ctx.fillStyle = `rgb(255,255,255)`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// now the TCL interpreter is running but not stopped
running = true;
stopped = false;
// get the code in the code editor
let code = document.getElementById('code-editor').getElementsByClassName('codes')[0].value;
addToResultBox('START');
let t0 = performance.now();
resetS(); // reset the interpreter
await runTCL(standard_library, 'STD'); // parse the standard library before running the code
let t_result = await runTCL(code, 'MAIN');
let t1 = performance.now();
logColor(`execution finished in ${(t1 - t0) / 1000} seconds`, '#5EBA7D');
addToResultBox(`execution finished in ${(t1 - t0) / 1000} seconds\n`);
running = false;
stopped = true;
}
// helps with asynchronous stopping
function stopTCL() {
running = false;
}
// holds the last key pressed anywhere on the page
// to enable it to be easily used by the interpreter
window.onkeypress = (e) => {
last_key_pressed = e.key.charCodeAt(0);
};
About me
Contact Information:
Voicemail box #: +14408478142
Email: administrator@ryancdeyong.us
All software on this website uses the MIT License.