import {Repl} from './repl.js'; let buffer = ''; const input = document.querySelector('#hidden-input'); const history = document.querySelector('#history'); const text = document.querySelector('#text'); const prefix = document.querySelector('#prefix'); const canvas = document.querySelector('#canvas'); const canvasSwitch = document.querySelector('#canvas-switch'); let showCanvas = false; const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const clearScreen = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx.clearRect(0, 0, canvas.width, canvas.height); }; const fillRect = (color, x, y, w, h) => { ctx.fillStyle = color; ctx.fillRect(x, y, w, h); }; canvasSwitch.addEventListener('click', () => { showCanvas = !showCanvas; if (showCanvas) { canvas.style.display = 'block'; canvasSwitch.textContent = 'REPL'; } else { canvas.style.display = 'none'; canvasSwitch.textContent = 'CANVAS'; } }) const repl = new Repl(loadFromFile, loadTest, async () => { const onwrite = repl.stdout.onwrite; prefix.innerHTML = '
'; const text = await read('
'); repl.stdout.onwrite = result => { history.innerHTML += `${result.replaceAll('\n', '
')}`; repl.stdout.onwrite = onwrite; }; prefix.textContent = ''; repl.stdin.pushData(text); }); function evalp(text) { if (!text.trim()) { return false; } return Array.from(text).reduce((op, c) => { if (c === '(') { return op + 1; } if (c === ')' && op > 0) { return op - 1; } return op; }, 0) === 0; } function read(prefix = '') { return new Promise(resolve => { const controller = new AbortController(); input.addEventListener( 'keypress', (e) => { switch(e.key) { case 'Enter': if (evalp(input.value) && (input.selectionStart == input.value.length || e.shiftKey)) { let text = input.value; history.innerHTML += `${prefix}${text}`; input.value = ''; updateText(); resolve(text); e.preventDefault(); controller.abort(); } break; } }, { signal: controller.signal }) }); } function loadFromFile() { return new Promise((resolve) => { const input = document.createElement('input'); input.setAttribute('type', 'file'); input.setAttribute('accept', '.ptlisp'); input.setAttribute('class', 'hidden'); document.body.appendChild(input); input.addEventListener('change', e => { const reader = new FileReader(); reader.addEventListener('load', e => { resolve(e.target.result) document.body.removeChild(input); }); reader.readAsText(e.target.files[0]); }); input.click(); }); } async function loadTest() { let response = await fetch("test.ptlisp"); if (response.status != 200) { throw new Error("Server Error"); } return await response.text(); } function readInput() { prefix.textContent = 'USER > '; read(prefix.textContent).then(text => { prefix.textContent = ''; repl.stdout.onwrite = result => { history.innerHTML += `${result.replaceAll('\n', '
')}`; readInput(); }; repl.stdin.pushData(text); }); } readInput(); function getNextBracket (chars, searched, alternate) { let alternates = 0; let i = 0; for (let c of chars) { if (alternates == 0 && c == searched) { return i; } else if (c == alternate) { alternates++; } else if (c == searched) { alternates--; } i++; } } function getNextClosingBracket(text, index) { return getNextBracket(Array.from(text.substr(index)), ')', '(') + index; } function getPreviousOpenBracket(text, index) { return index - getNextBracket(Array.from(text.substr(0, index)).reverse(), '(', ')') - 1; } function updateText(selectionStart, selectionEnd) { while(text.firstChild) { text.removeChild(text.lastChild); } selectionEnd += selectionEnd - selectionStart ? 0 : 1; let cursorText = input.value.substr(selectionStart, selectionEnd - selectionStart) || ' '; let secondCursorIdx = -1; switch (cursorText) { case '(': secondCursorIdx = getNextClosingBracket(input.value, selectionEnd); break; case ')': secondCursorIdx = getPreviousOpenBracket(input.value, selectionStart); break; } if (secondCursorIdx >= 0 && selectionStart > secondCursorIdx) { text.appendChild(document.createTextNode(input.value.substr(0, secondCursorIdx))); const secondCursor = document.createElement('SPAN'); secondCursor.classList.add('second-cursor'); secondCursor.appendChild(document.createTextNode(input.value.substr(secondCursorIdx, 1))); text.appendChild(secondCursor); if (secondCursorIdx + 1 != selectionStart) { text.appendChild( document.createTextNode( input.value.substr(secondCursorIdx + 1, selectionStart - (secondCursorIdx + 1)))); } } else if (selectionStart > 0) { text.appendChild(document.createTextNode(input.value.substr(0, selectionStart))); } let cursor = document.createElement('SPAN'); cursor.classList.add('cursor'); // cursor.appendChild(document.createTextNode(cursorText)); cursor.innerHTML = cursorText; if (cursorText == '\n') { cursor.classList.add('end-of-line'); } text.appendChild(cursor); if (secondCursorIdx >= selectionEnd) { text.appendChild( document.createTextNode( input.value.substr(selectionEnd, secondCursorIdx - selectionEnd))); const secondCursor = document.createElement('SPAN'); secondCursor.classList.add('second-cursor'); secondCursor.appendChild(document.createTextNode(input.value.substr(secondCursorIdx, 1))); text.appendChild(secondCursor); text.appendChild( document.createTextNode( input.value.substr(secondCursorIdx + 1))); } else if (selectionEnd < input.value.length) { text.appendChild(document.createTextNode(input.value.substr(selectionEnd))); } } input.addEventListener('input', (e) => { var selection = input.selectionStart; if (e.data == '(') { input.value = input.value.slice(0, selection) + ')' + input.value.slice(selection); input.setSelectionRange(selection, selection); } updateText(input.selectionStart, input.selectionEnd); buffer = input.value; }) input.addEventListener('keydown', (e) => { if (e.key == 'Backspace' && input.selectionEnd == input.selectionStart) { if (input.value.substr(input.selectionStart - 1, 1) == '(' && input.value.substr(input.selectionStart, 1) == ')') { input.value = input.value.substr(0, input.selectionStart - 1) + input.value.substr(input.selectionStart + 1); input.selectionStart = input.selectionEnd = input.selectionStart - 1; e.preventDefault(); } else if (input.value.substr(input.selectionStart - 1, 1) == ')'){ input.selectionStart = input.selectionEnd = input.selectionStart - 1; e.preventDefault(); } } else if (e.key == ')' && input.selectionStart - input.selectionEnd == 0 && input.value.substr(input.selectionStart, 1) == ')') { input.selectionEnd = ++input.selectionStart; e.preventDefault(); } setTimeout(() => { updateText(input.selectionStart, input.selectionEnd); }); }) document.addEventListener('mouseup', (e) => { const selection = document.getSelection(); if (!(selection.anchorNode.parentNode == text || selection.anchorNode.parentNode.parentNode == text)) { return; } let textNodes = [], node, walk = document.createTreeWalker(text, NodeFilter.SHOW_TEXT, null, false); while (node = walk.nextNode()) { textNodes.push(node); } let startNodeIdx = textNodes.indexOf(selection.anchorNode); let endNodeIdx = textNodes.indexOf(selection.focusNode); let startNodeOffset = selection.anchorOffset; let endNodeOffset = selection.focusOffset; for (let i = 0; i < startNodeIdx; i++) { startNodeOffset += textNodes[i].textContent.length; } for (let i = 0; i < endNodeIdx; i++) { endNodeOffset += textNodes[i].textContent.length; } input.setSelectionRange(Math.min(startNodeOffset, endNodeOffset), Math.max(startNodeOffset, endNodeOffset)); updateText(input.selectionStart, input.selectionEnd); }); function focusInput() { input.focus(); } window.addEventListener('focus', focusInput); window.addEventListener('click', focusInput); focusInput(); // WASM const response = await fetch('./lisp.wasm'); const bytes = await response.arrayBuffer(); const results = await WebAssembly.instantiate(bytes, { env: { jsprint: (byteOffset) => { let s = ''; const a = new Uint8Array(memory.buffer); for (var i = byteOffset; a[i]; i++) { s += String.fromCharCode(a[i]); } console.log(s); } } }); const instance = results.instance; const memory = instance.exports.pagememory; instance.exports.helloworld();