From 9ab322751a732d8cbc1ddf4f2ecf5022d7242baa Mon Sep 17 00:00:00 2001 From: Marijn Besseling Date: Sun, 7 Sep 2025 20:56:09 +0200 Subject: WIP migration --- Blog/Components/Pages/Concat.razor.js | 261 ++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 Blog/Components/Pages/Concat.razor.js (limited to 'Blog/Components/Pages/Concat.razor.js') diff --git a/Blog/Components/Pages/Concat.razor.js b/Blog/Components/Pages/Concat.razor.js new file mode 100644 index 0000000..bd476e9 --- /dev/null +++ b/Blog/Components/Pages/Concat.razor.js @@ -0,0 +1,261 @@ +import { getById, div, span, h, writeError } from "/common.module.js"; +import lzString from "/lz-string.module.js"; + +let definitions = {}; +let log = undefined; +let input = undefined; + +export function onLoad() { + console.log('Loaded'); + const form = getById("form"); + input = getById("input"); + log = getById("log"); + + form.onsubmit = async (event) => { + event.preventDefault(); + await submitForm(); + }; + + const urlParams = new URLSearchParams(window.location.search); + const queryInput = urlParams.get("in"); + + if (input.value.length === 0) { + input.value = lzString.decompressFromEncodedURIComponent(queryInput); + } +} + +export function onUpdate() { + const urlParams = new URLSearchParams(window.location.search); + const queryInput = urlParams.get("in"); + + if (input.value.length === 0) { + input.value = lzString.decompressFromEncodedURIComponent(queryInput); + } +} + +async function submitForm() { + console.log(input.value); + resetLog(); + try { + definitions = {}; + const stack = await evalString(input.value); + console.log(stack); + + const path = window.location.pathname; + const params = new URLSearchParams(window.location.search); + const hash = window.location.hash; + + params.set("in", lzString.compressToEncodedURIComponent(input.value)); + window.history.replaceState( + {}, + "", + `${path}?${params.toString()}${hash}`, + ); + } catch (error) { + writeError(error); + console.log(error); + } +} + +function splitWords(input) { + return input + .trim() + .split(/\s+/) + .filter((i) => i); +} + +/** @param {string} inputString */ +function evalString(inputString) { + let words = splitWords(inputString); + return evalWords(words); +} + +function effect2(f) { + return (stack) => { + if (stack.length <= 1) throw "stack underflow, need 2 numbers"; + let [x, y, ...rest] = stack; + return [f(y, x), ...rest]; + }; +} + +const plus = effect2((a, b) => a + b); +const subtract = effect2((a, b) => a - b); +const multiply = effect2((a, b) => a * b); +const divide = effect2((a, b) => a / b); + +async function evalWords(inputWords) { + let words = inputWords; + let stack = []; + + while (words.length > 0) { + await writeLog(stack, words); + if (words.length === 0) return stack; + + let [word, ...rest] = words; + [stack, words] = evalWord(word, stack, rest); + + await new Promise((r) => setTimeout(r, 100)); + } + await writeLog(stack, words); + return stack; +} + +function evalWord(word, stack, rest) { + switch (word) { + case "+": + return [plus(stack), rest]; + case "-": + return [subtract(stack), rest]; + case "*": + return [multiply(stack), rest]; + case "/": + return [divide(stack), rest]; + case "dup": + return [dup(stack), rest]; + case "drop": + return [drop(stack), rest]; + case "swap": + return [swap(stack), rest]; + case "skip": + return skip(stack, rest); + case ",,": + return unquote(stack, rest); + case ":": + return [stack, define(rest)]; + default: + return parse(word, stack, rest); + } +} + +function unquote(stack, rest) { + let [quote, ...restStack] = stack; + if (typeof quote === "string" || quote instanceof String) { + return [restStack, [...splitWords(quote), ...rest]]; + } else { + throw "not a string, only strings are unquoteable"; + } +} + +function skip(stack, words) { + if (stack.length === 0) throw "stack underflow, dont know how much to skip"; + let [amount, ...restStack] = stack; + + if (amount > words.length) + throw `program underflow, cant skip ${amount} words`; + if (amount <= 0) return [stack.slice(1), words]; // no skipping on <= 0 + + let restWords = words.slice(amount); + + return [restStack, restWords]; +} + +function define(words) { + if (words.length < 2) throw "missing definition after ':'"; + let [ident, ...rest] = words; + let index = 0; + let definition = []; + + for (; index < rest.length; index++) { + const word = rest[index]; + if (word === ";") { + break; + } else if (rest.length - 1 === index) { + throw "expected ';', found end of program"; + } + definition.push(word); + } + + definitions[ident] = definition; + + return rest.slice(index + 1); +} + +function dup(stack) { + if (stack.length === 0) throw "stack underflow, nothing to duplicate"; + let [x, ...rest] = stack; + return [x, x, ...rest]; +} + +function drop(stack) { + if (stack.length === 0) throw "stack underflow, nothing to drop"; + let [_, ...rest] = stack; + return rest; +} + +function swap(stack) { + if (stack.length < 2) throw "stack underflow, not enough to swap"; + let [x, y, ...rest] = stack; + return [y, x, ...rest]; +} + +function parse(word, stack, rest) { + if (word.startsWith('"')) { + return parseString(word, stack, rest); + } + + if (word in definitions) { + return [stack, [...definitions[word], ...rest]]; + } + + let num = Number(word); + if (isNaN(num)) { + throw `word '${word}' not recognised`; + } + + return [[num, ...stack], rest]; +} + +function parseString(word, stack, rest) { + if (word.length > 1 && word.endsWith('"')) { + return [[word.slice(1, -1), ...stack], rest]; + } + + let string = word.slice(1); + let index = 0; + + for (; index < rest.length; index++) { + const word = rest[index]; + if (word.endsWith('"')) { + string = string.concat(" ", word.slice(0, -1)); + break; + } else if (rest.length - 1 === index) { + throw "expected word ending with '\"', found end of program"; + } + string = string.concat(" ", word); + } + + return [[string, ...stack], rest.slice(index + 1)]; +} + +function writeLog(stack, words) { + return new Promise((resolve, _reject) => { + let log_left = span(`[${stack.join(", ")}]`); + let log_right = span(words.join(" ")); + + if (words.length === 0) { + log_left.textContent += " <=="; + } + + let log_row = div(log_left, log_right); + log_row.className = "flex-spread"; + + log.appendChild(log_row); + resolve(); + }); +} + +function resetLog() { + if (!log.hasChildNodes()) return; + + let summary = h("summary"); + summary.textContent = log.firstChild.lastChild.textContent; + + let old_log = log.cloneNode(true); + old_log.id = ""; + + let details = h("details", summary, old_log); + details.className = "history"; + + log.insertAdjacentElement("afterend", details); + log.replaceChildren(); //remove children, clear log +} -- cgit v1.2.3