MIRROR: javascript for 🐜's, a tiny runtime with big ambitions
1// Reproduce "undeclared reg 0" MIR error: switch statement with many
2// string-constant cases, method calls, and property access — matching
3// the exact bytecode pattern of a TUI handleKey function.
4
5const keys = {
6 UP: '\x1b[A',
7 DOWN: '\x1b[B',
8 RIGHT: '\x1b[C',
9 LEFT: '\x1b[D',
10 PAGE_UP: '\x1b[5~',
11 PAGE_DOWN: '\x1b[6~',
12 ENTER: '\r',
13 ESCAPE: '\x1b',
14 TAB: '\t',
15 BACKSPACE: '\x7f',
16 HOME: '\x1b[H',
17 END: '\x1b[F',
18 CTRL_C: '\x03',
19};
20
21const list = {
22 index: 0,
23 items: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
24 selectNext() { this.index = Math.min(this.items.length - 1, this.index + 1); },
25 selectPrev() { this.index = Math.max(0, this.index - 1); },
26 pageDown() { this.index = Math.min(this.items.length - 1, this.index + 5); },
27 pageUp() { this.index = Math.max(0, this.index - 5); },
28 getSelected() { return this.items[this.index]; },
29};
30
31const logList = {
32 index: 0,
33 items: ['a', 'b', 'c'],
34 selectNext() { this.index = Math.min(this.items.length - 1, this.index + 1); },
35 selectPrev() { this.index = Math.max(0, this.index - 1); },
36 pageDown() { this.index = Math.min(this.items.length - 1, this.index + 5); },
37 pageUp() { this.index = Math.max(0, this.index - 5); },
38};
39
40const state = {
41 view: 'tasks',
42 searchMode: false,
43 searchQuery: '',
44 taskFilter: 'all',
45 settingsIndex: 0,
46};
47
48let renderCount = 0;
49function render() { renderCount++; }
50
51// Use switch(key) — this generates DUP+CONST+SEQ+JMP_FALSE chains where
52// the discriminant stays on the vstack across branch targets.
53function handleKey(key) {
54 if (state.searchMode) {
55 if (key === keys.ESCAPE) {
56 state.searchMode = false;
57 state.searchQuery = '';
58 render();
59 return;
60 }
61 if (key === keys.ENTER) {
62 state.searchMode = false;
63 render();
64 return;
65 }
66 return;
67 }
68
69 switch (key) {
70 case 'q':
71 case keys.CTRL_C:
72 return;
73
74 case '1':
75 state.view = 'dashboard';
76 render();
77 break;
78 case '2':
79 state.view = 'tasks';
80 render();
81 break;
82 case '3':
83 state.view = 'logs';
84 render();
85 break;
86 case '4':
87 state.view = 'settings';
88 render();
89 break;
90
91 case '?':
92 render();
93 break;
94 case 'm':
95 render();
96 break;
97
98 case keys.UP:
99 case 'k':
100 if (state.view === 'tasks') list.selectPrev();
101 else if (state.view === 'logs') logList.selectPrev();
102 else if (state.view === 'settings') state.settingsIndex = Math.max(0, state.settingsIndex - 1);
103 render();
104 break;
105
106 case keys.DOWN:
107 case 'j':
108 if (state.view === 'tasks') list.selectNext();
109 else if (state.view === 'logs') logList.selectNext();
110 else if (state.view === 'settings') state.settingsIndex = Math.min(9, state.settingsIndex + 1);
111 render();
112 break;
113
114 case keys.PAGE_UP:
115 if (state.view === 'tasks') list.pageUp();
116 else if (state.view === 'logs') logList.pageUp();
117 render();
118 break;
119
120 case keys.PAGE_DOWN:
121 if (state.view === 'tasks') list.pageDown();
122 else if (state.view === 'logs') logList.pageDown();
123 render();
124 break;
125
126 case keys.ENTER:
127 case keys.RIGHT:
128 if (state.view === 'tasks') {
129 const item = list.getSelected();
130 if (item) {
131 const desc = 'item_' + String(item);
132 if (desc.length > 0) render();
133 }
134 }
135 render();
136 break;
137
138 case keys.LEFT:
139 render();
140 break;
141
142 case keys.HOME:
143 list.index = 0;
144 render();
145 break;
146
147 case keys.END:
148 list.index = list.items.length - 1;
149 render();
150 break;
151
152 case keys.TAB:
153 state.view = 'dashboard';
154 render();
155 break;
156
157 case '/':
158 if (state.view === 'tasks') {
159 state.searchMode = true;
160 state.searchQuery = '';
161 }
162 render();
163 break;
164
165 case 'a':
166 if (state.view === 'tasks') { state.taskFilter = 'all'; render(); }
167 break;
168 case 't':
169 if (state.view === 'tasks') { state.taskFilter = 'todo'; render(); }
170 break;
171 case 'p':
172 if (state.view === 'tasks') { state.taskFilter = 'in_progress'; render(); }
173 break;
174 case 'd':
175 if (state.view === 'tasks') { state.taskFilter = 'done'; render(); }
176 break;
177 case 'P':
178 render();
179 break;
180 case 'D':
181 render();
182 break;
183 case 'N':
184 render();
185 break;
186 }
187}
188
189const scrollKeys = [
190 keys.UP, keys.DOWN, keys.UP, keys.DOWN,
191 keys.PAGE_UP, keys.PAGE_DOWN,
192 keys.UP, keys.DOWN, 'k', 'j',
193 keys.ENTER, keys.LEFT, keys.RIGHT,
194 keys.HOME, keys.END, keys.TAB,
195];
196
197for (let i = 0; i < 500; i++) {
198 handleKey(scrollKeys[i % scrollKeys.length]);
199}
200
201console.log('OK: rendered', renderCount, 'times, list index:', list.index);