blob: cd615758017b3583af8245f3919fa55f08317a1c [file] [log] [blame]
/**
* This function is used to prevent the page from getting stuck
* for the infinite loop in the user code.
*
* @param {string} code the source code
*/
function handleLoop(code) {
let AST;
try {
AST = acorn.parse(code, {
ecmaVersion: 'latest',
sourceType: 'script'
});
} catch (e) {
console.error('failed to parse code', e);
return code;
}
/**
* Temporarily store the range of positions where the code needs to be inserted
*/
const fragments = [];
/**
* loopID is used to mark the loop
*/
let loopID = 1;
/**
* Mark the code that needs to be inserted when looping
*/
const insertCode = {
setMonitor: 'LoopController.loopMonitor(%d);',
delMonitor: ';LoopController.delLoop(%d);'
};
// Traverse the AST to find the loop position
estraverse.traverse(AST, {
enter(node) {
switch (node.type) {
case 'WhileStatement':
case 'DoWhileStatement':
case 'ForStatement':
case 'ForInStatement':
case 'ForOfStatement':
// Gets the head and tail of the loop body
let { start, end } = node.body;
start++;
let pre = insertCode.setMonitor.replace('%d', loopID);
let aft = '';
// If the body of the loop is not enveloped by {} and is indented, we need to manually add {}
if (node.body.type !== 'BlockStatement') {
pre = '{' + pre;
aft = '}';
--start;
}
fragments.push({ pos: start, str: pre });
fragments.push({ pos: end, str: aft });
fragments.push({
pos: node.end,
str: insertCode.delMonitor.replace('%d', loopID)
});
++loopID;
break;
default:
break;
}
}
});
// Insert code to corresponding position
fragments
.sort((a, b) => b.pos - a.pos)
.forEach((fragment) => {
code =
code.slice(0, fragment.pos) + fragment.str + code.slice(fragment.pos);
});
return code;
}