openct-tasks/_common/modules/pemFioi/quickAlgo/python_runner.js

883 lines
29 KiB
JavaScript

/*
python_runner:
Python code runner.
*/
var currentPythonContext = null;
function PythonInterpreter(context, msgCallback) {
this.context = context;
this.messageCallback = msgCallback;
this._code = '';
this._editor_filename = "<stdin>";
this.context.runner = this;
this._maxIterations = 4000;
this._maxIterWithoutAction = 50;
this._resetCallstackOnNextStep = false;
this._paused = false;
this._isRunning = false;
this._stepInProgress = false;
this._resetDone = true;
this.stepMode = false;
this._steps = 0;
this._stepsWithoutAction = 0;
this._lastNbActions = null;
this._hasActions = false;
this._nbActions = 0;
this._allowStepsWithoutDelay = 0;
this._timeouts = [];
this._editorMarker = null;
this.availableModules = [];
this._argumentsByBlock = {};
this._definedFunctions = [];
this.nbNodes = 0;
this.curNode = 0;
this.readyNodes = [];
this.finishedNodes = [];
this.nodeStates = [];
this.waitingOnReadyNode = false;
var that = this;
this._skulptifyHandler = function (name, generatorName, blockName, nbArgs, type) {
if(!arrayContains(this._definedFunctions, name)) { this._definedFunctions.push(name); }
var handler = '';
handler += "\tcurrentPythonContext.runner.checkArgs('" + name + "', '" + generatorName + "', '" + blockName + "', arguments);";
handler += "\n\tvar susp = new Sk.misceval.Suspension();";
handler += "\n\tvar result = Sk.builtin.none.none$;";
// If there are arguments, convert them from Skulpt format to the libs format
handler += "\n\tvar args = Array.prototype.slice.call(arguments);";
handler += "\n\tfor(var i=0; i<args.length; i++) { args[i] = currentPythonContext.runner.skToJs(args[i]); };";
handler += "\n\tsusp.resume = function() { return result; };";
handler += "\n\tsusp.data = {type: 'Sk.promise', promise: new Promise(function(resolve) {";
handler += "\n\targs.push(resolve);";
// Count actions
if(type == 'actions') {
handler += "\n\tcurrentPythonContext.runner._nbActions += 1;";
}
handler += "\n\ttry {";
handler += '\n\t\tcurrentPythonContext["' + generatorName + '"]["' + blockName + '"].apply(currentPythonContext, args);';
handler += "\n\t} catch (e) {";
handler += "\n\t\tcurrentPythonContext.runner._onStepError(e)}";
handler += '\n\t}).then(function (value) {\nresult = value;\nreturn value;\n })};';
handler += '\n\treturn susp;';
return '\nmod.' + name + ' = new Sk.builtin.func(function () {\n' + handler + '\n});\n';
};
this._skulptifyValue = function(value) {
if(typeof value === "number") {
var handler = 'Sk.builtin.int_(' + value + ')';
} else if(typeof value === "boolean") {
var handler = 'Sk.builtin.bool(' + value.toString() + ')';
} else if(typeof value === "string") {
var handler = 'Sk.builtin.str(' + JSON.stringify(value) + ')';
} else if(Array.isArray(value)) {
var list = [];
for(var i=0; i<value.length; i++) {
list.push(this._skulptifyValue(value[i]));
}
var handler = 'Sk.builtin.list([' + list.join(',') + '])';
} else {
throw "Unable to translate value '" + value + "' into a Skulpt constant.";
}
return 'new ' + handler;
}
this._skulptifyConst = function(name, value) {
var handler = this._skulptifyValue(value);
return '\nmod.' + name + ' = ' + handler + ';\n';
};
this._injectFunctions = function () {
// Generate Python custom libraries from all generated blocks
this._definedFunctions = [];
if(this.context.infos && this.context.infos.includeBlocks && this.context.infos.includeBlocks.generatedBlocks) {
// Flatten customBlocks information for easy access
var blocksInfos = {};
for (var generatorName in this.context.customBlocks) {
for (var typeName in this.context.customBlocks[generatorName]) {
var blockList = this.context.customBlocks[generatorName][typeName];
for (var iBlock=0; iBlock < blockList.length; iBlock++) {
var blockInfo = blockList[iBlock];
blocksInfos[blockInfo.name] = {
nbArgs: 0, // handled below
type: typeName};
blocksInfos[blockInfo.name].nbsArgs = [];
if(blockInfo.anyArgs) {
// Allows to specify the function can accept any number of arguments
blocksInfos[blockInfo.name].nbsArgs.push(Infinity);
}
var variants = blockInfo.variants ? blockInfo.variants : (blockInfo.params ? [blockInfo.params] : []);
if(variants.length) {
for(var i=0; i < variants.length; i++) {
blocksInfos[blockInfo.name].nbsArgs.push(variants[i].length);
}
}
}
}
}
// Generate functions used in the task
for (var generatorName in this.context.infos.includeBlocks.generatedBlocks) {
var blockList = this.context.infos.includeBlocks.generatedBlocks[generatorName];
if(!blockList.length) { continue; }
var modContents = "var $builtinmodule = function (name) {\n\nvar mod = {};\nmod.__package__ = Sk.builtin.none.none$;\n";
if(!this._argumentsByBlock[generatorName]) {
this._argumentsByBlock[generatorName] = {};
}
for (var iBlock=0; iBlock < blockList.length; iBlock++) {
var blockName = blockList[iBlock];
var code = this.context.strings.code[blockName];
if (typeof(code) == "undefined") {
code = blockName;
}
var nbsArgs = blocksInfos[blockName] ? (blocksInfos[blockName].nbsArgs ? blocksInfos[blockName].nbsArgs : []) : [];
var type = blocksInfos[blockName] ? blocksInfos[blockName].type : 'actions';
if(type == 'actions') {
this._hasActions = true;
}
this._argumentsByBlock[generatorName][blockName] = nbsArgs;
modContents += this._skulptifyHandler(code, generatorName, blockName, nbsArgs, type);
}
// TODO :: allow selection of constants available in a task
// if(this.context.infos.includeBlocks.constants && this.context.infos.includeBlocks.constants[generatorName]) {
if(this.context.customConstants && this.context.customConstants[generatorName]) {
var constList = this.context.customConstants[generatorName];
for(var iConst=0; iConst < constList.length; iConst++) {
var name = constList[iConst].name;
if(this.context.strings.constant && this.context.strings.constant[name]) {
name = this.context.strings.constant[name];
}
modContents += this._skulptifyConst(name, constList[iConst].value)
}
}
//console.log(modContents);
modContents += "\nreturn mod;\n};";
Sk.builtinFiles["files"]["src/lib/"+generatorName+".js"] = modContents;
this.availableModules.push(generatorName);
}
}
};
this.checkArgs = function (name, generatorName, blockName, args) {
// Check the number of arguments corresponds to a variant of the function
if(!this._argumentsByBlock[generatorName] || !this._argumentsByBlock[generatorName][blockName]) {
console.error("Couldn't find the number of arguments for " + generatorName + "/" + blockName + ".");
return;
}
var nbsArgs = this._argumentsByBlock[generatorName][blockName];
if(nbsArgs.length == 0) {
// This function doesn't have arguments
if(args.length > 0) {
msg = name + "() takes no arguments (" + args.length + " given)";
throw new Sk.builtin.TypeError(msg);
}
} else if(nbsArgs.indexOf(args.length) == -1 && nbsArgs.indexOf(Infinity) == -1) {
var minArgs = nbsArgs[0];
var maxArgs = nbsArgs[0];
for(var i=1; i < nbsArgs.length; i++) {
minArgs = Math.min(minArgs, nbsArgs[i]);
maxArgs = Math.max(maxArgs, nbsArgs[i]);
}
if (minArgs === maxArgs) {
msg = name + "() takes exactly " + minArgs + " arguments";
} else if (args.length < minArgs) {
msg = name + "() takes at least " + minArgs + " arguments";
} else if (args.length > maxArgs){
msg = name + "() takes at most " + maxArgs + " arguments";
} else {
msg = name + "() doesn't have a variant accepting this number of arguments";
}
msg += " (" + args.length + " given)";
throw new Sk.builtin.TypeError(msg);
}
};
this._definePythonNumber = function() {
// Create a class which behaves as a Number, but can have extra properties
this.pythonNumber = function(val) {
this.val = new Number(val);
}
this.pythonNumber.prototype = Object.create(Number.prototype);
function makePrototype(func) {
return function() { return Number.prototype[func].call(this.val); }
}
var funcs = ['toExponential', 'toFixed', 'toLocaleString', 'toPrecision', 'toSource', 'toString', 'valueOf'];
for(var i = 0; i < funcs.length ; i++) {
this.pythonNumber.prototype[funcs[i]] = makePrototype(funcs[i]);
}
}
this.skToJs = function(val) {
// Convert Skulpt item to JavaScript
// TODO :: Might be partly replaceable with Sk.ffi.remapToJs
if(val instanceof Sk.builtin.bool) {
return val.v ? true : false;
} else if(val instanceof Sk.builtin.func) {
return function() {
var args = [];
for(var i = 0; i < arguments.length; i++) {
args.push(that._createPrimitive(arguments[i]));
}
var retp = new Promise(function(resolve, reject) {
var p = Sk.misceval.asyncToPromise(function() { return val.tp$call(args); });
p.then(function(val) { resolve(that.skToJs(val)); });
});
return retp;
}
} else if(val instanceof Sk.builtin.dict) {
var dictKeys = Object.keys(val);
var retVal = {};
for(var i = 0; i < dictKeys.length; i++) {
var key = dictKeys[i];
if(key == 'size' || key == '__class__') { continue; }
var subItems = val[key].items;
for(var j = 0; j < subItems.length; j++) {
var subItem = subItems[j];
retVal[subItem.lhs.v] = this.skToJs(subItem.rhs);
}
}
return retVal;
} else {
var retVal = val.v;
if(val instanceof Sk.builtin.tuple || val instanceof Sk.builtin.list) {
retVal = [];
for(var i = 0; i < val.v.length; i++) {
retVal[i] = this.skToJs(val.v[i]);
}
}
if(val instanceof Sk.builtin.tuple) {
retVal.isTuple = true;
}
if(val instanceof Sk.builtin.float_) {
retVal = new this.pythonNumber(retVal);
retVal.isFloat = true;
}
return retVal;
}
};
this.getDefinedFunctions = function() {
this._injectFunctions();
return this._definedFunctions.slice();
};
this._setTimeout = function(func, time) {
var timeoutId = null;
var that = this;
function wrapper() {
var idx = that._timeouts.indexOf(timeoutId);
if(idx > -1) { that._timeouts.splice(idx, 1); }
func();
}
timeoutId = window.setTimeout(wrapper, time);
this._timeouts.push(timeoutId);
}
this.waitDelay = function (callback, value, delay) {
this._paused = true;
if (delay > 0) {
var _noDelay = this.noDelay.bind(this, callback, value);
this._setTimeout(_noDelay, delay);
// We just waited some time, allow next steps to not be delayed
this._allowStepsWithoutDelay = Math.min(this._allowStepsWithoutDelay + Math.ceil(delay / 10), 100);
} else {
this.noDelay(callback, value);
}
};
this.waitEvent = function (callback, target, eventName, func) {
this._paused = true;
var listenerFunc = null;
var that = this;
listenerFunc = function(e) {
target.removeEventListener(eventName, listenerFunc);
that.noDelay(callback, func(e));
};
target.addEventListener(eventName, listenerFunc);
};
this.waitCallback = function (callback) {
// Returns a callback to be called once we can continue the execution
this._paused = true;
var that = this;
return function(value) {
that.noDelay(callback, value);
};
};
this.noDelay = function (callback, value) {
var primitive = this._createPrimitive(value);
if (primitive !== Sk.builtin.none.none$) {
// Apparently when we create a new primitive, the debugger adds a call to
// the callstack.
this._resetCallstackOnNextStep = true;
this.reportValue(value);
}
this._paused = false;
callback(primitive);
this._setTimeout(this._continue.bind(this), 10);
};
this.allowSwitch = function(callback) {
// Tells the runner that we can switch the execution to another node
var curNode = context.curNode;
var ready = function(readyCallback) {
that.readyNodes[curNode] = function() {
readyCallback(callback);
};
if(that.waitingOnReadyNode) {
that.waitingOnReadyNode = false;
that.startNode(that.curNode, curNode);
}
};
this.readyNodes[curNode] = false;
this.startNextNode(curNode);
return ready;
};
this.defaultSelectNextNode = function(runner, previousNode) {
var i = previousNode + 1;
if(i >= runner.nbNodes) { i = 0; }
do {
if(runner.readyNodes[i]) {
break;
} else {
i++;
}
if(i >= runner.nbNodes) { i = 0; }
} while(i != previousNode);
return i;
};
// Allow the next node selection process to be customized
this.selectNextNode = this.defaultSelectNextNode;
this.startNextNode = function(curNode) {
// Start the next node when one has been switched from
var newNode = this.selectNextNode(this, curNode);
this._paused = true;
if(newNode == curNode) {
// No ready node
this.waitingOnReadyNode = true;
} else {
// TODO :: switch execution
this.startNode(curNode, newNode);
}
};
this.startNode = function(curNode, newNode) {
setTimeout(function() {
that.nodeStates[curNode] = that._debugger.suspension_stack.slice();
that._debugger.suspension_stack = that.nodeStates[newNode];
that.curNode = newNode;
var ready = that.readyNodes[newNode];
if(ready) {
that.readyNodes[newNode] = false;
context.setCurNode(newNode);
if(typeof ready == 'function') {
ready();
} else {
that._paused = false;
that._continue();
}
} else {
that.waitingOnReadyNode = true;
}
}, 0);
};
this._createPrimitive = function (data) {
// TODO :: Might be replaceable with Sk.ffi.remapToPy
if (data === undefined || data === null) {
return Sk.builtin.none.none$; // Reuse the same object.
}
var type = typeof data;
var result = {v: data}; // Emulate a Skulpt object as default
if (type === 'number') {
if(Math.floor(data) == data) { // isInteger isn't supported by IE
result = new Sk.builtin.int_(data);
} else {
result = new Sk.builtin.float_(data);
}
} else if (type === 'string') {
result = new Sk.builtin.str(data);
} else if (type === 'boolean') {
result = new Sk.builtin.bool(data);
} else if (typeof data.length != 'undefined') {
var skl = [];
for(var i = 0; i < data.length; i++) {
skl.push(this._createPrimitive(data[i]));
}
result = new Sk.builtin.list(skl);
} else if (data) {
// Create a dict if it's an object with properties
var props = [];
for(var prop in data) {
if(data.hasOwnProperty(prop)) {
// We can pass a list [prop1name, prop1val, ...] to Skulpt's dict
// constructor ; however to work properly they need to be Skulpt
// primitives too
props.push(this._createPrimitive(prop));
props.push(this._createPrimitive(data[prop]));
}
}
if(props.length > 0) {
result = new Sk.builtin.dict(props);
}
}
return result;
};
this._onOutput = function (_output) {
that.print(_output);
};
this._onDebugOut = function (text) {
// console.log('DEBUG: ', text);
};
this._configure = function () {
Sk.configure({
output: this._onOutput,
debugout: this._onDebugOut,
read: this._builtinRead.bind(this),
yieldLimit: null,
execLimit: null,
debugging: true,
breakpoints: this._debugger.check_breakpoints.bind(this._debugger)
});
Sk.pre = "edoutput";
Sk.pre = "codeoutput";
// Disable document library
delete Sk.builtinFiles["files"]["src/lib/document.js"];
this._definePythonNumber();
this.context.callCallback = this.noDelay.bind(this);
};
this.print = function (message, className) {
if (message === 'Program execution complete') {
this._onFinished();
}
if (message) {
//console.log('PRINT: ', message, className || '');
}
};
this._onFinished = function () {
this.finishedNodes[this.curNode] = true;
this.readyNodes[this.curNode] = false;
if(this.finishedNodes.indexOf(false) != -1) {
// At least one node is not finished
this.startNextNode(this.curNode);
} else {
// All nodes are finished, stop the execution
this.stop();
}
try {
this.context.infos.checkEndCondition(this.context, true);
} catch (e) {
this._onStepError(e);
}
};
this._builtinRead = function (x) {
if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined)
throw "File not found: '" + x + "'";
return Sk.builtinFiles["files"][x];
};
this.get_source_line = function (lineno) {
return this._code.split('\n')[lineno];
};
this._continue = function () {
if (this.context.infos.checkEndEveryTurn) {
try {
this.context.infos.checkEndCondition(context, false);
} catch(e) {
this._onStepError(e);
return;
}
}
if (!this.context.allowInfiniteLoop && this._steps >= this._maxIterations) {
this._onStepError(window.languageStrings.tooManyIterations);
} else if (!this.context.allowInfiniteLoop && this._stepsWithoutAction >= this._maxIterWithoutAction) {
this._onStepError(window.languageStrings.tooManyIterationsWithoutAction);
} else if (!this._paused && this._isRunning) {
this.step();
}
};
this.initCodes = function (codes) {
if(Sk.running) {
if(typeof Sk.runQueue === 'undefined') {
Sk.runQueue = [];
}
Sk.runQueue.push({ctrl: this, codes: codes});
return;
}
// Set Skulpt to Python 3
Sk.python3 = true;
currentPythonContext = this.context;
this._debugger = new Sk.Debugger(this._editor_filename, this);
this._configure();
this._injectFunctions();
this._code = codes[0];
this._setBreakpoint(1, false);
if(typeof this.context.infos.maxIter !== 'undefined') {
this._maxIterations = Math.ceil(this.context.infos.maxIter/10);
}
if(typeof this.context.infos.maxIterWithoutAction !== 'undefined') {
this._maxIterWithoutAction = Math.ceil(this.context.infos.maxIterWithoutAction/10);
}
if(!this._hasActions) {
// No limit on
this._maxIterWithoutAction = this._maxIterations;
}
var susp_handlers = {};
susp_handlers["*"] = this._debugger.suspension_handler.bind(this);
this.nbNodes = codes.length;
this.curNode = 0;
context.setCurNode(this.curNode);
this.readyNodes = [];
this.finishedNodes = [];
this.nodeStates = [];
for(var i = 0; i < codes.length ; i++) {
this.readyNodes.push(true);
this.finishedNodes.push(false);
try {
var promise = this._debugger.asyncToPromise(this._asyncCallback(this._editor_filename, codes[i]), susp_handlers, this._debugger);
promise.then(this._debugger.success.bind(this._debugger), this._debugger.error.bind(this._debugger));
} catch (e) {
this._onOutput(e.toString() + "\n");
}
this.nodeStates.push(this._debugger.suspension_stack);
this._debugger.suspension_stack = [];
}
this._debugger.suspension_stack = this.nodeStates[0];
this._resetInterpreterState();
Sk.running = true;
this._isRunning = true;
};
this.run = function () {
if(this.stepMode) {
this._paused = this._stepInProgress;
this.stepMode = false;
}
this._setTimeout(this._continue.bind(this), 100);
};
this.runCodes = function(codes) {
this.initCodes(codes);
this.run();
};
this.runStep = function () {
this.stepMode = true;
if(this._isRunning && !this._stepInProgress) {
this.step();
}
};
this.nbRunning = function () {
return this._isRunning ? 1 : 0;
};
this.removeEditorMarker = function () {
var editor = this.context.blocklyHelper._aceEditor;
if(editor && this._editorMarker) {
editor.session.removeMarker(this._editorMarker);
this._editorMarker = null;
}
};
this.unSkulptValue = function (origValue) {
// Transform a value, possibly a Skulpt one, into a printable value
if(typeof origValue !== 'object' || origValue === null) {
var value = origValue;
} else if(origValue.constructor === Sk.builtin.dict) {
var keys = Object.keys(origValue);
var dictElems = [];
for(var i=0; i<keys.length; i++) {
if(keys[i] == 'size' || keys[i] == '__class__'
|| !origValue[keys[i]].items
|| !origValue[keys[i]].items[0]) {
continue;
}
var items = origValue[keys[i]].items[0];
dictElems.push('' + this.unSkulptValue(items.lhs) + ': ' + this.unSkulptValue(items.rhs));
}
var value = '{' + dictElems.join(',' ) + '}';
} else if(origValue.constructor === Sk.builtin.list) {
var oldArray = origValue.v;
var newArray = [];
for(var i=0; i<oldArray.length; i++) {
newArray.push(this.unSkulptValue(oldArray[i]));
}
var value = '[' + newArray.join(', ') + ']';
} else if(origValue.v !== undefined) {
var value = origValue.v;
if(typeof value == 'string') {
value = '"' + value + '"';
}
} else if(typeof origValue == 'object') {
var value = origValue;
}
return value;
};
this.reportValue = function (origValue, varName) {
// Show a popup displaying the value of a block in step-by-step mode
if(origValue === undefined
|| (origValue && origValue.constructor === Sk.builtin.func)
|| !this._editorMarker
|| !context.display
|| !this.stepMode) {
return origValue;
}
var value = this.unSkulptValue(origValue);
var highlighted = $('.aceHighlight');
if(highlighted.length == 0) {
return origValue;
} else if(highlighted.find('.ace_start').length > 0) {
var target = highlighted.find('.ace_start')[0];
} else {
var target = highlighted[0];
}
var bbox = target.getBoundingClientRect();
var leftPos = bbox.left+10;
var topPos = bbox.top-14;
if(typeof value == 'boolean') {
var displayStr = value ? window.languageStrings.valueTrue : window.languageStrings.valueFalse;
} else if(value === null) {
var displayStr = "None"
} else {
var displayStr = value.toString();
}
if(typeof value == 'boolean') {
displayStr = value ? window.languageStrings.valueTrue : window.languageStrings.valueFalse;
}
if(varName) {
displayStr = '' + varName + ' = ' + displayStr;
}
var dropDownDiv = '' +
'<div class="blocklyDropDownDiv" style="transition: transform 0.25s, opacity 0.25s; background-color: rgb(255, 255, 255); border-color: rgb(170, 170, 170); left: '+leftPos+'px; top: '+topPos+'px; display: block; opacity: 1; transform: translate(0px, -20px);">' +
' <div class="blocklyDropDownContent">' +
' <div class="valueReportBox">' +
displayStr +
' </div>' +
' </div>' +
' <div class="blocklyDropDownArrow arrowBottom" style="transform: translate(22px, 15px) rotate(45deg);"></div>' +
'</div>';
$('.blocklyDropDownDiv').remove();
$('body').append(dropDownDiv);
return origValue;
};
this.stop = function () {
for (var i = 0; i < this._timeouts.length; i += 1) {
window.clearTimeout(this._timeouts[i]);
}
this._timeouts = [];
this.removeEditorMarker();
if(Sk.runQueue) {
for (var i=0; i<Sk.runQueue.length; i++) {
if(Sk.runQueue[i].ctrl === this) {
Sk.runQueue.splice(i, 1);
i--;
}
}
}
if(window.quickAlgoInterface) {
window.quickAlgoInterface.setPlayPause(false);
}
this._resetInterpreterState();
};
this.isRunning = function () {
return this._isRunning;
};
this._resetInterpreterState = function () {
this._steps = 0;
this._stepsWithoutAction = 0;
this._lastNbActions = 0;
this._nbActions = 0;
this._allowStepsWithoutDelay = 0;
this._isRunning = false;
this._resetDone = false;
this.stepMode = false;
this._stepInProgress = false;
this._resetCallstackOnNextStep = false;
this._paused = false;
this.waitingOnReadyNode = false;
Sk.running = false;
if(Sk.runQueue && Sk.runQueue.length > 0) {
var nextExec = Sk.runQueue.shift();
setTimeout(function () { nextExec.ctrl.runCodes(nextExec.codes); }, 100);
}
};
this._resetCallstack = function () {
if (this._resetCallstackOnNextStep) {
this._resetCallstackOnNextStep = false;
this._debugger.suspension_stack.pop();
}
};
this.reset = function() {
if(this._resetDone) { return; }
if(this.isRunning()) {
this.stop();
}
this.context.reset();
this._resetDone = true;
};
this.step = function () {
this._resetCallstack();
this._stepInProgress = true;
var editor = this.context.blocklyHelper._aceEditor;
var markDelay = this.context.infos ? Math.floor(this.context.infos.actionDelay/4) : 0;
if(this.context.display && (this.stepMode || markDelay > 30)) {
var curSusp = this._debugger.suspension_stack[this._debugger.suspension_stack.length-1];
if(curSusp && curSusp.lineno) {
this.removeEditorMarker();
var splitCode = this._code.split(/[\r\n]/);
var Range = ace.require('ace/range').Range;
this._editorMarker = editor.session.addMarker(
new Range(curSusp.lineno-1, curSusp.colno, curSusp.lineno, 0),
"aceHighlight",
"line");
}
} else {
this.removeEditorMarker();
}
var stepDelay = 0;
if(!this.stepMode && this.context.allowInfiniteLoop) {
// Add a delay in infinite loops to avoid using all CPU
if(this._allowStepsWithoutDelay > 0) {
// We just had a waitDelay, don't delay further
this._allowStepsWithoutDelay -= 1;
} else {
stepDelay = 10;
}
}
var realStepDelay = markDelay + stepDelay;
if(realStepDelay > 0) {
this._paused = true;
setTimeout(this.realStep.bind(this), realStepDelay);
} else {
this.realStep();
}
};
this.realStep = function () {
// For reportValue in Skulpt
window.currentPythonRunner = this;
this._paused = this.stepMode;
this._debugger.enable_step_mode();
this._debugger.resume.call(this._debugger);
this._steps += 1;
if(this._lastNbActions != this._nbActions) {
this._lastNbActions = this._nbActions;
this._stepsWithoutAction = 0;
} else {
this._stepsWithoutAction += 1;
}
};
this._onStepSuccess = function () {
// If there are still timeouts, there's still a step in progress
this._stepInProgress = !!this._timeouts.length;
this._continue();
};
this._onStepError = function (message) {
context.onExecutionEnd && context.onExecutionEnd();
// We always get there, even on a success
this.stop();
message = '' + message;
// Skulpt doesn't support well NoneTypes
if(message.indexOf("TypeError: Cannot read property") > -1 && message.indexOf("undefined") > -1) {
message = message.replace(/^.* line/, "TypeError: NoneType value used in operation on line");
}
if(message.indexOf('undefined') > -1) {
message += '. ' + window.languageStrings.undefinedMsg;
}
// Transform message depending on whether we successfully
if(this.context.success) {
message = "<span style='color:green;font-weight:bold'>" + message + "</span>";
} else {
message = this.context.messagePrefixFailure + message;
}
this.messageCallback(message);
};
this._setBreakpoint = function (bp, isTemporary) {
this._debugger.add_breakpoint(this._editor_filename + ".py", bp, "0", isTemporary);
};
this._asyncCallback = function (editor_filename, code) {
return function() {
return Sk.importMainWithBody(editor_filename, true, code, true);
};
};
this.signalAction = function () {
// Allows a context to signal an "action" happened
this._stepsWithoutAction = 0;
};
}
function initBlocklyRunner(context, msgCallback) {
return new PythonInterpreter(context, msgCallback);
};