Blog/Components/Pages/Calc.razor.js 3.1 K · 126 lines · raw · history

1 import { getById, h, writeError } from "/common.module.js"
2
3 const form = getById("form");
4 const input = getById("input");
5 const log = getById("log");
6
7 form.addEventListener("submit", submitForm);
8
9 const urlParams = new URLSearchParams(window.location.search);
10 const queryInput = urlParams.get('in');
11
12 if (input.value.length === 0) {
13 input.value = queryInput;
14 }
15
16 /** @param {SubmitEvent} event */
17 function submitForm(event) {
18 console.log(input.value);
19 resetLog();
20 try {
21 const stack = evalString(input.value);
22 console.log(stack);
23
24 const path = window.location.pathname;
25 const params = new URLSearchParams(window.location.search);
26 const hash = window.location.hash;
27
28 params.set("in", input.value);
29 window.history.replaceState({}, '', `${path}?${params.toString()}${hash}`);
30 }
31 catch (error) {
32 writeError(error);
33 }
34 event.preventDefault();
35 }
36
37 /** @param {string} input */
38 function evalString(input) {
39 let words = input.trim().split(' ').filter(i => i);
40 return evalWords([], words);
41 }
42
43 function effect2(f) {
44 return (stack) => {
45 if (stack.length <= 1) throw "stack underflow";
46 let [x, y, ...rest] = stack;
47 return [f(y, x), ...rest];
48 }
49 }
50
51 const plus = effect2((a, b) => a + b);
52 const subtract = effect2((a, b) => a - b);
53 const multiply = effect2((a, b) => a * b);
54 const divide = effect2((a, b) => a / b);
55
56
57 function evalWords(stack, words) {
58 writeLog(stack, words);
59 if (words.length === 0) return stack;
60
61 let [word, ...rest] = words;
62 return evalWords(evalWord(word, stack), rest);
63 }
64
65 function evalWord(word, stack, rest) {
66 switch (word) {
67 case "+": return plus(stack);
68 case "-": return subtract(stack);
69 case "*": return multiply(stack);
70 case "/": return divide(stack);
71 case "dup": return dup(stack);
72 case "drop": return drop(stack);
73 case "swap": return swap(stack);
74 default: return parse(word, stack);
75 }
76 }
77
78 function dup(stack) {
79 let [x, ...rest] = stack;
80 return [x, x, ...rest];
81 }
82
83 function drop(stack) {
84 let [_, ...rest] = stack;
85 return rest;
86 }
87
88 function swap(stack) {
89 let [x, y, ...rest] = stack;
90 return [y, x, ...rest];
91 }
92
93 function parse(word, stack) {
94 let num = Number(word);
95 if (isNaN(num)) {
96 throw `word '${word}' not recognised`;
97 }
98 return [num, ...stack];
99 }
100
101 function writeLog(stack, words) {
102 let log_left = h("span", `[${stack.join(', ')}]`);
103 let log_right = h("span", words.join(' '));
104
105 if (words.length === 0) {
106 log_left.textContent += " <=="
107 }
108
109 let log_row = h("div", {class: "stack-step"}, [log_left, log_right]);
110
111 log.appendChild(log_row);
112 }
113
114 function resetLog() {
115 if (!log.hasChildNodes()) return;
116
117 let summary = h("summary", log.firstChild.lastChild.textContent);
118
119 let old_log = log.cloneNode(true);
120 old_log.id = '';
121
122 let details = h("details", {class: "history"}, [summary, old_log]);
123
124 log.insertAdjacentElement("afterend", details);
125 log.replaceChildren(); //remove children, clear log
126 }