summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/codeq.js32
-rw-r--r--js/def_parser.js22
-rw-r--r--js/prolog.js187
3 files changed, 221 insertions, 20 deletions
diff --git a/js/codeq.js b/js/codeq.js
index 770153e..e867ffd 100644
--- a/js/codeq.js
+++ b/js/codeq.js
@@ -1,6 +1,17 @@
// introduce the namespace object for codeq
window.codeq = {};
window.siteDefinition = { logLevel: 'debug' }; // for debug purposes
+window.phandler = null; // TODO: this is for debug only
+
+// type definitions, so the IDE and coders have easier work
+/**
+ * @typedef {Object} ServerHint a hint sent by the server
+ * @property {string} id the hint ID
+ * @property {Object} args optional arguments for the hint in case of a pop-up or static hint
+ * @property {string[]} choices array of choices in case of a drop-down hint
+ * @property {number} start the starting position of the highlighted code, in case of a pop-up or drop-down hint
+ * @property {number} end the ending position of the highlighted code, in case of a pop-up or drop-down hint
+ */
(function () {
@@ -700,8 +711,6 @@ window.siteDefinition = { logLevel: 'debug' }; // for debug purposes
// if (s.length == 0) return; // empty hash
// if (s.charAt(0) == '#') s = s.substring(1);
// if (s.length == 0) return; // empty hash
- var editor = CodeMirror(document.getElementById('code_editor'), { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true });
- editor.setValue('sister(X, Y) :-\n female(X),\n parent(Z, X),\n parent(Z, Y),\n X \\== Y.');
/* $('#console').terminal(function (command, term) {
term.echo('Not implemented.');
@@ -715,27 +724,16 @@ window.siteDefinition = { logLevel: 'debug' }; // for debug purposes
width: '100%'
});*/
- var controller = $('#console').console({
- promptLabel: '?- ',
- commandValidate: function (line) {
- return !!line;
- },
- commandHandle: function (line) {
- return [{msg:'Not implemented.', className:'console-response'}];
- },
- autofocus: false,
- animateScroll: false,
- promptHistory: false,
- welcomeMessage: 'Prolog REPL.'
- });
-
codeq.system.load({
type: 'text',
url: 'sister.py',
callback: function (data, status, url) {
if (!data) return;
var info = codeq.parseDefinition(data);
- $('#description').html(info.description);
+ window.phandler = codeq.createPrologHandler(info); // TODO: for debug only
+ // DEBUG: phandler.processServerHints([{id:'x_must_be_female'}]);
+ // DEBUG: phandler.processServerHints([{id:'popup_unknown', start: 20, end: 26}]);
+ // DEBUG: phandler.processServerHints([{id:'drop_down', start: 20, end: 26, choices:['ena', 'dva', 'tri']}]);
}
});
};
diff --git a/js/def_parser.js b/js/def_parser.js
index 91ca35d..fd34e80 100644
--- a/js/def_parser.js
+++ b/js/def_parser.js
@@ -356,6 +356,23 @@
else throw new Error("Unexpected token: expected a string, number, object, or array, at line " + token.line + ", column " + token.column);
};
+
+ /**
+ * @typedef {string|{type: string, message: string}} HintDefinition
+ */
+ /**
+ * @typedef {Object} PrologTaskDef a Prolog assignment definition
+ * @property {string} description the assignment description
+ * @property {Object.<string, HintDefinition>} hint the assignment hint definitions, keyed by hint ID
+ */
+
+ /**
+ * Converts the given pythonic assignment definition into a JavaScriptish definition,
+ * executes it, and takes the variables "description" and "hint" from the definition.
+ *
+ * @param {string} definition The assignment definition.
+ * @returns {PrologTaskDef}
+ */
codeq.parseDefinition = function (definition) {
var next = tokenize(definition),
vars = { 'description': true, 'hint': true },
@@ -408,12 +425,11 @@
}
parts[0] = 'var ' + v.join(', ') + ';\n';
- parts.push(';\n__params__.description = description;\n__params__.hint = hint;');
+ parts.push(';\nreturn {"description":description,"hint":hint};');
v = parts.join('');
codeq.log.debug("Creating a new parseInfo function having the body:\n" + v);
fn = new Function("__params__", v);
- obj = {};
- fn(obj);
+ obj = fn();
return obj; // obj now contains "description" and "hint"
}; // parseDefinition
diff --git a/js/prolog.js b/js/prolog.js
new file mode 100644
index 0000000..b697fca
--- /dev/null
+++ b/js/prolog.js
@@ -0,0 +1,187 @@
+/**
+ * Handler for a Prolog assignment. Works with the corresponding prolog.html content.
+ */
+
+(function () {
+ /**
+ * Creates a new handler for the given Prolog assignment definition.
+ *
+ * @param {PrologTaskDef} info
+ * @returns {{destroy: Function, processServerHints: Function}}
+ */
+ codeq.createPrologHandler = function (info) {
+ var jqDescription = $('#description'),
+ jqEditor = $('#code_editor'),
+ jqConsole = $('#console'),
+ jqHints = $('#info'),
+ editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true }),
+ controller = jqConsole.console({
+ promptLabel: '?- ',
+ commandValidate: function (line) {
+ return !!line;
+ },
+ commandHandle: function (line) {
+ return [{msg:'Not implemented.', className:'console-response'}];
+ },
+ autofocus: false,
+ animateScroll: false,
+ promptHistory: false,
+ welcomeMessage: 'Prolog REPL.'
+ }),
+ /** Object.<string, HintDefinition> */ hintDefs = info.hint,
+ hintCounter = 0, // for generating unique class-names
+ hintCleaners = [],
+ clearHints = function () {
+ var i;
+ for (i = hintCleaners.length - 1; i >= 0; i--) {
+ hintCleaners[i]();
+ }
+ hintCleaners.length = 0;
+ hintCounter = 0;
+ },
+ addMark = function (start, end) {
+ var posStart = editor.posFromIndex(start),
+ posEnd = editor.posFromIndex(end),
+ doc = editor.getDoc(),
+ mark = doc.markText(posStart, posEnd, {className: 'editor-mark _emark_' + hintCounter}),
+ result = {start: posStart, end: posEnd, mark: mark, className: '_emark_' + hintCounter};
+ hintCleaners.push(function () { mark.clear(); mark = null; doc = null; result.mark = null; });
+ hintCounter++;
+ return result;
+ },
+ hintHandlers = {
+ 'static': function (type, template, serverHint) {
+ codeq.log.debug('Processing static hint');
+ jqHints.append(template); // TODO: incorporate any serverHint.args
+ // no hint cleaner here, a static hint remains on the screen
+ },
+ 'popup': function (type, template, serverHint) {
+ codeq.log.debug('Processing popup hint');
+ var mark = addMark(serverHint.start, serverHint.end), // add the mark
+ jqMark = jqEditor.find('.' + mark.className),
+ jqPopup = null,
+ onBlur = function () {
+ codeq.log.debug('Removing popup');
+ if (jqPopup) {
+ jqPopup.off('blur', onBlur);
+ jqPopup.remove();
+ jqPopup = null;
+ }
+ };
+
+ window.jqMark = jqMark; // TODO: DEBUG
+
+ jqMark.on('click', function () {
+ if (jqPopup) return;
+ codeq.log.debug('Showing popup');
+ var pos = mark.mark.find(), // results in {from: {line: number, ch: number}, to: {line: number, ch: number}}
+ left = pos.from.ch < pos.to.ch ? pos.from.ch : pos.to.ch,
+ down = pos.from.line < pos.to.line ? pos.to.line : pos.from.line;
+ jqPopup = $('<div style="position: absolute;" class="editor-popup ' + mark.className + '"></div>').html(template);
+ editor.addWidget({line: down, ch: left}, jqPopup[0], true);
+ jqPopup = jqEditor.find('.editor-popup.' + mark.className);
+ setTimeout(function () {jqPopup.trigger('focus'); jqPopup.on('click', onBlur);}, 50); // event handlers can be registered only when the DOM elements is part of the document
+ window.jqPopup = jqPopup; // TODO: DEBUG
+ });
+
+ hintCleaners.push(function () {
+ if (jqPopup) {
+ jqPopup.off('blur', onBlur);
+ jqPopup.remove();
+ jqPopup = null;
+ }
+ if (jqMark) {
+ jqMark = null;
+ }
+ mark = null;
+ });
+ },
+ 'dropdown': function (type, template, serverHint) {
+ codeq.log.debug('Processing dropdown hint');
+ var completion = null, // the completion object, created in showHint()
+ close = function () {
+ if (completion) {
+ completion.close();
+ completion = null;
+ }
+ };
+
+ if ((editor.listSelections().length > 1) || editor.somethingSelected()) {
+ // showHint() doesn't work if a selection is active
+ }
+
+ editor.showHint({
+ hint: function () {
+ var hints = {
+ list: serverHint.choices,
+ from: editor.posFromIndex(serverHint.start),
+ to: editor.posFromIndex(serverHint.end)
+ };
+ completion = editor.state.completionActive;
+ return hints;
+ },
+ completeOnSingleClick: true,
+ completeSingle: false
+ });
+
+ hintCleaners.push(close);
+ }
+ };
+
+ editor.setValue('sister(X, Y) :-\n female(X),\n parent(Z, X),\n parent(Z, Y),\n X \\== Y.'); // demo
+ jqDescription.html(info.description);
+
+ return {
+ destroy: function () {
+ 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
+ jqHints.empty(); // TODO: just make static references to these
+ jqDescription = null;
+ jqEditor = null;
+ jqConsole = null;
+ jqHints = null;
+ },
+
+ /**
+ * Processes and display appropriately the server hints.
+ * TODO: sort hints so static and popup hints come first, and a (single) drop-down hint last
+ *
+ * @param {ServerHint[]} serverHints an array of hints from the server
+ */
+ processServerHints: function (serverHints) {
+ var n = serverHints.length,
+ /** number */ i,
+ /** ServerHint */ serverHint,
+ /** HintDefinition */ hintDef,
+ hintType, hintTemplate, t, fn;
+ clearHints();
+ for (i = 0; i < n; i++) {
+ serverHint = serverHints[i];
+ hintDef = hintDefs[serverHint.id];
+ if (!hintDef) {
+ codeq.log.error('Undefined hint: ' + serverHint.id);
+ continue;
+ }
+ t = typeof hintDef;
+ if (t === 'object') {
+ hintType = hintDef.type;
+ hintTemplate = hintDef.message;
+ }
+ else if (t === 'string') {
+ hintType = 'static';
+ hintTemplate = hintDef;
+ }
+ else {
+ codeq.log.error('Unsupported hint definition: ' + t);
+ continue;
+ }
+
+ fn = hintHandlers[hintType];
+ if (!fn) codeq.log.error('Unsupported hint type: ' + hintType);
+ else fn(hintType, hintTemplate, serverHint);
+ }
+ }
+ };
+ };
+})(); \ No newline at end of file