156 lines
5.5 KiB
JavaScript
156 lines
5.5 KiB
JavaScript
let inFlightRequest = null;
|
|
const inputElement = document.querySelector("#org-input");
|
|
const outputElement = document.querySelector("#parse-output");
|
|
const astTreeElement = document.querySelector("#ast-tree");
|
|
|
|
function abortableFetch(request, options) {
|
|
const controller = new AbortController();
|
|
const signal = controller.signal;
|
|
|
|
return {
|
|
abort: () => controller.abort(),
|
|
ready: fetch(request, { ...options, signal })
|
|
};
|
|
}
|
|
|
|
function clearOutput() {
|
|
clearActiveAstNode();
|
|
outputElement.innerHTML = "";
|
|
astTreeElement.innerHTML = "";
|
|
}
|
|
|
|
function renderParseResponse(response) {
|
|
clearOutput();
|
|
console.log(response);
|
|
renderSourceBox(response);
|
|
renderAstTree(response);
|
|
}
|
|
|
|
function renderSourceBox(response) {
|
|
const lines = response.input.split(/\r?\n/);
|
|
const numLines = lines.length;
|
|
const numDigits = Math.log10(numLines) + 1;
|
|
|
|
outputElement.style.paddingLeft = `calc(${numDigits + 1}ch + 10px)`;
|
|
|
|
for (let line of lines) {
|
|
let wrappedLine = document.createElement("code");
|
|
if (line !== "" && line !== null) {
|
|
for (let chr of line) {
|
|
// Please forgive me
|
|
let wrappedCharacter = document.createElement("span");
|
|
wrappedCharacter.textContent = chr;
|
|
wrappedLine.appendChild(wrappedCharacter);
|
|
}
|
|
} else {
|
|
let wrappedCharacter = document.createElement("span");
|
|
wrappedCharacter.textContent = "\n";
|
|
wrappedLine.appendChild(wrappedCharacter);
|
|
}
|
|
outputElement.appendChild(wrappedLine);
|
|
}
|
|
}
|
|
|
|
function renderAstTree(response) {
|
|
renderAstNode(response.input, 0, response.tree);
|
|
}
|
|
|
|
function renderAstNode(originalSource, depth, astNode) {
|
|
const nodeElem = document.createElement("div");
|
|
nodeElem.classList.add("ast_node");
|
|
|
|
let sourceForNode = originalSource.slice(astNode.position.start_character - 1, astNode.position.end_character - 1);
|
|
// Since sourceForList is a string, JSON.stringify will escape with backslashes and wrap the text in quotation marks, ensuring that the string ends up on a single line. Coincidentally, this is the behavior we want.
|
|
let escapedSource = JSON.stringify(sourceForNode);
|
|
|
|
nodeElem.innerText = `${astNode.name}: ${escapedSource}`;
|
|
nodeElem.style.marginLeft = `${depth * 20}px`;
|
|
nodeElem.dataset.startLine = astNode.position.start_line;
|
|
nodeElem.dataset.endLine = astNode.position.end_line;
|
|
nodeElem.dataset.startCharacter = astNode.position.start_character;
|
|
nodeElem.dataset.endCharacter = astNode.position.end_character;
|
|
|
|
nodeElem.addEventListener("click", () => {
|
|
setActiveAstNode(nodeElem, originalSource);
|
|
});
|
|
|
|
astTreeElement.appendChild(nodeElem);
|
|
for (let child of astNode.children) {
|
|
renderAstNode(originalSource, depth + 1, child);
|
|
}
|
|
}
|
|
|
|
function clearActiveAstNode() {
|
|
for (let elem of document.querySelectorAll("#ast-tree .ast_node.highlighted")) {
|
|
elem.classList.remove("highlighted");
|
|
}
|
|
for (let elem of document.querySelectorAll("#parse-output > code.highlighted")) {
|
|
elem.classList.remove("highlighted");
|
|
}
|
|
for (let elem of document.querySelectorAll("#parse-output > code > span")) {
|
|
elem.classList.remove("highlighted");
|
|
}
|
|
}
|
|
|
|
function setActiveAstNode(elem, originalSource) {
|
|
clearActiveAstNode();
|
|
elem.classList.add("highlighted");
|
|
let startLine = parseInt(elem.dataset.startLine, 10);
|
|
let endLine = parseInt(elem.dataset.endLine, 10);
|
|
let startCharacter = parseInt(elem.dataset.startCharacter, 10);
|
|
let endCharacter = parseInt(elem.dataset.endCharacter, 10);
|
|
for (let line = startLine; line < endLine; ++line) {
|
|
highlightLine("parse-output", line - 1);
|
|
}
|
|
highlightCharacters("parse-output", originalSource, startCharacter, endCharacter);
|
|
}
|
|
|
|
inputElement.addEventListener("input", async () => {
|
|
let orgSource = inputElement.value;
|
|
if (inFlightRequest != null) {
|
|
inFlightRequest.abort();
|
|
inFlightRequest = null;
|
|
}
|
|
clearOutput();
|
|
|
|
let newRequest = abortableFetch("/parse", {
|
|
method: "POST",
|
|
cache: "no-cache",
|
|
body: orgSource,
|
|
});
|
|
inFlightRequest = newRequest;
|
|
|
|
let response = null;
|
|
try {
|
|
response = await inFlightRequest.ready;
|
|
}
|
|
catch (err) {
|
|
if (err.name === "AbortError") return;
|
|
}
|
|
renderParseResponse(await response.json());
|
|
});
|
|
|
|
function highlightLine(htmlName, lineOffset) {
|
|
const childOffset = lineOffset + 1;
|
|
const codeLineElement = document.querySelector(`#${htmlName} > code:nth-child(${childOffset})`);
|
|
codeLineElement?.classList.add("highlighted")
|
|
}
|
|
|
|
function highlightCharacters(htmlName, originalSource, startCharacter, endCharacter) {
|
|
let sourceBefore = originalSource.slice(0, startCharacter - 1);
|
|
let precedingLineBreak = sourceBefore.lastIndexOf("\n");
|
|
let characterIndexOnLine = precedingLineBreak !== -1 ? startCharacter - precedingLineBreak - 1 : startCharacter;
|
|
let lineNumber = (sourceBefore.match(/\r?\n/g) || '').length + 1;
|
|
|
|
for (let characterIndex = startCharacter; characterIndex < endCharacter; ++characterIndex) {
|
|
document.querySelector(`#${htmlName} > code:nth-child(${lineNumber}) > span:nth-child(${characterIndexOnLine})`)?.classList.add("highlighted");
|
|
if (originalSource[characterIndex - 1] == "\n") {
|
|
++lineNumber;
|
|
characterIndexOnLine = 1;
|
|
} else {
|
|
++characterIndexOnLine;
|
|
}
|
|
}
|
|
|
|
}
|