rk86 2.0.19 → 2.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/rk86.js +232 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rk86",
3
- "version": "2.0.19",
3
+ "version": "2.0.20",
4
4
  "description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
5
5
  "bin": {
6
6
  "rk86": "rk86.js"
package/rk86.js CHANGED
@@ -1726,6 +1726,127 @@ var ALL_MNEMONICS = new Set([
1726
1726
  "END",
1727
1727
  "EQU"
1728
1728
  ]);
1729
+ var INVERT_JUMP = {
1730
+ Z: "JNZ",
1731
+ NZ: "JZ",
1732
+ C: "JNC",
1733
+ NC: "JC",
1734
+ PO: "JPE",
1735
+ PE: "JPO",
1736
+ P: "JM",
1737
+ M: "JP",
1738
+ "==": "JNZ",
1739
+ "<>": "JZ"
1740
+ };
1741
+ var VALID_PROC_REGS = new Set(["PSW", "B", "D", "H"]);
1742
+ function popsAndRet(regs, orig) {
1743
+ const out = [];
1744
+ for (let k = regs.length - 1;k >= 0; k--) {
1745
+ out.push({ text: ` POP ${regs[k]}`, orig });
1746
+ }
1747
+ out.push({ text: ` RET`, orig });
1748
+ return out;
1749
+ }
1750
+ function preprocess(source) {
1751
+ const lines = source.split(`
1752
+ `);
1753
+ const out = [];
1754
+ const stack = [];
1755
+ let counter = 0;
1756
+ let proc = null;
1757
+ for (let i = 0;i < lines.length; i++) {
1758
+ const line = lines[i];
1759
+ const orig = i + 1;
1760
+ const bare = stripComment(line).trim();
1761
+ const ifMatch = bare.match(/^\.?if\s+(\S+)\s*$/i);
1762
+ if (ifMatch) {
1763
+ const cond = ifMatch[1].toUpperCase();
1764
+ const jmp = INVERT_JUMP[cond];
1765
+ if (!jmp) {
1766
+ throw new AsmError(`unknown .if condition: ${ifMatch[1]}`, orig, line, firstNonSpaceCol(line));
1767
+ }
1768
+ const id = counter++;
1769
+ stack.push({ id, sawElse: false, line: orig, source: line });
1770
+ out.push({ text: ` ${jmp} @_if_${id}_else`, orig });
1771
+ continue;
1772
+ }
1773
+ if (/^\.?else\s*$/i.test(bare)) {
1774
+ const top = stack[stack.length - 1];
1775
+ if (!top) {
1776
+ throw new AsmError(".else without .if", orig, line, firstNonSpaceCol(line));
1777
+ }
1778
+ if (top.sawElse) {
1779
+ throw new AsmError("duplicate .else", orig, line, firstNonSpaceCol(line));
1780
+ }
1781
+ top.sawElse = true;
1782
+ out.push({ text: ` JMP @_if_${top.id}_exit`, orig });
1783
+ out.push({ text: `@_if_${top.id}_else:`, orig });
1784
+ continue;
1785
+ }
1786
+ if (/^\.?endif\s*$/i.test(bare)) {
1787
+ const top = stack.pop();
1788
+ if (!top) {
1789
+ throw new AsmError(".endif without .if", orig, line, firstNonSpaceCol(line));
1790
+ }
1791
+ const suffix = top.sawElse ? "exit" : "else";
1792
+ out.push({ text: `@_if_${top.id}_${suffix}:`, orig });
1793
+ continue;
1794
+ }
1795
+ const procMatch = bare.match(/^([A-Za-z_]\w*):?\s+\.?proc\b\s*(.*)$/i);
1796
+ if (procMatch && !ALL_MNEMONICS.has(procMatch[1].toUpperCase())) {
1797
+ if (proc) {
1798
+ throw new AsmError("nested .proc not allowed", orig, line, firstNonSpaceCol(line));
1799
+ }
1800
+ const name = procMatch[1];
1801
+ const regsRaw = procMatch[2].trim();
1802
+ const regs = [];
1803
+ if (regsRaw) {
1804
+ for (const r of regsRaw.split(/[,\s]+/)) {
1805
+ if (!r)
1806
+ continue;
1807
+ const up = r.toUpperCase();
1808
+ if (!VALID_PROC_REGS.has(up)) {
1809
+ throw new AsmError(`invalid .proc register: ${r} (expected PSW, B, D, or H)`, orig, line, firstNonSpaceCol(line));
1810
+ }
1811
+ regs.push(up);
1812
+ }
1813
+ }
1814
+ proc = { regs, line: orig, source: line };
1815
+ out.push({ text: `${name}:`, orig });
1816
+ for (const r of regs) {
1817
+ out.push({ text: ` PUSH ${r}`, orig });
1818
+ }
1819
+ continue;
1820
+ }
1821
+ if (/^\.proc(\s|$)/i.test(bare) || /^proc\s+\S/i.test(bare)) {
1822
+ throw new AsmError(".proc requires a label", orig, line, firstNonSpaceCol(line));
1823
+ }
1824
+ if (/^\.?endp\s*$/i.test(bare)) {
1825
+ if (!proc) {
1826
+ throw new AsmError(".endp without .proc", orig, line, firstNonSpaceCol(line));
1827
+ }
1828
+ out.push(...popsAndRet(proc.regs, orig));
1829
+ proc = null;
1830
+ continue;
1831
+ }
1832
+ if (/^\.?return\s*$/i.test(bare)) {
1833
+ if (!proc) {
1834
+ throw new AsmError(".return outside .proc", orig, line, firstNonSpaceCol(line));
1835
+ }
1836
+ out.push(...popsAndRet(proc.regs, orig));
1837
+ continue;
1838
+ }
1839
+ out.push({ text: line, orig });
1840
+ }
1841
+ if (stack.length) {
1842
+ const top = stack[stack.length - 1];
1843
+ throw new AsmError(".if without .endif", top.line, top.source, firstNonSpaceCol(top.source));
1844
+ }
1845
+ if (proc) {
1846
+ throw new AsmError(".proc without .endp", proc.line, proc.source, firstNonSpaceCol(proc.source));
1847
+ }
1848
+ return out;
1849
+ }
1729
1850
  var MAX_STATEMENTS_PER_LINE = 10;
1730
1851
  function splitStatements(line) {
1731
1852
  const src = stripComment(line);
@@ -1847,35 +1968,46 @@ function stripDirectiveDot(s) {
1847
1968
  }
1848
1969
  return s;
1849
1970
  }
1971
+ var LABEL_RE = /^(?:[A-Za-z_]\w*|@\w+|\.\w+)$/;
1972
+ function isMnemonic(tok) {
1973
+ return ALL_MNEMONICS.has(stripDirectiveDot(tok).toUpperCase());
1974
+ }
1850
1975
  function parseLine(line) {
1851
1976
  let s = stripComment(line).trim();
1852
1977
  if (!s)
1853
1978
  return { operands: [] };
1854
1979
  let label;
1855
1980
  const ci = s.indexOf(":");
1856
- if (ci > 0 && /^[A-Za-z_]\w*$/.test(s.slice(0, ci).trim())) {
1981
+ if (ci > 0 && LABEL_RE.test(s.slice(0, ci).trim())) {
1857
1982
  label = s.slice(0, ci).trim();
1858
1983
  s = s.slice(ci + 1).trim();
1859
1984
  }
1860
1985
  if (!s)
1861
1986
  return { label, operands: [] };
1862
- const si = s.search(/\s/);
1863
- const first = si < 0 ? s : s.slice(0, si);
1864
- const rest = si < 0 ? "" : s.slice(si).trim();
1865
- if (!label && rest) {
1866
- const parts = rest.split(/\s+/);
1867
- if (stripDirectiveDot(parts[0]).toUpperCase() === "EQU") {
1868
- return {
1869
- label: first,
1870
- mnemonic: "EQU",
1871
- operands: [parts.slice(1).join(" ")],
1872
- isEqu: true
1873
- };
1987
+ let si = s.search(/\s/);
1988
+ let first = si < 0 ? s : s.slice(0, si);
1989
+ let rest = si < 0 ? "" : s.slice(si).trim();
1990
+ if (!label && rest && LABEL_RE.test(first) && !isMnemonic(first)) {
1991
+ const nextTok = rest.match(/^\S+/)?.[0] ?? "";
1992
+ if (isMnemonic(nextTok)) {
1993
+ label = first;
1994
+ si = rest.search(/\s/);
1995
+ first = si < 0 ? rest : rest.slice(0, si);
1996
+ rest = si < 0 ? "" : rest.slice(si).trim();
1874
1997
  }
1875
1998
  }
1999
+ const mnemonic = stripDirectiveDot(first);
2000
+ if (label && mnemonic.toUpperCase() === "EQU") {
2001
+ return {
2002
+ label,
2003
+ mnemonic: "EQU",
2004
+ operands: [rest],
2005
+ isEqu: true
2006
+ };
2007
+ }
1876
2008
  return {
1877
2009
  label,
1878
- mnemonic: stripDirectiveDot(first),
2010
+ mnemonic,
1879
2011
  operands: rest ? splitOperands(rest) : []
1880
2012
  };
1881
2013
  }
@@ -1893,6 +2025,31 @@ function tokenizeExpr(expr) {
1893
2025
  i += 3;
1894
2026
  continue;
1895
2027
  }
2028
+ if (c === "$") {
2029
+ tokens.push({ kind: "id", val: "$" });
2030
+ i++;
2031
+ continue;
2032
+ }
2033
+ if (c === "@") {
2034
+ let j = i + 1;
2035
+ while (j < expr.length && /\w/.test(expr[j]))
2036
+ j++;
2037
+ if (j === i + 1)
2038
+ throw new Error("expected identifier after '@'");
2039
+ tokens.push({ kind: "id", val: expr.slice(i, j) });
2040
+ i = j;
2041
+ continue;
2042
+ }
2043
+ if (c === ".") {
2044
+ let j = i + 1;
2045
+ while (j < expr.length && /\w/.test(expr[j]))
2046
+ j++;
2047
+ if (j === i + 1)
2048
+ throw new Error("expected identifier after '.'");
2049
+ tokens.push({ kind: "id", val: expr.slice(i, j) });
2050
+ i = j;
2051
+ continue;
2052
+ }
1896
2053
  if (/[0-9]/.test(c)) {
1897
2054
  let j = i;
1898
2055
  while (j < expr.length && /[0-9A-Fa-f]/.test(expr[j]))
@@ -1933,7 +2090,7 @@ function tokenizeExpr(expr) {
1933
2090
  }
1934
2091
  return tokens;
1935
2092
  }
1936
- function evalExpr(expr, symbols) {
2093
+ function evalExpr(expr, symbols, pc = 0, lastLabel = "") {
1937
2094
  const tokens = tokenizeExpr(expr);
1938
2095
  let pos = 0;
1939
2096
  function peek() {
@@ -1956,20 +2113,30 @@ function evalExpr(expr, symbols) {
1956
2113
  }
1957
2114
  if (t.kind === "id") {
1958
2115
  next();
1959
- const k = t.val.toUpperCase();
1960
- if (k === "LOW" || k === "HIGH") {
2116
+ const raw = t.val;
2117
+ if (raw === "$")
2118
+ return pc;
2119
+ const upper = raw.toUpperCase();
2120
+ if (upper === "LOW" || upper === "HIGH") {
1961
2121
  if (!isOp("("))
1962
- throw new Error(`${k} requires parentheses`);
2122
+ throw new Error(`${upper} requires parentheses`);
1963
2123
  next();
1964
2124
  const v = parseOr();
1965
2125
  if (!isOp(")"))
1966
2126
  throw new Error("expected ')'");
1967
2127
  next();
1968
- return k === "LOW" ? v & 255 : v >> 8 & 255;
2128
+ return upper === "LOW" ? v & 255 : v >> 8 & 255;
1969
2129
  }
2130
+ let name = raw;
2131
+ if (name.startsWith("@") || name.startsWith(".")) {
2132
+ if (!lastLabel)
2133
+ throw new Error(`local label without scope: ${raw}`);
2134
+ name = lastLabel + name;
2135
+ }
2136
+ const k = name.toUpperCase();
1970
2137
  if (symbols.has(k))
1971
2138
  return symbols.get(k);
1972
- throw new Error(`unknown symbol: ${t.val}`);
2139
+ throw new Error(`unknown symbol: ${raw}`);
1973
2140
  }
1974
2141
  if (t.kind === "op" && t.val === "(") {
1975
2142
  next();
@@ -2057,15 +2224,15 @@ function evalExpr(expr, symbols) {
2057
2224
  throw new Error(`unexpected token: ${tokens[pos].val}`);
2058
2225
  return result;
2059
2226
  }
2060
- function encode(m, ops, symbols) {
2227
+ function encode(m, ops, symbols, pc = 0, lastLabel = "") {
2061
2228
  if (m in IMPLIED)
2062
2229
  return [IMPLIED[m]];
2063
2230
  if (m in ALU_REG)
2064
2231
  return [ALU_REG[m] | REG8[ops[0].toUpperCase()]];
2065
2232
  if (m in ALU_IMM)
2066
- return [ALU_IMM[m], evalExpr(ops[0], symbols) & 255];
2233
+ return [ALU_IMM[m], evalExpr(ops[0], symbols, pc, lastLabel) & 255];
2067
2234
  if (m in ADDR16) {
2068
- const v = evalExpr(ops[0], symbols);
2235
+ const v = evalExpr(ops[0], symbols, pc, lastLabel);
2069
2236
  return [ADDR16[m], v & 255, v >> 8 & 255];
2070
2237
  }
2071
2238
  if (m === "MOV")
@@ -2073,7 +2240,7 @@ function encode(m, ops, symbols) {
2073
2240
  64 | REG8[ops[0].toUpperCase()] << 3 | REG8[ops[1].toUpperCase()]
2074
2241
  ];
2075
2242
  if (m === "MVI") {
2076
- const v = evalExpr(ops[1], symbols);
2243
+ const v = evalExpr(ops[1], symbols, pc, lastLabel);
2077
2244
  return [6 | REG8[ops[0].toUpperCase()] << 3, v & 255];
2078
2245
  }
2079
2246
  if (m === "INR")
@@ -2081,7 +2248,7 @@ function encode(m, ops, symbols) {
2081
2248
  if (m === "DCR")
2082
2249
  return [5 | REG8[ops[0].toUpperCase()] << 3];
2083
2250
  if (m === "LXI") {
2084
- const v = evalExpr(ops[1], symbols);
2251
+ const v = evalExpr(ops[1], symbols, pc, lastLabel);
2085
2252
  return [
2086
2253
  1 | REG_PAIR[ops[0].toUpperCase()] << 4,
2087
2254
  v & 255,
@@ -2103,31 +2270,31 @@ function encode(m, ops, symbols) {
2103
2270
  if (m === "STAX")
2104
2271
  return [2 | REG_PAIR[ops[0].toUpperCase()] << 4];
2105
2272
  if (m === "IN")
2106
- return [219, evalExpr(ops[0], symbols) & 255];
2273
+ return [219, evalExpr(ops[0], symbols, pc, lastLabel) & 255];
2107
2274
  if (m === "OUT")
2108
- return [211, evalExpr(ops[0], symbols) & 255];
2275
+ return [211, evalExpr(ops[0], symbols, pc, lastLabel) & 255];
2109
2276
  if (m === "RST") {
2110
- const n = evalExpr(ops[0], symbols);
2277
+ const n = evalExpr(ops[0], symbols, pc, lastLabel);
2111
2278
  return [199 | n << 3];
2112
2279
  }
2113
2280
  throw new Error(`cannot encode: ${m} ${ops.join(", ")}`);
2114
2281
  }
2115
- function dbBytes(operands, symbols) {
2282
+ function dbBytes(operands, symbols, pc = 0, lastLabel = "") {
2116
2283
  const out = [];
2117
2284
  for (const op of operands) {
2118
2285
  if (op.startsWith('"') && op.endsWith('"') || op.startsWith("'") && op.endsWith("'")) {
2119
2286
  for (const ch of op.slice(1, -1))
2120
2287
  out.push(ch.charCodeAt(0));
2121
2288
  } else {
2122
- out.push(evalExpr(op, symbols) & 255);
2289
+ out.push(evalExpr(op, symbols, pc, lastLabel) & 255);
2123
2290
  }
2124
2291
  }
2125
2292
  return out;
2126
2293
  }
2127
- function dwBytes(operands, symbols) {
2294
+ function dwBytes(operands, symbols, pc = 0, lastLabel = "") {
2128
2295
  const out = [];
2129
2296
  for (const op of operands) {
2130
- const v = evalExpr(op, symbols) & 65535;
2297
+ const v = evalExpr(op, symbols, pc, lastLabel) & 65535;
2131
2298
  out.push(v & 255, v >> 8 & 255);
2132
2299
  }
2133
2300
  return out;
@@ -2140,15 +2307,15 @@ function parseDs(operands) {
2140
2307
  return { count: m[1], fill: m[2] };
2141
2308
  return { count: operands[0], fill: "0" };
2142
2309
  }
2143
- function dsBytes(operands, symbols) {
2310
+ function dsBytes(operands, symbols, pc = 0, lastLabel = "") {
2144
2311
  const { count, fill } = parseDs(operands);
2145
- const n = evalExpr(count, symbols);
2146
- const f = evalExpr(fill, symbols) & 255;
2312
+ const n = evalExpr(count, symbols, pc, lastLabel);
2313
+ const f = evalExpr(fill, symbols, pc, lastLabel) & 255;
2147
2314
  return new Array(n).fill(f);
2148
2315
  }
2149
- function countDs(operands, symbols) {
2316
+ function countDs(operands, symbols, pc = 0, lastLabel = "") {
2150
2317
  const { count } = parseDs(operands);
2151
- return evalExpr(count, symbols);
2318
+ return evalExpr(count, symbols, pc, lastLabel);
2152
2319
  }
2153
2320
  function countDb(operands) {
2154
2321
  let n = 0;
@@ -2161,22 +2328,30 @@ function countDb(operands) {
2161
2328
  return n;
2162
2329
  }
2163
2330
  function asm(source) {
2164
- const lines = source.split(`
2165
- `);
2331
+ const pp = preprocess(source);
2166
2332
  const symbols = new Map;
2167
2333
  let pc = 0;
2334
+ let lastLabel = "";
2168
2335
  let ended = false;
2169
- for (let idx = 0;idx < lines.length && !ended; idx++) {
2170
- const line = lines[idx];
2336
+ for (let idx = 0;idx < pp.length && !ended; idx++) {
2337
+ const { text: line, orig } = pp[idx];
2171
2338
  try {
2172
2339
  for (const stmt of splitStatements(line)) {
2173
2340
  const parts = parseLine(stmt);
2174
2341
  if (parts.label) {
2342
+ let labelName = parts.label;
2343
+ if (labelName.startsWith("@") || labelName.startsWith(".")) {
2344
+ if (!lastLabel)
2345
+ throw new Error(`local label without preceding normal label: ${labelName}`);
2346
+ labelName = lastLabel + labelName;
2347
+ } else if (!parts.isEqu) {
2348
+ lastLabel = parts.label;
2349
+ }
2175
2350
  if (parts.isEqu) {
2176
- symbols.set(parts.label.toUpperCase(), evalExpr(parts.operands[0], symbols));
2351
+ symbols.set(labelName.toUpperCase(), evalExpr(parts.operands[0], symbols, pc, lastLabel));
2177
2352
  continue;
2178
2353
  }
2179
- symbols.set(parts.label.toUpperCase(), pc);
2354
+ symbols.set(labelName.toUpperCase(), pc);
2180
2355
  }
2181
2356
  if (!parts.mnemonic)
2182
2357
  continue;
@@ -2184,7 +2359,7 @@ function asm(source) {
2184
2359
  if (m === "EQU")
2185
2360
  continue;
2186
2361
  if (m === "ORG") {
2187
- pc = evalExpr(parts.operands[0], symbols);
2362
+ pc = evalExpr(parts.operands[0], symbols, pc, lastLabel);
2188
2363
  continue;
2189
2364
  }
2190
2365
  if (m === "SECTION")
@@ -2202,7 +2377,7 @@ function asm(source) {
2202
2377
  continue;
2203
2378
  }
2204
2379
  if (m === "DS") {
2205
- pc += countDs(parts.operands, symbols);
2380
+ pc += countDs(parts.operands, symbols, pc, lastLabel);
2206
2381
  continue;
2207
2382
  }
2208
2383
  pc += instrSize(m);
@@ -2210,29 +2385,34 @@ function asm(source) {
2210
2385
  } catch (e) {
2211
2386
  if (e instanceof AsmError)
2212
2387
  throw e;
2213
- throw new AsmError(e.message, idx + 1, line, firstNonSpaceCol(line));
2388
+ throw new AsmError(e.message, orig, line, firstNonSpaceCol(line));
2214
2389
  }
2215
2390
  }
2216
2391
  const sections = [];
2217
2392
  let current = null;
2218
2393
  const sectionNames = new Set;
2394
+ let lastLabel2 = "";
2219
2395
  let endedPass2 = false;
2220
- for (let idx = 0;idx < lines.length && !endedPass2; idx++) {
2221
- const line = lines[idx];
2396
+ for (let idx = 0;idx < pp.length && !endedPass2; idx++) {
2397
+ const { text: line, orig } = pp[idx];
2222
2398
  try {
2223
2399
  for (const stmt of splitStatements(line)) {
2224
2400
  const parts = parseLine(stmt);
2401
+ if (parts.label && !parts.label.startsWith("@") && !parts.label.startsWith(".") && !parts.isEqu) {
2402
+ lastLabel2 = parts.label;
2403
+ }
2225
2404
  if (parts.isEqu || !parts.mnemonic)
2226
2405
  continue;
2227
2406
  const m = parts.mnemonic.toUpperCase();
2228
2407
  if (m === "EQU")
2229
2408
  continue;
2409
+ const curPc = current ? current.start + current.data.length : 0;
2230
2410
  if (m === "ORG") {
2231
2411
  if (current && current.data.length) {
2232
2412
  current.end = current.start + current.data.length - 1;
2233
2413
  sections.push(current);
2234
2414
  }
2235
- const addr = evalExpr(parts.operands[0], symbols);
2415
+ const addr = evalExpr(parts.operands[0], symbols, curPc, lastLabel2);
2236
2416
  current = { start: addr, end: addr, data: [] };
2237
2417
  continue;
2238
2418
  }
@@ -2254,13 +2434,13 @@ function asm(source) {
2254
2434
  }
2255
2435
  if (!current)
2256
2436
  throw new Error("code before ORG");
2257
- const bytes = m === "DB" ? dbBytes(parts.operands, symbols) : m === "DW" ? dwBytes(parts.operands, symbols) : m === "DS" ? dsBytes(parts.operands, symbols) : encode(m, parts.operands, symbols);
2437
+ const bytes = m === "DB" ? dbBytes(parts.operands, symbols, curPc, lastLabel2) : m === "DW" ? dwBytes(parts.operands, symbols, curPc, lastLabel2) : m === "DS" ? dsBytes(parts.operands, symbols, curPc, lastLabel2) : encode(m, parts.operands, symbols, curPc, lastLabel2);
2258
2438
  current.data.push(...bytes);
2259
2439
  }
2260
2440
  } catch (e) {
2261
2441
  if (e instanceof AsmError)
2262
2442
  throw e;
2263
- throw new AsmError(e.message, idx + 1, line, firstNonSpaceCol(line));
2443
+ throw new AsmError(e.message, orig, line, firstNonSpaceCol(line));
2264
2444
  }
2265
2445
  }
2266
2446
  if (current && current.data.length) {
@@ -2277,7 +2457,7 @@ import { readFile, writeFile } from "fs/promises";
2277
2457
  // packages/rk86/package.json
2278
2458
  var package_default = {
2279
2459
  name: "rk86",
2280
- version: "2.0.18",
2460
+ version: "2.0.19",
2281
2461
  description: "\u042D\u043C\u0443\u043B\u044F\u0442\u043E\u0440 \u0420\u0430\u0434\u0438\u043E-86\u0420\u041A (Intel 8080) \u0434\u043B\u044F \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430",
2282
2462
  bin: {
2283
2463
  rk86: "rk86.js"
@@ -4180,7 +4360,6 @@ class TerminalUI {
4180
4360
  terminal = { put: () => {}, history: [] };
4181
4361
  i8080disasm;
4182
4362
  visualizer;
4183
- toggle_assembler;
4184
4363
  on_visualizer_hit;
4185
4364
  on_pause_changed;
4186
4365
  refreshDebugger;