You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
8.6 KiB
309 lines
8.6 KiB
3 years ago
|
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 = '<br>';
|
||
|
const text = await read('<br>');
|
||
|
|
||
|
repl.stdout.onwrite = result => {
|
||
|
history.innerHTML += `${result.replaceAll('\n', '<br>')}`;
|
||
|
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', '<br>')}`;
|
||
|
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();
|