Making Future Posts Runnable Online with Stack Snippets
Python 2 (No STDIN)
Thanks to Skulpt, it has become very easy to write a Python interpreter.
function out(text) {
var output = document.getElementById("output");
output.innerHTML = output.innerHTML + text;
}
function builtinRead(x) {
if(Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined)
throw "File not found: '" + x + "'";
return Sk.builtinFiles["files"][x];
}
function run() {
var program = document.getElementById("python-in").value;
var output = document.getElementById("output");
output.innerHTML = "";
Sk.canvas = "canvas";
Sk.pre = "output";
Sk.configure({
output: out,
read: builtinRead
});
try {
Sk.importMainWithBody("<stdin>", false, program);
}
catch(e) {
throw new Error(e.toString());
}
}
#python-in {
border-radius: 3px;
background: rgb(250, 250, 250);
width: 95%;
height: 200px;
}
#output {
border-radius: 3px;
background: lightgray;
width: 95%;
height: 200px;
overflow: auto;
}
#canvas {
border: 1px solid gray;
border-radius: 3px;
height: 400px;
width: 400px;
}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://www.skulpt.org/static/skulpt.min.js"></script>
<script src="http://www.skulpt.org/static/skulpt-stdlib.js"></script>
Python code:<br/>
<textarea id="python-in" name="python-in" rows="10" cols="80"></textarea><br/>
<button id="run-code" onclick="run()">Run</button><br/>
<br/>
Output:<br/>
<pre id="output" name="output" rows="10" cols="10" disabled></pre><br/>
Canvas for turtles:<br/>
<canvas height="400" width="400" id="canvas">Your browser does not support HTML5 Canvas!</canvas>
No STDIN, but it's a pretty complete implementation of Python in JS. I would load the libraries from somewhere else but I can't find a host of them.
Befunge-93
Edit: I reworked the entire GUI now. I'm much happier with it. Some kind of breakpoint feature would be cool, but it's probably too much for this. Open Points I will probably revisit:
- Allow an infinite board
- Displaying the stack when stepping through the program would be neat
Since I just implemented this interpreter for this challenge it's not really extensively tested. However, I tried it with a variety of different programs now and it seems to work fine.
The latest version can be found at the latest revision of this fiddle.
function BefungeBoard(source, constraints) {
constraints = constraints || {
width: 80,
height: 25
};
this.constraints = constraints;
this.grid = source.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/).map(function (line) {
return (line + String.repeat(' ', constraints.width - line.length)).split('');
});
for (var i = this.grid.length; i < constraints.height; i++) {
this.grid[i] = String.repeat(' ', constraints.width).split('');
}
this.pointer = {
x: 0,
y: 0
};
this.direction = Direction.RIGHT;
}
BefungeBoard.prototype.nextPosition = function () {
var vector = this.direction.toVector(),
nextPosition = {
x: this.pointer.x + vector[0],
y: this.pointer.y + vector[1]
};
nextPosition.x = nextPosition.x < 0 ? this.constraints.width - 1 : nextPosition.x;
nextPosition.y = nextPosition.y < 0 ? this.constraints.height - 1 : nextPosition.y;
nextPosition.x = nextPosition.x >= this.constraints.width ? 0 : nextPosition.x;
nextPosition.y = nextPosition.y >= this.constraints.height ? 0 : nextPosition.y;
return nextPosition;
};
BefungeBoard.prototype.advance = function () {
this.pointer = this.nextPosition();
if (this.onAdvance) {
this.onAdvance.call(null, this.pointer);
}
};
BefungeBoard.prototype.currentToken = function () {
return this.grid[this.pointer.y][this.pointer.x];
};
BefungeBoard.prototype.nextToken = function () {
var nextPosition = this.nextPosition();
return this.grid[nextPosition.y][nextPosition.x];
};
var Direction = (function () {
var vectors = [
[1, 0],
[-1, 0],
[0, -1],
[0, 1]
];
function Direction(value) {
this.value = value;
}
Direction.prototype.toVector = function () {
return vectors[this.value];
};
return {
UP: new Direction(2),
DOWN: new Direction(3),
RIGHT: new Direction(0),
LEFT: new Direction(1)
};
})();
function BefungeStack() {
this.stack = [];
}
BefungeStack.prototype.pushAscii = function (item) {
this.pushNumber(item.charCodeAt());
};
BefungeStack.prototype.pushNumber = function (item) {
if (isNaN(+item)) {
throw new Error(typeof item + " | " + item + " is not a number");
}
this.stack.push(+item);
};
BefungeStack.prototype.popAscii = function () {
return String.fromCharCode(this.popNumber());
};
BefungeStack.prototype.popNumber = function () {
return this.stack.length === 0 ? 0 : this.stack.pop();
};
function Befunge(source, constraints) {
this.board = new BefungeBoard(source, constraints);
this.stack = new BefungeStack();
this.stringMode = false;
this.terminated = false;
this.digits = "0123456789".split('');
}
Befunge.prototype.run = function () {
for (var i = 1; i <= (this.stepsPerTick || 10); i++) {
this.step();
if (this.terminated) {
return;
}
}
requestAnimationFrame(this.run.bind(this));
};
Befunge.prototype.step = function () {
this.processCurrentToken();
this.board.advance();
};
Befunge.prototype.processCurrentToken = function () {
var token = this.board.currentToken();
if (this.stringMode && token !== '"') {
return this.stack.pushAscii(token);
}
if (this.digits.indexOf(token) !== -1) {
return this.stack.pushNumber(token);
}
switch (token) {
case ' ':
while ((token = this.board.nextToken()) == ' ') {
this.board.advance();
}
return;
case '+':
return this.stack.pushNumber(this.stack.popNumber() + this.stack.popNumber());
case '-':
return this.stack.pushNumber(-this.stack.popNumber() + this.stack.popNumber());
case '*':
return this.stack.pushNumber(this.stack.popNumber() * this.stack.popNumber());
case '/':
var denominator = this.stack.popNumber(),
numerator = this.stack.popNumber(),
result;
if (denominator === 0) {
result = +prompt("Illegal division by zero. Please enter the result to use:");
} else {
result = Math.floor(numerator / denominator);
}
return this.stack.pushNumber(result);
case '%':
var modulus = this.stack.popNumber(),
numerator = this.stack.popNumber(),
result;
if (modulus === 0) {
result = +prompt("Illegal division by zero. Please enter the result to use:");
} else {
result = Math.floor(numerator / modulus);
}
return this.stack.pushNumber(result);
case '!':
return this.stack.pushNumber(this.stack.popNumber() === 0 ? 1 : 0);
case '`':
return this.stack.pushNumber(this.stack.popNumber() < this.stack.popNumber() ? 1 : 0);
case '>':
this.board.direction = Direction.RIGHT;
return;
case '<':
this.board.direction = Direction.LEFT;
return;
case '^':
this.board.direction = Direction.UP;
return;
case 'v':
this.board.direction = Direction.DOWN;
return;
case '?':
this.board.direction = [Direction.RIGHT, Direction.UP, Direction.LEFT, Direction.DOWN][Math.floor(4 * Math.random())];
return;
case '_':
this.board.direction = this.stack.popNumber() === 0 ? Direction.RIGHT : Direction.LEFT;
return;
case '|':
this.board.direction = this.stack.popNumber() === 0 ? Direction.DOWN : Direction.UP;
return;
case '"':
this.stringMode = !this.stringMode;
return;
case ':':
var top = this.stack.popNumber();
this.stack.pushNumber(top);
return this.stack.pushNumber(top);
case '\\':
var first = this.stack.popNumber(),
second = this.stack.popNumber();
this.stack.pushNumber(first);
return this.stack.pushNumber(second);
case '$':
return this.stack.popNumber();
case '#':
return this.board.advance();
case 'p':
return this.board.grid[this.stack.popNumber()][this.stack.popNumber()] = this.stack.popAscii();
case 'g':
return this.stack.pushAscii(this.board.grid[this.stack.popNumber()][this.stack.popNumber()]);
case '&':
return this.stack.pushNumber(+prompt("Please enter a number:"));
case '~':
return this.stack.pushAscii(prompt("Please enter a character:")[0]);
case '.':
return this.print(this.stack.popNumber());
case ',':
return this.print(this.stack.popAscii());
case '@':
this.terminated = true;
return;
}
};
Befunge.prototype.withStdout = function (printer) {
this.print = printer;
return this;
};
Befunge.prototype.withOnAdvance = function (onAdvance) {
this.board.onAdvance = onAdvance;
return this;
};
String.repeat = function (str, count) {
var repeated = "";
for (var i = 1; i <= count; i++) {
repeated += str;
}
return repeated;
};
window['requestAnimationFrame'] = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
(function () {
var currentInstance = null;
function resetInstance() {
currentInstance = null;
}
function getOrCreateInstance() {
if (currentInstance !== null && currentInstance.terminated) {
resetInstance();
}
if (currentInstance === null) {
var boardSize = Editor.getBoardSize();
currentInstance = new Befunge(Editor.getSource(), {
width: boardSize.width,
height: boardSize.height
});
currentInstance.stepsPerTick = Editor.getStepsPerTick();
currentInstance.withStdout(Editor.append);
currentInstance.withOnAdvance(function (position) {
Editor.highlight(currentInstance.board.grid, position.x, position.y);
});
}
return currentInstance;
}
var Editor = (function (onExecute, onStep, onReset) {
var source = document.getElementById('source'),
sourceDisplay = document.getElementById('source-display'),
sourceDisplayWrapper = document.getElementById('source-display-wrapper'),
stdout = document.getElementById('stdout');
var execute = document.getElementById('execute'),
step = document.getElementById('step'),
reset = document.getElementById('reset');
var boardWidth = document.getElementById('board-width'),
boardHeight = document.getElementById('board-height'),
stepsPerTick = document.getElementById('steps-per-tick');
function showEditor() {
source.style.display = "block";
sourceDisplayWrapper.style.display = "none";
source.focus();
}
function hideEditor() {
source.style.display = "none";
sourceDisplayWrapper.style.display = "block";
var computedHeight = getComputedStyle(source).height;
sourceDisplayWrapper.style.minHeight = computedHeight;
sourceDisplayWrapper.style.maxHeight = computedHeight;
sourceDisplay.textContent = source.value;
}
function resetOutput() {
stdout.value = null;
}
function escapeEntities(input) {
return input.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
sourceDisplayWrapper.onclick = function () {
resetOutput();
showEditor();
onReset && onReset.call(null);
};
execute.onclick = function () {
resetOutput();
hideEditor();
onExecute && onExecute.call(null);
};
step.onclick = function () {
hideEditor();
onStep && onStep.call(null);
};
reset.onclick = function () {
resetOutput();
showEditor();
onReset && onReset.call(null);
};
return {
getSource: function () {
return source.value;
},
append: function (content) {
stdout.value = stdout.value + content;
},
highlight: function (grid, x, y) {
var highlighted = [];
for (var row = 0; row < grid.length; row++) {
highlighted[row] = [];
for (var column = 0; column < grid[row].length; column++) {
highlighted[row][column] = escapeEntities(grid[row][column]);
}
}
highlighted[y][x] = '<span class="activeToken">' + highlighted[y][x] + '</span>';
sourceDisplay.innerHTML = highlighted.map(function (lineTokens) {
return lineTokens.join('');
}).join('\n');
},
getBoardSize: function () {
return {
width: +boardWidth.innerHTML,
height: +boardHeight.innerHTML
};
},
getStepsPerTick: function () {
return +stepsPerTick.innerHTML;
}
};
})(function () {
getOrCreateInstance().run();
}, function () {
getOrCreateInstance().step();
}, resetInstance);
})();
.container {
width: 100%;
}
.so-box {
font-family:'Helvetica Neue', Arial, sans-serif;
font-weight: bold;
color: #fff;
text-align: center;
padding: .3em .7em;
font-size: 1em;
line-height: 1.1;
border: 1px solid #c47b07;
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3), 0 2px 0 rgba(255, 255, 255, 0.15) inset;
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
background: #f88912;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3), 0 2px 0 rgba(255, 255, 255, 0.15) inset;
}
.control {
display: inline-block;
border-radius: 6px;
float: left;
margin-right: 25px;
cursor: pointer;
}
.option {
padding: 10px 20px;
margin-right: 25px;
float: left;
}
input, textarea {
box-sizing: border-box;
}
textarea {
display: block;
white-space: pre;
overflow: auto;
height: 75px;
width: 100%;
max-width: 100%;
min-height: 25px;
}
span[contenteditable] {
padding: 2px 6px;
background: #cc7801;
color: #fff;
}
#controls-container, #options-container {
height: auto;
padding: 6px 0;
}
#stdout {
height: 50px;
}
#reset {
float: right;
}
#source-display-wrapper {
display: none;
width: 100%;
height: 100%;
overflow: auto;
border: 1px solid black;
box-sizing: border-box;
}
#source-display {
font-family: monospace;
white-space: pre;
padding: 2px;
}
.activeToken {
background: #f88912;
}
.clearfix:after {
content:".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.clearfix {
display: inline-block;
}
* html .clearfix {
height: 1%;
}
.clearfix {
display: block;
}
<div class="container">
<textarea id="source" placeholder="Enter your Befunge-93 program here" wrap="off">"39-egnufeB">:#,_@</textarea>
<div id="source-display-wrapper">
<div id="source-display"></div>
</div>
</div>
<div id="controls-container" class="container clearfix">
<input type="button" id="execute" class="control so-box" value="► Execute" />
<input type="button" id="step" class="control so-box" value="Step" />
<input type="button" id="reset" class="control so-box" value="Reset" />
</div>
<div id="stdout-container" class="container">
<textarea id="stdout" placeholder="Output" wrap="off" readonly></textarea>
</div>
<div id="options-container" class="container">
<div class="option so-box">Steps per Tick: <span id="steps-per-tick" contenteditable>500</span>
</div>
<div class="option so-box">Board Size: <span id="board-width" contenteditable>80</span> x <span id="board-height" contenteditable>25</span>
</div>
</div>
TI-BASIC
'cause who doesn't like TI-Basic?
I wanted to contribute to this, so I picked a language that is (in my humble opinion) slightly more complicated than, say, Deadfish, but within my control. I know a lot about my calculator, so I picked this.
I don't however, have any background experience with JavaScript/CSS/HTML. This was my first program. That means...
Please point out the errors you find in my code!
That being said, this is a limited version of TI-Basic. I will continue to implement things as I get around to them.
Features as of right now
- common control keywords (if,then,else,end)
- All available loops (while, repeat, for)
- disp command
- JavaScript arithmetic operators, via eval
- Lists
- Built in auto colon-ator (see for yourself)
- Random values of uninitialized variables, just like your calculator
- limited math - sin,cos,tan,asin,acos,atan,sinh,cosh,tanh,log,ln,int,round,abs.
- constants pi and e as variables
- Prompt, input, pause (Yay!)
- writes to output during execution (thanks to pseudonym117)
- simple pixel-on/off / clear screen capabilities
Liberties taken
- supports multi-character variable names. Unfortunately, this means
ab
is a variable, not the valuea*b
. This uni-character variable name limit drove me crazy on my calculator. - ignoring "goto" and "lbl". They were buggy even on the calculator unless you did it a certain way.
- Going to ignore prgm, OpenLib and ExecLib because there are no external/other files
What still needs to be done
Some sort of canvas for drawing (coming soon!)More drawing stuffSome way forprompt
andinput
commands in the text window (suggestions, anyone?)- Support for (the rest of) calculator math operations (there's BUCKETS)
- Suppot for
Ans
keyword shenanigans. - Support for rest of CTL keywords
- Some way to support a form of getkey, though most languages nowadays are event driven... Perhaps it could be the last pressed key.
I don't know if this is true, but JavaScript seems to think first, then display to a textarea, rather than display as it goes.- There's no CSS/HTML prettiness
I still don't know much about JavaScript. If any of the above are possible/impossible, please let me know.
function inputChanged() {
var codeText = document.getElementById('code-block');
if (codeText.value.charAt(0) !== ':') {
codeText.value = ':' + codeText.value;
}
var cursor = codeText.selectionEnd;
if (lastText.length < codeText.value.length) { //adding characters
if (/(\n)$/.test(codeText.value) === true || /(\n)(\n)/.test(codeText.value) === true) {
cursor++;
}
codeText.value = codeText.value.replace(/(\n)$/, '\n:');
codeText.value = codeText.value.replace(/(\n)(\n)/, '\n:\n');
} else { //removing characters
if (/(\n)$/.test(codeText.value) === true || /(\n)(\n)/.test(codeText.value) === true) {
cursor--;
}
codeText.value = codeText.value.replace(/(\n)$/, '');
codeText.value = codeText.value.replace(/(\n)(\n)/, '\n');
}
codeText.setSelectionRange(cursor, cursor);
lastText = codeText.value;
}
function onUserInput(e){
var output = document.getElementById('output');
//enter has code 13
if(awaitingInput && e.keyCode===13){
var lines = output.value.split('\n');
var input = lines[lines.length-1];
if(input.indexOf('?')!==-1){
input = input.substring(input.indexOf('?')+1,input.length);
}
if(newInputVarName !== null){
var iValue = replaceVars(input);
variables[newInputVarName]= eval(iValue);
}else{
output.value = output.value.substring(0,output.value.length-1);
}
awaitingInput = false;
}
}
var lastText = ':';
var L1 = [],
L2 = [],
L3 = [],
L4 = [],
L5 = [],
L6 = []; //note - these are special. They're like variables, and are considered in replaceVars.
var tokens = [':', '-->', 'if', 'then',
'else', 'for', 'while', ',',
'repeat', 'end', 'disp','prompt','input',
'pause','pxl-on','pxl-off','clrdraw'];
var variables = [];
var loopStack = []; //holds line numbers
var ifStack = [true]; //holds true or false
var typeStack = ['if'];
var repeatStack = []; //holds current iteration for repeats. NOTE: starts at 1 to accomidate repeat 0
var awaitingInput = false;
var newInputVarName=null;
var pixels = []; //96*64
var running = false;
var lineProcessing;
function run() {
if(running===false){
for(var pixelX=0;pixelX<96;pixelX++){
for(var pixelY=0;pixelY<64;pixelY++){
pixels[(pixelX,pixelY)]=0; //doesn't work
}
}
variables = [];
loopStack = [];
ifStack = [true];
typeStack = ['if'];
repeatStack = [];
clear();
var code = document.getElementById('code-block');
var lines = code.value.split('\n');
var currentLine = 0;
variables['e'] = Math.E;
variables['pi'] = Math.PI;
L1.push(3);
L1.push(4);
lineProcessing = setInterval(function(){
if(!awaitingInput){
running = true;
if(currentLine < lines.length || loopStack.length !== 0){
currentLine = processLine(lines,currentLine);
currentLine++;
}
else{
running = false;
lineProcessing.clearInterval();
}
}
},10);
}
}
function processLine(lines, currentLine) {
var tokens = tokenize(lines[currentLine]);
if (ifStack[ifStack.length - 1] === true){
if (tokens.indexOf('-->') !== -1) {
var index = tokens.indexOf('-->');
for (var i = 0; i < index; i++) {
tokens[i] = replaceVars(tokens[i]);
}
var value = eval(tokens[index - 1]);
var name = tokens[index + 1];
var listElement = false;
if (name.length > 4) {
if (/L[1-6]\(/.test(name) === true) {
var nextP = matchParenthese(name, 2);
var innerStuff = name.substring(3, nextP);
var value2 = eval(replaceVars(innerStuff));
var listNum = name.substring(0, 2);
if (value2 === Math.round(value2)) {
if (value2 > 0) {
if (listNum === 'L1') {
L1[value2] = value;
}
if (listNum === 'L2') {
L2[value2] = value;
}
if (listNum === 'L3') {
L3[value2] = value;
}
if (listNum === 'L4') {
L4[value2] = value;
}
if (listNum === 'L5') {
L5[value2] = value;
}
if (listNum === 'L6') {
L6[value2] = value;
}
} else {
printE('Arrays can only have positive indexes, and by some weird quirk of Texas Instruments, indexes start at 1, not 0.');
throw new Error();
}
} else {
printE('Sorry, this is not Javascript. Array indexes must be integers.');
throw new Error();
}
}
}
variables[name + ""] = value;
}
if (tokens.indexOf('disp') !== -1) {
var index2 = tokens.indexOf('disp');
for (var g = index2 + 1; g < tokens.length; g++) {
tokens[g] = replaceVars(tokens[g]);
if (tokens[g] === ',') {} else {
print(eval(tokens[g]));
}
}
}
if(tokens.indexOf('prompt')!==-1){
var pIndex = tokens.indexOf('prompt');
var newVar = tokens[pIndex+1];
document.getElementById('output').value += newVar+'=?';
awaitingInput=true;
newInputVarName = newVar;
}
if(tokens.indexOf('input')!==-1){
var pIndex = tokens.indexOf('input');
var newVar = tokens[pIndex+1];
document.getElementById('output').value += '?';
awaitingInput=true;
newInputVarName = newVar;
}
if(tokens.indexOf('pause')!==-1){
awaitingInput=true;
newInputVarName = null;
}
if(tokens.indexOf('pxl-on')!==-1){
var first = tokens[2].substring(1,tokens[2].length);
var second = tokens[4].substring(0,tokens[4].length-1);
first = replaceVars(first);
second = replaceVars(second);
var valueF = eval(first);
var valueS = eval(second);
drawPixel(valueF,valueS,1);
pixels[(valueF,valueS)] = 1;
}
if(tokens.indexOf('pxl-off')!==-1){
var first2 = tokens[2].substring(1,tokens[2].length);
var second2 = tokens[4].substring(0,tokens[4].length-1);
first2 = replaceVars(first2);
second2 = replaceVars(second2);
var valueF2 = eval(first2);
var valueS2 = eval(second2);
pixels[(valueF2,valueS2)] = 0;
drawPixel(valueF2,valueS2,0);
}
if(tokens.indexOf('clrdraw')!==-1){
clearPixels();
}
if (tokens.indexOf('if') !== -1) {
var index3 = tokens.indexOf('if');
tokens[index3 + 1] = replaceVars(tokens[index3 + 1]);
var value = eval(tokens[index3 + 1]);
ifStack.push(value);
typeStack.push('if');
}
if (tokens.indexOf('while') !== -1) {
var index4 = tokens.indexOf('while');
var expression = tokens[index4 + 1];
expression = replaceVars(expression);
var value2 = eval(expression);
if (value2 === true) {
typeStack.push('while');
loopStack.push(currentLine);
} else {
currentLine = nextEnd(currentLine, lines);
return currentLine;
}
}
if (tokens.indexOf('for') !== -1) {
var index5 = tokens.indexOf('for');
var varName = tokens[index5 + 1];
varName = varName.trim();
varName = varName.substring(1, varName.length);
var initValue = eval(replaceVars(tokens[index5 + 3]));
variables[varName] = initValue;
var endingValue = eval(replaceVars(tokens[index5 + 5]));
var increaseVal = replaceVars(tokens[index5 + 7]);
increaseVal = eval(increaseVal.substring(0, increaseVal.length - 1));
var diff = endingValue - initValue;
if (sign(diff) !== sign(increaseVal) && sign(diff) !== 0) {
currentLine = nextEnd(currentLine, lines);
return currentLine;
} else {
loopStack.push(currentLine);
typeStack.push('for');
}
}
if (tokens.indexOf('repeat') !== -1) {
var repeatVal = eval(replaceVars(tokens[1]));
if (repeatVal === true || repeatVal === false) {
//basically the same as a while loop...so just replace it!
var newLine = 'while ' + '!(' + tokens[1] + ')';
lines[currentLine] = newLine;
currentLine--;
return currentLine;
} else {
repeatStack.push(0);
typeStack.push('repeat');
loopStack.push(currentLine);
}
}
}
if (tokens.indexOf('else') !== -1) {
ifStack[ifStack.length - 1] = !ifStack[ifStack.length - 1];
}
if (tokens.indexOf('end') !== -1) {
if (typeStack[typeStack.length - 1] === 'if') {
ifStack.pop();
typeStack.pop();
} else if (typeStack[typeStack.length - 1] === 'while') {
var prevLineNum = loopStack[loopStack.length - 1];
var prevLine = lines[prevLineNum];
var prevTokens = tokenize(prevLine);
var whileIndex = prevTokens.indexOf('while');
var expression2 = replaceVars(prevTokens[whileIndex + 1]);
if (eval(expression2) === true) {
currentLine = prevLineNum - 1;
return currentLine;
} else {
loopStack.pop();
typeStack.pop();
}
} else if (typeStack[typeStack.length - 1] === 'repeat') {
var line = lines[loopStack[loopStack.length - 1]];
var tokenized = tokenize(line);
var numTimes = eval(replaceVars(tokenized[1]));
var currentIteration = repeatStack[repeatStack.length - 1];
currentIteration++;
if (currentIteration !== numTimes) {
repeatStack[repeatStack.length - 1] = currentIteration;
currentLine = loopStack[loopStack.length - 1]; //once currentLine++, will be on next line after repeat;
return currentLine;
} else {
repeatStack.pop();
typeStack.pop();
loopStack.pop();
}
} else if (typeStack[typeStack.length - 1] === 'for') {
var prevLineNum2 = loopStack[loopStack.length - 1];
var prevLine2 = lines[prevLineNum2];
var prevTokens2 = tokenize(prevLine2);
var forIndex = prevTokens2.indexOf('for');
var varName2 = prevTokens2[forIndex + 1];
varName2 = varName2.substring(1, varName2.length);
var initVal = eval(replaceVars(prevTokens2[forIndex + 3]));
var endVal = eval(replaceVars(prevTokens2[forIndex + 5]));
var stepVal = replaceVars(prevTokens2[forIndex + 7]);
stepVal = eval(stepVal.substring(0, stepVal.length - 1));
variables[varName2] = parseInt(stepVal, 10) + parseInt(variables[varName2], 10);
var signDiff = sign(endVal - initVal);
var condTrue = true;
if (signDiff === 0) {
condTrue = false;
}
if (signDiff === 1) {
if (variables[varName2] > endVal) {
condTrue = false;
}
} else {
if (variables[varName2] < endVal) {
condTrue = false;
}
}
if (condTrue === true) {
currentLine = prevLineNum2;
return currentLine;
} else {
typeStack.pop();
loopStack.pop();
}
}
}
return currentLine;
}
function sign(num) {
if (num === 0) {
return 0;
}
if (num > 0) {
return 1;
}
return -1;
}
function drawPixel(x,y,color){//color = 0 if off, 1 if on
var canvas = document.getElementById('canvas');
var width = canvas.width;
var height = canvas.height;
var pxWidth = width/96;
var pxHeight = height/64;
var ctx = canvas.getContext('2d');
if(color===1){
ctx.fillStyle = '#000';
}else{
ctx.fillStyle = '#fff';
}
ctx.fillRect((x/96)*width, (y/64)*height,pxWidth,pxHeight);
}
function nextEnd(line, lines) {
for (var i = line + 1; i < lines.length; i++) {
if (lines[i].indexOf('end') !== -1) {
return i;
}
}
return line;
}
function tokenize(line) {
line = line.trim();
var split = [];
var currentSplit = '';
var cursor = 0;
var unknown = '';
var allTokens = tokens;
while (cursor < line.length) {
var lengthOfCursor = line.length - cursor;
while (lengthOfCursor > 0) {
var index = allTokens.indexOf(line.substring(cursor, cursor + lengthOfCursor));
if (index !== -1) {
if (unknown !== '') {
split.push(unknown.trim());
}
unknown = '';
split.push(allTokens[index]);
cursor += lengthOfCursor - 1; //for cursor++ later
break;
}
lengthOfCursor--;
if (lengthOfCursor === 0) {
unknown = unknown.concat(line.charAt(cursor));
}
}
cursor++;
}
if (unknown !== '') {
split.push(unknown.trim());
}
return split;
}
function replaceVars(token) {
for (var i = 0; i < tokens.length; i++) {
if (token === tokens[i]) {
return token;
}
}
//deals with lists
for (var cursor = 0; cursor < token.length - 1; cursor++) {
if (/[^a-zA-Z_]L[1-6]|^L[1-6]/.test(token.substring(cursor, cursor + 3)) === true) {
var parenthIndex;
for (var newCurs = cursor; newCurs < token.length; newCurs++) {
if (token.charAt(newCurs) === '(') {
parenthIndex = newCurs;
break;
}
}
var nextPIndex = matchParenthese(token, parenthIndex);
var inner = token.substring(parenthIndex + 1, nextPIndex);
//note recursiveness, supports L1(L1(L1(5))), for example
var innerValue = eval(replaceVars(inner));
var num = parseInt(token.substring(parenthIndex - 1, parenthIndex));
var realValue;
if (num === 1) {
realValue = L1[innerValue];
}
if (num === 2) {
realValue = L2[innerValue];
}
if (num === 3) {
realValue = L3[innerValue];
}
if (num === 4) {
realValue = L4[innerValue];
}
if (num === 5) {
realValue = L5[innerValue];
}
if (num === 6) {
realValue = L6[innerValue];
}
if (innerValue !== Math.round(innerValue) || innerValue < 1) {
printE('array indexes must be integers greater or equal to 1. Why? ask Texas Instruments.');
throw new Error();
}
token = token.substring(0, parenthIndex - 2) + realValue + token.substring(nextPIndex + 1);
}
}
//Math stuff
var a;
var sinFunc = function (value) {
return Math.sin(value);
};
while ((a = returnValue('sin', token, sinFunc)) !== null) {
token = a;
}
var cosFunc = function (value) {
return Math.cos(value);
};
while ((a = returnValue('cos', token, cosFunc)) !== null) {
token = a;
}
var tanFunc = function (value) {
return Math.tan(value);
};
while ((a = returnValue('tan', token, tanFunc)) !== null) {
token = a;
}
var asinFunc = function (value) {
return Math.asin(value);
};
while ((a = returnValue('asin', token, asinFunc)) !== null) {
token = a;
}
var acosFunc = function (value) {
return Math.acos(value);
};
while ((a = returnValue('acos', token, acosFunc)) !== null) {
token = a;
}
var atanFunc = function (value) {
return Math.atan(value);
};
while ((a = returnValue('atan', token, atanFunc)) !== null) {
token = a;
}
//note my calculator doesn't have atan2...
var absFunc = function (value) {
return Math.abs(value);
};
while ((a = returnValue('abs', token, absFunc)) !== null) {
token = a;
}
var roundFunc = function (value) {
return Math.round(value);
};
while ((a = returnValue('round', token, roundFunc)) !== null) {
token = a;
}
var intFunc = function (value) {
return Math.floor(value);
};
while ((a = returnValue('int', token, intFunc)) !== null) {
token = a;
}
var coshFunc = function (value) {
return (Math.pow(Math.E, value) + Math.pow(Math.E, -1 * value)) / 2;
};
while ((a = returnValue('cosh', token, coshFunc)) !== null) {
token = a;
}
var sinhFunc = function (value) {
return (Math.pow(Math.E, value) - Math.pow(Math.E, -1 * value)) / 2;
};
while ((a = returnValue('sinh', token, sinhFunc)) !== null) {
token = a;
}
var tanhFunc = function (value) {
return (Math.pow(Math.E, value) - Math.pow(Math.E, -1 * value)) / (Math.pow(Math.E, value) + Math.pow(Math.E, -1 * value));
};
while ((a = returnValue('tanh', token, tanhFunc)) !== null) {
token = a;
}
//if token contains new variables, then initialize them with random values
var newReg = new RegExp('([^a-zA-Z_])([a-zA-Z_]+)([^a-zA-Z_])', 'g');
var newReg2 = new RegExp('^([a-zA-Z_]+)([^a-zA-Z_])', 'g');
var newReg3 = new RegExp('([^a-zA-Z_])([a-zA-Z_]+)$', 'g');
var newReg4 = new RegExp('^([a-zA-Z_]+)$', 'g');
var match1 = token.match(newReg);
if (match1 !== null) {
for (var q = 0; q < match1.length; q++) {
initializeVar(match1[q].substring(1, match1[q].length - 1));
}
}
var match2 = token.match(newReg2);
if (match2 !== null) {
for (var w = 0; w < match2.length; w++) {
initializeVar(match2[w].substring(0, match2[w].length - 1));
}
}
var match3 = token.match(newReg3);
if (match3 !== null) {
for (var e = 0; e < match3.length; e++) {
initializeVar(match3[e].substring(1, match3[e].length));
}
}
var match4 = token.match(newReg4);
if (match4 !== null) {
for (var r = 0; r < match4.length; r++) {
initializeVar(match4[r].substring(0, match4[r].length));
}
}
var varNames = [];
for (var key in variables) {
if (token.indexOf(key) !== -1) {
var regex1 = '([^a-zA-Z_])(' + key + ')([^a-zA-Z_])';
var reg = new RegExp(regex1, 'g');
token = token.replace(reg, '$1' + variables[key] + '$3');
var regex2 = '(^' + key + ')([^a-zA-Z_])';
var reg2 = new RegExp(regex2, 'g');
token = token.replace(reg2, variables[key] + '$2');
var regex3 = '([^a-zA-Z_])(' + key + ')$';
var reg3 = new RegExp(regex3, 'g');
token = token.replace(reg3, '$1' + variables[key]);
var reg4 = new RegExp('^' + key + '$', 'g');
token = token.replace(reg4, variables[key]);
}
}
return token;
}
function matchParenthese(token, index) {
var pStack = [];
for (var cursor = index + 1; cursor < token.length; cursor++) {
if (token.charAt(cursor) === '(') {
pStack.push(1);
}
if (token.charAt(cursor) === ')') {
if (pStack.length > 0) {
pStack.pop();
} else {
return cursor;
}
}
}
return index;
}
function returnValue(identifyer, token, toDoFunction) { //note: do NOT include parentheses in identifyer
for (var cursor = 0; cursor < token.length; cursor++) {
if (token.length > cursor + identifyer.length) {
if (token.substring(cursor, cursor + identifyer.length) === identifyer) {
if (cursor === 0 || /^[^a-zA-Z_]/.test(token.charAt(cursor - 1)) === true) {
if (nextNonWhiteChar(token, cursor + identifyer.length - 1) === '(') {
print(identifyer);
var nextParen = matchParenthese(token, cursor + identifyer.length + 1);
var inner = token.substring(cursor + identifyer.length + 1, nextParen);
inner = replaceVars(inner);
var value = eval(inner);
value = toDoFunction(value);
return token.substring(0, cursor) + value + token.substring(nextParen + 1, token.length);
}
}
}
}
}
return null;
}
function nextNonWhiteChar(token, index) { //give non-white character
for (var i = index + 1; i < token.length; i++) {
if (token.charAt(i) !== ' ') {
return token.charAt(i);
}
}
}
function clearPixels(){
var canvas = document.getElementById('canvas');
canvas.getContext('2d').fillStyle = '#fff';
canvas.getContext('2d').fillRect(0,0,canvas.width,canvas.height);
for(var x=0;x<96;x++){
for(var y=0;y<64;y++){
pixels[(x,y)] = 0;
}
}
}
function stop(){
running = false;
clearInterval(lineProcessing);
}
function initializeVar(name) {
if (!(name in variables) && name !== 'L1' && name !== 'L2' && name !== 'L3' && name !== 'L4' && name !== 'L5' && name !== 'L6') {
variables[name] = Math.random() * 100;
}
}
function clear() {
document.getElementById('output').value = '';
document.getElementById('error').value = '';
}
function printE(str) {
document.getElementById('error').value += str + '\n'
}
function print(str) {
document.getElementById('output').value += str + '\n';
}
<h1>Ti-Basic Interpreter</h1>
<h3>Code</h3>
<textarea id='code-block' cols='50' rows='7' oninput='inputChanged()'>:clrdraw
:for(a,2,96,2)
:for(b,(a/2)%2,64,2)
:pxl-on(a,b)
:end
:end</textarea>
<br>
<button width='20' height='10' onclick='run()'>Run</button>
<button width='20' height='10' onclick='stop()'>Stop</button>
<h3>Output (also input)</h3>
<textarea id='output' cols='50' rows='7' onkeypress='onUserInput(event)'></textarea>
<br>
<canvas id='canvas' width='192' height='128'></canvas>
<br>
<textarea id='error' cols='50' rows='7'></textarea>