summaryrefslogtreecommitdiff
path: root/Blog/Components/Pages/Concat.razor.js
diff options
context:
space:
mode:
authorMarijn Besseling <njirambem@gmail.com>2025-09-07 20:56:09 +0200
committerMarijn Besseling <njirambem@gmail.com>2025-09-07 20:56:09 +0200
commit9ab322751a732d8cbc1ddf4f2ecf5022d7242baa (patch)
tree49abc49c7d148b2f575aa5daef32875d44729561 /Blog/Components/Pages/Concat.razor.js
WIP migration
Diffstat (limited to 'Blog/Components/Pages/Concat.razor.js')
-rw-r--r--Blog/Components/Pages/Concat.razor.js261
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 @@
1import { getById, div, span, h, writeError } from "/common.module.js";
2import lzString from "/lz-string.module.js";
3
4let definitions = {};
5let log = undefined;
6let input = undefined;
7
8export 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
27export 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
36async 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
60function splitWords(input) {
61 return input
62 .trim()
63 .split(/\s+/)
64 .filter((i) => i);
65}
66
67/** @param {string} inputString */
68function evalString(inputString) {
69 let words = splitWords(inputString);
70 return evalWords(words);
71}
72
73function 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
81const plus = effect2((a, b) => a + b);
82const subtract = effect2((a, b) => a - b);
83const multiply = effect2((a, b) => a * b);
84const divide = effect2((a, b) => a / b);
85
86async 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
103function 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
130function 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
139function 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
152function 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
173function 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
179function drop(stack) {
180 if (stack.length === 0) throw "stack underflow, nothing to drop";
181 let [_, ...rest] = stack;
182 return rest;
183}
184
185function 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
191function 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
208function 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
230function 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
247function 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}