/** * Handler for a Prolog assignment. Works with the corresponding prolog.html content. */ (function () { // a constant var firstCharacterPos = {'line': 0, 'ch': 0}; //codeq.makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var promptMode = true, // default: query composition; alternative: query result browsing manualStop = false, // if the user stopped showing next answers (false) or if there are no more answers (true) terminal = codeq.makeConsole(jqConsole, { 'greeting': 'CodeQ Prolog terminal proxy' }), tcs = function terminalCommandSuccess (data) { var t, lines, i; if (data.code === 0) { t = data.terminal; terminal.append(t.messages.join('\n'), 'output'); promptMode = !t.have_more; } else { terminal.append(data.message, 'error'); promptMode = true; } if (promptMode) { terminal.setLineBuffered(); terminal.append(manualStop ? '?- ' : '.\n?- ', 'output'); } }, tcf = function terminalCommandFailed (error) { promptMode = true; terminal.setLineBuffered(); terminal.append(error + '\n', 'error'); terminal.append('?- ', 'output'); }; terminal.onKeypress = function (c) { if (promptMode) return c; // query composition: return the character unchanged switch (c) { case ' ': case ';': case '\n': return ';'; // show next answer on space, semicolon or enter default: return '.'; // everything else: stop searching for answers } }; terminal.onInput = function (command) { if (promptMode) { promptMode = false; manualStop = false; terminal.setNotBuffered(); return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'run', 'program': editor.getDoc().getValue(), 'query': command, 'trace': activityHandler.addAndPurge({'typ': 'slv', 'qry': command}) }, problem_id).then(tcs, tcf); } else { terminal.append('\n', 'input'); if (command == ';') { // show next answer return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'next', 'trace': activityHandler.addAndPurge({'typ': 'nxt'}) }, problem_id).then(tcs, tcf); } else { // stop searching for answers manualStop = true; return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'end', 'trace': activityHandler.addAndPurge({'typ': 'stp'}) }, problem_id).then(tcs, tcf); } } }; terminal.leftmostCol = 3; terminal.append('?- ', 'output'); return terminal; }; var makeActivityHandler = function (editor, problem_id) { var lastActivityMillis = Date.now(), deltaActivityMillis = function deltaActivityMillisFunc () { var now = Date.now(), dt = now - lastActivityMillis; lastActivityMillis = now; return dt; }, queue = [], ts = null, timer = function () { var promise; ts = null; if (queue.length === 0) return Q(true); promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id); queue.length = 0; return promise; }, flush = function () { clearTimeout(ts); return timer(); }; return { 'queueTrace': function (trace) { trace['dt'] = deltaActivityMillis(); queue.push(trace); if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds return this; }, 'flush': flush, 'addAndPurge': function (trace) { var accumulatedTrace = queue; queue = []; trace['dt'] = deltaActivityMillis(); accumulatedTrace.push(trace); if (ts !== null) { clearTimeout(ts); ts = null; } return accumulatedTrace; } }; }; /** * Creates a new handler for the given Prolog assignment definition. * * @param {PrologTaskDef} info * @returns {{destroy: Function, processServerHints: Function}} */ codeq.createPrologHandler = function (info) { var problem = info.problem, jqDescription = $('#description'), jqEditor = $('#code_editor'), jqConsole = $('#console'), jqHints = $('#info'), editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true }), activityHandler = codeq.makeActivityHandler(editor, problem.id), terminal = makePrologTerminalHandler(jqConsole, editor, problem.id, activityHandler), hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint); editor.setValue(info.solution); $('#title').text(problem.slug); jqDescription.html(problem.description); editor.on('change', function (instance, changeObj) { var doc = editor.getDoc(), pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)); if (changeObj.removed) { activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'len': codeq.codePointCount(changeObj.removed)}); } if (changeObj.text) { activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text}); } }); $('#btn_code_hint').on('click', function () { terminal.append('hint.\n', 'input'); terminal.inputDisable(); var doc = editor.getDoc(); codeq.comms.sendHint({ 'language': 'prolog', 'program': editor.getDoc().getValue(), 'problem_id': problem.id }) .then( function hintSuccess(data) { if (data.code === 0) hinter.handle(data.hints); else terminal.append(data.message + '\n', 'error'); }, function hintFailed (error) { terminal.append(error + '\n', 'error'); } ) .fin(function () { terminal.inputEnable(); terminal.append('?- ', 'output'); }) .done(); }); $('#btn_code_test').on('click', function () { terminal.append('test.\n', 'input'); terminal.inputDisable(); var doc = editor.getDoc(); codeq.comms.sendTest({ 'language': 'prolog', 'program': editor.getDoc().getValue(), 'problem_id': problem.id }) .then( function testSuccess(data) { if (data.code === 0) hinter.handle(data.hints); else terminal.append(data.message + '\n', 'error'); }, function testFailed (error) { terminal.append(error + '\n', 'error'); } ) .fin(function () { terminal.inputEnable(); terminal.append('?- ', 'output'); }) .done(); }); return { destroy: function () { hinter.destroy(); terminal.destroy(); jqDescription.empty(); jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it jqConsole.empty(); // TODO: the same with the console jqDescription = null; jqEditor = null; jqConsole = null; jqHints = null; } }; }; })();