diff options
Diffstat (limited to 'Blog/Components/Pages/Concat.razor.js')
| -rw-r--r-- | Blog/Components/Pages/Concat.razor.js | 261 |
1 files changed, 261 insertions, 0 deletions
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 @@ | |||
| 1 | import { getById, div, span, h, writeError } from "/common.module.js"; | ||
| 2 | import lzString from "/lz-string.module.js"; | ||
| 3 | |||
| 4 | let definitions = {}; | ||
| 5 | let log = undefined; | ||
| 6 | let input = undefined; | ||
| 7 | |||
| 8 | export function onLoad() { | ||
| 9 | console.log('Loaded'); | ||
| 10 | const form = getById("form"); | ||
| 11 | input = getById("input"); | ||
| 12 | log = getById("log"); | ||
| 13 | |||
| 14 | form.onsubmit = async (event) => { | ||
| 15 | event.preventDefault(); | ||
| 16 | await submitForm(); | ||
| 17 | }; | ||
| 18 | |||
| 19 | const urlParams = new URLSearchParams(window.location.search); | ||
| 20 | const queryInput = urlParams.get("in"); | ||
| 21 | |||
| 22 | if (input.value.length === 0) { | ||
| 23 | input.value = lzString.decompressFromEncodedURIComponent(queryInput); | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | export function onUpdate() { | ||
| 28 | const urlParams = new URLSearchParams(window.location.search); | ||
| 29 | const queryInput = urlParams.get("in"); | ||
| 30 | |||
| 31 | if (input.value.length === 0) { | ||
| 32 | input.value = lzString.decompressFromEncodedURIComponent(queryInput); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | async function submitForm() { | ||
| 37 | console.log(input.value); | ||
| 38 | resetLog(); | ||
| 39 | try { | ||
| 40 | definitions = {}; | ||
| 41 | const stack = await evalString(input.value); | ||
| 42 | console.log(stack); | ||
| 43 | |||
| 44 | const path = window.location.pathname; | ||
| 45 | const params = new URLSearchParams(window.location.search); | ||
| 46 | const hash = window.location.hash; | ||
| 47 | |||
| 48 | params.set("in", lzString.compressToEncodedURIComponent(input.value)); | ||
| 49 | window.history.replaceState( | ||
| 50 | {}, | ||
| 51 | "", | ||
| 52 | `${path}?${params.toString()}${hash}`, | ||
| 53 | ); | ||
| 54 | } catch (error) { | ||
| 55 | writeError(error); | ||
| 56 | console.log(error); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | function splitWords(input) { | ||
| 61 | return input | ||
| 62 | .trim() | ||
| 63 | .split(/\s+/) | ||
| 64 | .filter((i) => i); | ||
| 65 | } | ||
| 66 | |||
| 67 | /** @param {string} inputString */ | ||
| 68 | function evalString(inputString) { | ||
| 69 | let words = splitWords(inputString); | ||
| 70 | return evalWords(words); | ||
| 71 | } | ||
| 72 | |||
| 73 | function effect2(f) { | ||
| 74 | return (stack) => { | ||
| 75 | if (stack.length <= 1) throw "stack underflow, need 2 numbers"; | ||
| 76 | let [x, y, ...rest] = stack; | ||
| 77 | return [f(y, x), ...rest]; | ||
| 78 | }; | ||
| 79 | } | ||
| 80 | |||
| 81 | const plus = effect2((a, b) => a + b); | ||
| 82 | const subtract = effect2((a, b) => a - b); | ||
| 83 | const multiply = effect2((a, b) => a * b); | ||
| 84 | const divide = effect2((a, b) => a / b); | ||
| 85 | |||
| 86 | async function evalWords(inputWords) { | ||
| 87 | let words = inputWords; | ||
| 88 | let stack = []; | ||
| 89 | |||
| 90 | while (words.length > 0) { | ||
| 91 | await writeLog(stack, words); | ||
| 92 | if (words.length === 0) return stack; | ||
| 93 | |||
| 94 | let [word, ...rest] = words; | ||
| 95 | [stack, words] = evalWord(word, stack, rest); | ||
| 96 | |||
| 97 | await new Promise((r) => setTimeout(r, 100)); | ||
| 98 | } | ||
| 99 | await writeLog(stack, words); | ||
| 100 | return stack; | ||
| 101 | } | ||
| 102 | |||
| 103 | function evalWord(word, stack, rest) { | ||
| 104 | switch (word) { | ||
| 105 | case "+": | ||
| 106 | return [plus(stack), rest]; | ||
| 107 | case "-": | ||
| 108 | return [subtract(stack), rest]; | ||
| 109 | case "*": | ||
| 110 | return [multiply(stack), rest]; | ||
| 111 | case "/": | ||
| 112 | return [divide(stack), rest]; | ||
| 113 | case "dup": | ||
| 114 | return [dup(stack), rest]; | ||
| 115 | case "drop": | ||
| 116 | return [drop(stack), rest]; | ||
| 117 | case "swap": | ||
| 118 | return [swap(stack), rest]; | ||
| 119 | case "skip": | ||
| 120 | return skip(stack, rest); | ||
| 121 | case ",,": | ||
| 122 | return unquote(stack, rest); | ||
| 123 | case ":": | ||
| 124 | return [stack, define(rest)]; | ||
| 125 | default: | ||
| 126 | return parse(word, stack, rest); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | function unquote(stack, rest) { | ||
| 131 | let [quote, ...restStack] = stack; | ||
| 132 | if (typeof quote === "string" || quote instanceof String) { | ||
| 133 | return [restStack, [...splitWords(quote), ...rest]]; | ||
| 134 | } else { | ||
| 135 | throw "not a string, only strings are unquoteable"; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | function skip(stack, words) { | ||
| 140 | if (stack.length === 0) throw "stack underflow, dont know how much to skip"; | ||
| 141 | let [amount, ...restStack] = stack; | ||
| 142 | |||
| 143 | if (amount > words.length) | ||
| 144 | throw `program underflow, cant skip ${amount} words`; | ||
| 145 | if (amount <= 0) return [stack.slice(1), words]; // no skipping on <= 0 | ||
| 146 | |||
| 147 | let restWords = words.slice(amount); | ||
| 148 | |||
| 149 | return [restStack, restWords]; | ||
| 150 | } | ||
| 151 | |||
| 152 | function define(words) { | ||
| 153 | if (words.length < 2) throw "missing definition after ':'"; | ||
| 154 | let [ident, ...rest] = words; | ||
| 155 | let index = 0; | ||
| 156 | let definition = []; | ||
| 157 | |||
| 158 | for (; index < rest.length; index++) { | ||
| 159 | const word = rest[index]; | ||
| 160 | if (word === ";") { | ||
| 161 | break; | ||
| 162 | } else if (rest.length - 1 === index) { | ||
| 163 | throw "expected ';', found end of program"; | ||
| 164 | } | ||
| 165 | definition.push(word); | ||
| 166 | } | ||
| 167 | |||
| 168 | definitions[ident] = definition; | ||
| 169 | |||
| 170 | return rest.slice(index + 1); | ||
| 171 | } | ||
| 172 | |||
| 173 | function dup(stack) { | ||
| 174 | if (stack.length === 0) throw "stack underflow, nothing to duplicate"; | ||
| 175 | let [x, ...rest] = stack; | ||
| 176 | return [x, x, ...rest]; | ||
| 177 | } | ||
| 178 | |||
| 179 | function drop(stack) { | ||
| 180 | if (stack.length === 0) throw "stack underflow, nothing to drop"; | ||
| 181 | let [_, ...rest] = stack; | ||
| 182 | return rest; | ||
| 183 | } | ||
| 184 | |||
| 185 | function swap(stack) { | ||
| 186 | if (stack.length < 2) throw "stack underflow, not enough to swap"; | ||
| 187 | let [x, y, ...rest] = stack; | ||
| 188 | return [y, x, ...rest]; | ||
| 189 | } | ||
| 190 | |||
| 191 | function parse(word, stack, rest) { | ||
| 192 | if (word.startsWith('"')) { | ||
| 193 | return parseString(word, stack, rest); | ||
| 194 | } | ||
| 195 | |||
| 196 | if (word in definitions) { | ||
| 197 | return [stack, [...definitions[word], ...rest]]; | ||
| 198 | } | ||
| 199 | |||
| 200 | let num = Number(word); | ||
| 201 | if (isNaN(num)) { | ||
| 202 | throw `word '${word}' not recognised`; | ||
| 203 | } | ||
| 204 | |||
| 205 | return [[num, ...stack], rest]; | ||
| 206 | } | ||
| 207 | |||
| 208 | function parseString(word, stack, rest) { | ||
| 209 | if (word.length > 1 && word.endsWith('"')) { | ||
| 210 | return [[word.slice(1, -1), ...stack], rest]; | ||
| 211 | } | ||
| 212 | |||
| 213 | let string = word.slice(1); | ||
| 214 | let index = 0; | ||
| 215 | |||
| 216 | for (; index < rest.length; index++) { | ||
| 217 | const word = rest[index]; | ||
| 218 | if (word.endsWith('"')) { | ||
| 219 | string = string.concat(" ", word.slice(0, -1)); | ||
| 220 | break; | ||
| 221 | } else if (rest.length - 1 === index) { | ||
| 222 | throw "expected word ending with '\"', found end of program"; | ||
| 223 | } | ||
| 224 | string = string.concat(" ", word); | ||
| 225 | } | ||
| 226 | |||
| 227 | return [[string, ...stack], rest.slice(index + 1)]; | ||
| 228 | } | ||
| 229 | |||
| 230 | function writeLog(stack, words) { | ||
| 231 | return new Promise((resolve, _reject) => { | ||
| 232 | let log_left = span(`[${stack.join(", ")}]`); | ||
| 233 | let log_right = span(words.join(" ")); | ||
| 234 | |||
| 235 | if (words.length === 0) { | ||
| 236 | log_left.textContent += " <=="; | ||
| 237 | } | ||
| 238 | |||
| 239 | let log_row = div(log_left, log_right); | ||
| 240 | log_row.className = "flex-spread"; | ||
| 241 | |||
| 242 | log.appendChild(log_row); | ||
| 243 | resolve(); | ||
| 244 | }); | ||
| 245 | } | ||
| 246 | |||
| 247 | function resetLog() { | ||
| 248 | if (!log.hasChildNodes()) return; | ||
| 249 | |||
| 250 | let summary = h("summary"); | ||
| 251 | summary.textContent = log.firstChild.lastChild.textContent; | ||
| 252 | |||
| 253 | let old_log = log.cloneNode(true); | ||
| 254 | old_log.id = ""; | ||
| 255 | |||
| 256 | let details = h("details", summary, old_log); | ||
| 257 | details.className = "history"; | ||
| 258 | |||
| 259 | log.insertAdjacentElement("afterend", details); | ||
| 260 | log.replaceChildren(); //remove children, clear log | ||
| 261 | } | ||