var localVars = frame[1];
$(self.dataVisElement).append('Local variables for ' + funcName + ':
');
// render locals in alphabetical order for tidiness:
var orderedVarnames = [];
$.each(localVars, function(varname, val) {
orderedVarnames.push(varname);
});
orderedVarnames.sort();
if (orderedVarnames.length > 0) {
$(self.dataVisElement + " .vizFrame:last").append('
');
var tbl = $(self.outputPaneTable + " table:last");
$.each(orderedVarnames, function(i, varname) {
var val = localVars[varname];
tbl.append(' | |
');
var curTr = tbl.find('tr:last');
if (varname == '__return__') {
curTr.find("td.varname").html('return value');
}
else {
curTr.find("td.varname").html(varname);
}
self.renderData(val, curTr.find("td.val"));
});
tbl.find("tr:last").find("td.varname").css('border-bottom', '0px');
tbl.find("tr:last").find("td.val").css('border-bottom', '0px');
}
else {
$(this.dataVisElement + " .vizFrame:last").append(' none');
}
});
}
// render globals LAST:
$(this.dataVisElement).append('Global variables:
');
var nonEmptyGlobals = false;
var curGlobalFields = {};
if (curEntry.globals != undefined) {
$.each(curEntry.globals, function(varname, val) {
curGlobalFields[varname] = true;
nonEmptyGlobals = true;
});
}
if (nonEmptyGlobals) {
$(this.dataVisElement + " .vizFrame:last").append('
');
// render all global variables IN THE ORDER they were created by the program,
// in order to ensure continuity:
var orderedGlobals = []
// iterating over ALL instructions (could be SLOW if not for our optimization below)
for (var i = 0; i <= this.curInstr; i++) {
// some entries (like for exceptions) don't have GLOBALS
if (this.curTrace[i].globals == undefined) continue;
$.each(this.curTrace[i].globals, function(varname, val) {
// eliminate duplicates (act as an ordered set)
if ($.inArray(varname, orderedGlobals) == -1) {
orderedGlobals.push(varname);
curGlobalFields[varname] = undefined; // 'unset it'
}
});
var earlyStop = true;
// as an optimization, STOP as soon as you've found everything in curGlobalFields:
for (o in curGlobalFields) {
if (curGlobalFields[o] != undefined) {
earlyStop = false;
break;
}
}
if (earlyStop) {
break;
}
}
var tbl = $(this.outputPaneTable + " table:last");
var self = this;
// iterate IN ORDER (it's possible that not all vars are in curEntry.globals)
$.each(orderedGlobals, function(i, varname) {
var val = curEntry.globals[varname];
if (val != undefined) { // might not be defined at this line, which is OKAY!
tbl.append(' | |
');
var curTr = tbl.find('tr:last');
curTr.find("td.varname").html(varname);
self.renderData(val, curTr.find("td.val"));
}
});
tbl.find("tr:last").find("td.varname").css('border-bottom', '0px');
tbl.find("tr:last").find("td.val").css('border-bottom', '0px');
}
else {
$(this.dataVisElement + " .vizFrame:last").append(' none');
}
}
// render the JS data object obj inside of jDomElt,
// which is a jQuery wrapped DOM object
// (obj is in a format encoded by cgi-bin/pg_encoder.py)
Visualizer.prototype.renderData = function(obj, jDomElt) {
// dispatch on types:
var typ = typeof obj;
if (obj == null) {
jDomElt.append('None');
}
else if (typ == "number") {
jDomElt.append('' + obj + '');
}
else if (typ == "boolean") {
if (obj) {
jDomElt.append('True');
}
else {
jDomElt.append('False');
}
}
else if (typ == "string") {
// escape using htmlspecialchars to prevent HTML/script injection
// print as a JSON literal
var literalStr = htmlspecialchars(obj);
literalStr = literalStr.replace('\"', '\\"');
literalStr = '"' + literalStr + '"';
jDomElt.append('' + literalStr + '');
}
else if (typ == "object") {
assert($.isArray(obj));
if (obj[0] == 'LIST') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('empty list (id=' + obj[1] + ')
');
}
else {
jDomElt.append('list (id=' + obj[1] + '):
');
jDomElt.append('');
var tbl = jDomElt.children('table');
var headerTr = tbl.find('tr:first');
var contentTr = tbl.find('tr:last');
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'LIST' tag and ID entry
// add a new column and then pass in that newly-added column
// as jDomElt to the recursive call to child:
headerTr.append('');
headerTr.find('td:last').append(ind - 2);
contentTr.append(' | ');
self.renderData(val, contentTr.find('td:last'));
});
}
}
else if (obj[0] == 'TUPLE') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('empty tuple (id=' + obj[1] + ')
');
}
else {
jDomElt.append('tuple (id=' + obj[1] + '):
');
jDomElt.append('');
var tbl = jDomElt.children('table');
var headerTr = tbl.find('tr:first');
var contentTr = tbl.find('tr:last');
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'TUPLE' tag and ID entry
// add a new column and then pass in that newly-added column
// as jDomElt to the recursive call to child:
headerTr.append('');
headerTr.find('td:last').append(ind - 2);
contentTr.append(' | ');
self.renderData(val, contentTr.find('td:last'));
});
}
}
else if (obj[0] == 'SET') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('empty set (id=' + obj[1] + ')
');
}
else {
jDomElt.append('set (id=' + obj[1] + '):
');
jDomElt.append('');
var tbl = jDomElt.children('table');
// create an R x C matrix:
var numElts = obj.length - 2;
// gives roughly a 3x5 rectangular ratio, square is too, err,
// 'square' and boring
var numRows = Math.round(Math.sqrt(numElts));
if (numRows > 3) {
numRows -= 1;
}
var numCols = Math.round(numElts / numRows);
// round up if not a perfect multiple:
if (numElts % numRows) {
numCols += 1;
}
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'SET' tag and ID entry
if (((ind - 2) % numCols) == 0) {
tbl.append('
');
}
var curTr = tbl.find('tr:last');
curTr.append(' | ');
self.renderData(val, curTr.find('td:last'));
});
}
}
else if (obj[0] == 'DICT') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('empty dict (id=' + obj[1] + ')
');
}
else {
jDomElt.append('dict (id=' + obj[1] + '):
');
jDomElt.append('');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 2) return; // skip 'DICT' tag and ID entry
tbl.append(' | |
');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
self.renderData(kvPair[0], keyTd);
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'INSTANCE') {
assert(obj.length >= 3);
jDomElt.append('' + obj[1] + ' instance (id=' + obj[2] + ')
');
if (obj.length > 3) {
jDomElt.append('');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 3) return; // skip type tag, class name, and ID entry
tbl.append(' | |
');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
// the keys should always be strings, so render them directly (and without quotes):
assert(typeof kvPair[0] == "string");
var attrnameStr = htmlspecialchars(kvPair[0]);
keyTd.append('' + attrnameStr + '');
// values can be arbitrary objects, so recurse:
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'CLASS') {
assert(obj.length >= 4);
var superclassStr = '';
if (obj[3].length > 0) {
superclassStr += ('[extends ' + obj[3].join(',') + '] ');
}
jDomElt.append('' + obj[1] + ' class ' + superclassStr + '(id=' + obj[2] + ')
');
if (obj.length > 4) {
jDomElt.append('');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 4) return; // skip type tag, class name, ID, and superclasses entries
tbl.append(' | |
');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
// the keys should always be strings, so render them directly (and without quotes):
assert(typeof kvPair[0] == "string");
var attrnameStr = htmlspecialchars(kvPair[0]);
keyTd.append('' + attrnameStr + '');
// values can be arbitrary objects, so recurse:
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'CIRCULAR_REF') {
assert(obj.length == 2);
jDomElt.append('circular reference to id=' + obj[1] + '
');
}
else {
// render custom data type
assert(obj.length == 3);
typeName = obj[0];
id = obj[1];
strRepr = obj[2];
// if obj[2] is like ' at 0x84760>',
// then display an abbreviated version rather than the gory details
noStrReprRE = /<.* at 0x.*>/;
if (noStrReprRE.test(strRepr)) {
jDomElt.append('' + typeName + ' (id=' + id + ')');
}
else {
strRepr = htmlspecialchars(strRepr); // escape strings!
// warning: we're overloading tuple elts for custom data types
jDomElt.append('' + typeName + ' (id=' + id + '):
');
jDomElt.append('');
}
}
}
else {
alert("Error: renderData FAIL!");
}
}
Visualizer.prototype.renderPyCodeOutput = function(codeStr) {
var tbl = $(this.codeOutputTable);
tbl.html('');
var lines = codeStr.rtrim().split('\n');
$.each(lines, function(i, cod) {
var lineNo = i + 1;
var htmlCod = htmlspecialchars(cod);
tbl.append(' | |
');
var curRow = tbl.find('tr:last');
curRow.find('td.lineNo').html(lineNo);
curRow.find('td.cod').html(htmlCod);
});
}
PythonTutor.Visualizer = Visualizer;
})();
String.prototype.rtrim = function() {
return this.replace(/\s*$/g, "");
}
function assert(cond) {
if (!cond) {
alert("Error: ASSERTION FAILED");
}
}
// taken from http://www.toao.net/32-my-htmlspecialchars-function-for-javascript
function htmlspecialchars(str) {
if (typeof(str) == "string") {
str = str.replace(/&/g, "&"); /* must do & first */
// ignore these for now ...
//str = str.replace(/"/g, """);
//str = str.replace(/'/g, "'");
str = str.replace(//g, ">");
// replace spaces:
str = str.replace(/ /g, " ");
}
return str;
}