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() { 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"); wrappedLine.textContent = line ? line : "\n"; 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`; astTreeElement.appendChild(nodeElem); for (let child of astNode.children) { renderAstNode(originalSource, depth + 1, child); } } 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 unhighlightLine(htmlName, lineOffset) { const childOffset = lineOffset + 1; const codeLineElement = document.querySelector(`.${htmlName} > code:nth-child(${childOffset})`); codeLineElement?.classList.remove("highlighted") }