src/editor/sandbox/handleLoop.js (55 lines of code) (raw):

/** * 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; }