`;
+
+ // Attempt to parse the error line from the diagnostic message (formatted as "line:col: message")
+ const match = result.out.match(/^(\d+):(\d+):/);
+ if (match) {
+ const errorLine = parseInt(match[1], 10);
+ syncHighlight(errorLine);
+ } else {
+ syncHighlight(null);
+ }
}
- }
}
// Shows engine-generated explanation text (a plan or a proof tree) in the
// text view. Clears stale table results and hides the view toggle.
function showExplanation(result, okStatus) {
- outputEl.textContent = result.out || "(no output)";
- outputEl.classList.toggle("error", result.status !== 0);
- outputTableEl.innerHTML = ""; // Clear stale table data
- if (viewToggleEl) viewToggleEl.classList.add("hidden"); // Hide the view toggle
- telemetryInfoEl.textContent = ""; // Clear stale telemetry info
- setView("text");
- if (result.status === 0) {
- setStatus(okStatus, "ok");
- } else {
- setStatus("error", "error");
- }
+ outputEl.textContent = result.out || "(no output)";
+ outputEl.classList.toggle("error", result.status !== 0);
+ outputTableEl.innerHTML = ""; // Clear stale table data
+ if (viewToggleEl) viewToggleEl.classList.add("hidden"); // Hide the view toggle
+ telemetryInfoEl.textContent = ""; // Clear stale telemetry info
+ setView("text");
+ if (result.status === 0) {
+ setStatus(okStatus, "ok");
+ } else {
+ setStatus("error", "error");
+ }
}
function showPlan() {
- if (!wasm) return;
- try {
- showExplanation(wasmCall("explainPlan", [sourceEl.value]), "plan");
- } catch (err) {
- outputEl.textContent = `internal error: ${err}`;
- outputEl.classList.add("error");
- setStatus("trap", "error");
- }
+ if (!wasm) return;
+ try {
+ showExplanation(wasmCall("explainPlan", [sourceEl.value]), "plan");
+ } catch (err) {
+ outputEl.textContent = `internal error: ${err}`;
+ outputEl.classList.add("error");
+ setStatus("trap", "error");
+ }
}
function explainAtom(atom) {
- if (!wasm) return;
- try {
- showExplanation(wasmCall("explain", [sourceEl.value, atom]), "explained");
- } catch (err) {
- outputEl.textContent = `internal error: ${err}`;
- outputEl.classList.add("error");
- setStatus("trap", "error");
- }
+ if (!wasm) return;
+ try {
+ showExplanation(wasmCall("explain", [sourceEl.value, atom]), "explained");
+ } catch (err) {
+ outputEl.textContent = `internal error: ${err}`;
+ outputEl.classList.add("error");
+ setStatus("trap", "error");
+ }
}
function share() {
- const url = new URL(window.location.href);
- url.hash = "program=" + encodeProgram(sourceEl.value);
- history.replaceState(null, "", url);
- copyToClipboard(url.href)
- .then(() => setStatus("link copied", "ok"))
- .catch(() => setStatus("link in address bar", "ok"));
+ const url = new URL(window.location.href);
+ url.hash = "program=" + encodeProgram(sourceEl.value);
+ history.replaceState(null, "", url);
+ copyToClipboard(url.href)
+ .then(() => setStatus("link copied", "ok"))
+ .catch(() => setStatus("link in address bar", "ok"));
}
function applyTheme(theme) {
- document.documentElement.dataset.theme = theme;
- themeEl.textContent = theme === "light" ? "☾" : "☀";
- themeEl.title = theme === "light" ? "Switch to the dark theme" : "Switch to the light theme";
+ document.documentElement.dataset.theme = theme;
+ themeEl.textContent = theme === "light" ? "☾" : "☀";
+ themeEl.title = theme === "light" ? "Switch to the dark theme" : "Switch to the light theme";
}
// --- Output View Toggling & Parsing -------------------------------------------
@@ -704,587 +707,603 @@ function applyTheme(theme) {
let currentView = "text"; // "text" or "table"
function setView(view) {
- currentView = view;
- if (view === "table") {
- viewTableEl.classList.add("active");
- viewTextEl.classList.remove("active");
- outputEl.classList.add("hidden");
- outputTableEl.classList.remove("hidden");
- } else {
- viewTextEl.classList.add("active");
- viewTableEl.classList.remove("active");
- outputTableEl.classList.add("hidden");
- outputEl.classList.remove("hidden");
- }
+ currentView = view;
+ if (view === "table") {
+ viewTableEl.classList.add("active");
+ viewTextEl.classList.remove("active");
+ outputEl.classList.add("hidden");
+ outputTableEl.classList.remove("hidden");
+ } else {
+ viewTextEl.classList.add("active");
+ viewTableEl.classList.remove("active");
+ outputTableEl.classList.add("hidden");
+ outputEl.classList.remove("hidden");
+ }
}
function parseOutputToTables(text) {
- if (!text || text.trim() === "" || text.trim() === "(no results)" || text.trim() === "(no output)") {
- return `
No results returned.
`;
- }
-
- const lines = text.split("\n");
- const parts = [];
- let currentTable = null;
- let currentText = [];
-
- function flushText() {
- if (currentText.length > 0) {
- // Remove trailing empty line if it is just a spacing artifact
- if (currentText[currentText.length - 1] === "") {
- currentText.pop();
- }
- if (currentText.length > 0) {
- parts.push({
- type: "text",
- content: currentText.join("\n")
- });
- }
- currentText = [];
- }
- }
-
- function flushTable() {
- if (currentTable) {
- parts.push({
- type: "table",
- title: currentTable.title,
- rows: currentTable.rows,
- truncated: currentTable.truncated
- });
- currentTable = null;
- }
- }
-
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- const trimmed = line.trim();
- if (!trimmed) {
- if (currentTable) {
- continue;
- }
- if (currentText.length > 0) {
- currentText.push(line);
- }
- continue;
+ if (!text || text.trim() === "" || text.trim() === "(no results)" || text.trim() === "(no output)") {
+ return `
No results returned.
`;
}
- // Check if the line is a block header: e.g. "pred_name:" or "?- q:"
- if (trimmed.endsWith(":") && !trimmed.startsWith("(")) {
- flushText();
- flushTable();
- const title = trimmed.slice(0, -1).trim();
- currentTable = {
- title: title,
- rows: [],
- truncated: null
- };
- continue;
+ const lines = text.split("\n");
+ const parts = [];
+ let currentTable = null;
+ let currentText = [];
+
+ function flushText() {
+ if (currentText.length > 0) {
+ // Remove trailing empty line if it is just a spacing artifact
+ if (currentText[currentText.length - 1] === "") {
+ currentText.pop();
+ }
+ if (currentText.length > 0) {
+ parts.push({
+ type: "text",
+ content: currentText.join("\n")
+ });
+ }
+ currentText = [];
+ }
}
- // Check if the line is a tuple: e.g. "(1, 2)" or " (1, 2)"
- if (trimmed.startsWith("(") && trimmed.endsWith(")") && trimmed !== "(no results)" && trimmed !== "(no output)") {
- flushText();
- const content = trimmed.slice(1, -1).trim();
- const rowData = parseTuple(content);
- if (currentTable) {
- currentTable.rows.push(rowData);
- } else {
- currentTable = { title: "Results", rows: [rowData], truncated: null };
- }
- continue;
+ function flushTable() {
+ if (currentTable) {
+ parts.push({
+ type: "table",
+ title: currentTable.title,
+ rows: currentTable.rows,
+ truncated: currentTable.truncated
+ });
+ currentTable = null;
+ }
}
- // Check if it is a truncation warning
- if (trimmed.startsWith("... (output truncated")) {
- if (currentTable) {
- currentTable.truncated = trimmed;
- } else {
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const trimmed = line.trim();
+ if (!trimmed) {
+ if (currentTable) {
+ continue;
+ }
+ if (currentText.length > 0) {
+ currentText.push(line);
+ }
+ continue;
+ }
+
+ // Check if the line is a block header: e.g. "pred_name:" or "?- q:"
+ if (trimmed.endsWith(":") && !trimmed.startsWith("(")) {
+ flushText();
+ flushTable();
+ const title = trimmed.slice(0, -1).trim();
+ currentTable = {
+ title: title,
+ rows: [],
+ truncated: null
+ };
+ continue;
+ }
+
+ // Check if the line is a tuple: e.g. "(1, 2)" or " (1, 2)"
+ if (trimmed.startsWith("(") && trimmed.endsWith(")") && trimmed !== "(no results)" && trimmed !== "(no output)") {
+ flushText();
+ const content = trimmed.slice(1, -1).trim();
+ const rowData = parseTuple(content);
+ if (currentTable) {
+ currentTable.rows.push(rowData);
+ } else {
+ currentTable = {title: "Results", rows: [rowData], truncated: null};
+ }
+ continue;
+ }
+
+ // Check if it is a truncation warning
+ if (trimmed.startsWith("... (output truncated")) {
+ if (currentTable) {
+ currentTable.truncated = trimmed;
+ } else {
+ currentText.push(line);
+ }
+ continue;
+ }
+
+ // Otherwise, it is non-tabular text
+ flushTable();
currentText.push(line);
- }
- continue;
}
- // Otherwise, it is non-tabular text
+ flushText();
flushTable();
- currentText.push(line);
- }
-
- flushText();
- flushTable();
-
- if (parts.length === 0) {
- return `
No results.
`;
- }
-
- let html = "";
- for (const part of parts) {
- if (part.type === "text") {
- html += `
${escapeHtml(part.content)}
`;
- } else if (part.type === "table") {
- html += `
`;
- html += `
`;
- html += `
${escapeHtml(part.title)}
`;
- if (part.rows.length > 0) {
- html += ``;
- }
- html += `
`;
- if (part.rows.length === 0) {
- html += `
No rows.
`;
- } else {
- html += `
`;
- const arity = part.rows[0].length;
- html += `
`;
- html += `
#
`;
- for (let c = 1; c <= arity; c++) {
- html += `
Col ${c}
`;
- }
- html += `
`;
-
- // Rows of a named predicate carry their atom text so a click can
- // ask the engine to explain the tuple's derivation.
- const pred = part.title.replace(/^\?-\s*/, "");
- const explainable = /^[a-z][A-Za-z0-9_]*$/.test(pred);
- html += ``;
- for (let r = 0; r < part.rows.length; r++) {
- const row = part.rows[r];
- if (explainable) {
- const atom = `${pred}(${row.join(", ")})`;
- html += `
`;
- } else {
- html += `
`;
- }
- html += `
${r + 1}
`;
- for (const val of row) {
- html += `
${escapeHtml(cleanValue(val))}
`;
- }
- html += `
`;
+
+ if (parts.length === 0) {
+ return `
No results.
`;
+ }
+
+ let html = "";
+ for (const part of parts) {
+ if (part.type === "text") {
+ html += `
${escapeHtml(part.content)}
`;
+ } else if (part.type === "table") {
+ html += `
`;
+ html += `
`;
+ html += `
${escapeHtml(part.title)}
`;
+ if (part.rows.length > 0) {
+ html += ``;
+ }
+ html += `
`;
+ if (part.rows.length === 0) {
+ html += `
No rows.
`;
+ } else {
+ html += `
`;
+ const arity = part.rows[0].length;
+ html += `
`;
+ html += `
#
`;
+ for (let c = 1; c <= arity; c++) {
+ html += `
Col ${c}
`;
+ }
+ html += `
`;
+
+ // Rows of a named predicate carry their atom text so a click can
+ // ask the engine to explain the tuple's derivation.
+ const pred = part.title.replace(/^\?-\s*/, "");
+ const explainable = /^[a-z][A-Za-z0-9_]*$/.test(pred);
+ html += ``;
+ for (let r = 0; r < part.rows.length; r++) {
+ const row = part.rows[r];
+ if (explainable) {
+ const atom = `${pred}(${row.join(", ")})`;
+ html += `