rk86 2.0.17 → 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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/rk86.js +319 -90
package/README.md
CHANGED
|
@@ -62,13 +62,13 @@ bunx rk86 -l # список файлов встроенн
|
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
bunx rk86 --headless \
|
|
65
|
-
--input "KeyD,Digit0,Comma,KeyF,KeyF,Enter" \
|
|
65
|
+
--input "KeyD,KeyF,Digit8,Digit0,Digit0,Comma,KeyF,Digit8,KeyF,KeyF,Enter" \
|
|
66
66
|
--timeout 10 \
|
|
67
67
|
--screen out.txt
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
После выхода `out.txt` содержит приглашение `-->
|
|
71
|
-
дампа `
|
|
70
|
+
После выхода `out.txt` содержит приглашение `-->DF800,F8FF` и 16 строк шестнадцатеричного
|
|
71
|
+
дампа `F800..F8FF`.
|
|
72
72
|
|
|
73
73
|
### Пример 2. Запись HLT и запуск через команды `M` / `G`
|
|
74
74
|
|
package/package.json
CHANGED
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 &&
|
|
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
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
if (!label && rest) {
|
|
1866
|
-
const
|
|
1867
|
-
if (
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
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
|
|
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
|
|
1960
|
-
if (
|
|
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(`${
|
|
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
|
|
2128
|
+
return upper === "LOW" ? v & 255 : v >> 8 & 255;
|
|
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;
|
|
1969
2135
|
}
|
|
2136
|
+
const k = name.toUpperCase();
|
|
1970
2137
|
if (symbols.has(k))
|
|
1971
2138
|
return symbols.get(k);
|
|
1972
|
-
throw new Error(`unknown symbol: ${
|
|
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
|
|
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 <
|
|
2170
|
-
const line =
|
|
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(
|
|
2351
|
+
symbols.set(labelName.toUpperCase(), evalExpr(parts.operands[0], symbols, pc, lastLabel));
|
|
2177
2352
|
continue;
|
|
2178
2353
|
}
|
|
2179
|
-
symbols.set(
|
|
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,
|
|
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 <
|
|
2221
|
-
const line =
|
|
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,
|
|
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.
|
|
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"
|
|
@@ -3707,16 +3887,19 @@ class Runner {
|
|
|
3707
3887
|
init_sound(enabled) {
|
|
3708
3888
|
if (enabled && this.sound == null && this.sound_factory) {
|
|
3709
3889
|
this.sound = this.sound_factory();
|
|
3710
|
-
|
|
3890
|
+
this.machine.log("\u0437\u0432\u0443\u043A \u0432\u043A\u043B\u044E\u0447\u0435\u043D");
|
|
3711
3891
|
} else if (!enabled) {
|
|
3712
3892
|
this.sound = null;
|
|
3713
|
-
|
|
3893
|
+
this.machine.log("\u0437\u0432\u0443\u043A \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D");
|
|
3714
3894
|
}
|
|
3715
3895
|
}
|
|
3716
3896
|
execute(options = {}) {
|
|
3717
|
-
const { terminate_address, on_terminate, exit_on_halt,
|
|
3897
|
+
const { terminate_address, on_terminate, exit_on_halt, on_batch_complete, turbo } = options;
|
|
3718
3898
|
clearTimeout(this.execute_timer);
|
|
3719
|
-
|
|
3899
|
+
const bursts = turbo ? 100 : 1;
|
|
3900
|
+
for (let burst = 0;burst < bursts; burst++) {
|
|
3901
|
+
if (this.paused)
|
|
3902
|
+
break;
|
|
3720
3903
|
let batch_ticks = 0;
|
|
3721
3904
|
let batch_instructions = 0;
|
|
3722
3905
|
while (batch_ticks < this.TICK_PER_MS) {
|
|
@@ -3742,8 +3925,6 @@ class Runner {
|
|
|
3742
3925
|
this.machine.ui.on_visualizer_hit(this.machine.memory.read_raw(this.machine.cpu.pc));
|
|
3743
3926
|
}
|
|
3744
3927
|
batch_instructions += 1;
|
|
3745
|
-
if (armed?.value === false)
|
|
3746
|
-
continue;
|
|
3747
3928
|
if (terminate_address !== undefined && this.machine.cpu.pc === terminate_address) {
|
|
3748
3929
|
on_terminate?.();
|
|
3749
3930
|
return;
|
|
@@ -3758,8 +3939,10 @@ class Runner {
|
|
|
3758
3939
|
this.previous_batch_time = now;
|
|
3759
3940
|
this.instructions_per_millisecond = batch_instructions / elapsed;
|
|
3760
3941
|
this.ticks_per_millisecond = batch_ticks / elapsed;
|
|
3942
|
+
this.machine.screen.tick_cursor(this.total_ticks, this.FREQ * (this.machine.screen.cursor_rate / 1000));
|
|
3943
|
+
on_batch_complete?.();
|
|
3761
3944
|
}
|
|
3762
|
-
this.execute_timer = setTimeout(() => this.execute(options), 10);
|
|
3945
|
+
this.execute_timer = setTimeout(() => this.execute(options), turbo ? 0 : 10);
|
|
3763
3946
|
}
|
|
3764
3947
|
pause() {
|
|
3765
3948
|
this.paused = true;
|
|
@@ -3845,9 +4028,15 @@ class Screen {
|
|
|
3845
4028
|
start(renderer) {
|
|
3846
4029
|
this.renderer = renderer;
|
|
3847
4030
|
this.renderer.connect(this.machine);
|
|
3848
|
-
this.flip_cursor();
|
|
3849
4031
|
this.render_loop();
|
|
3850
4032
|
}
|
|
4033
|
+
last_flip_ticks = 0;
|
|
4034
|
+
tick_cursor(total_ticks, ticks_per_flip) {
|
|
4035
|
+
while (total_ticks - this.last_flip_ticks >= ticks_per_flip) {
|
|
4036
|
+
this.cursor_state = !this.cursor_state;
|
|
4037
|
+
this.last_flip_ticks += ticks_per_flip;
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
3851
4040
|
render_loop() {
|
|
3852
4041
|
if (this.ready)
|
|
3853
4042
|
this.renderer.update();
|
|
@@ -3862,7 +4051,7 @@ class Screen {
|
|
|
3862
4051
|
this.machine.ui.update_screen_geometry(this.width, this.height);
|
|
3863
4052
|
if (this.last_width === this.width && this.last_height === this.height)
|
|
3864
4053
|
return;
|
|
3865
|
-
|
|
4054
|
+
this.machine.log(`\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0440\u0430\u0437\u043C\u0435\u0440 \u044D\u043A\u0440\u0430\u043D\u0430: ${width} x ${height}`);
|
|
3866
4055
|
this.last_width = this.width;
|
|
3867
4056
|
this.last_height = this.height;
|
|
3868
4057
|
if (this.last_video_memory_base !== -1)
|
|
@@ -3874,7 +4063,7 @@ class Screen {
|
|
|
3874
4063
|
this.machine.ui.update_video_memory_address(this.video_memory_base);
|
|
3875
4064
|
if (this.last_video_memory_base === this.video_memory_base)
|
|
3876
4065
|
return;
|
|
3877
|
-
|
|
4066
|
+
this.machine.log(`\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u0432\u0438\u0434\u0435\u043E\u043F\u0430\u043C\u044F\u0442\u044C \u0441 \u0430\u0434\u0440\u0435\u0441\u0430`, `${hex16(this.video_memory_base)}`, `\u0440\u0430\u0437\u043C\u0435\u0440\u043E\u043C ${hex16(this.video_memory_size)}`);
|
|
3878
4067
|
this.last_video_memory_base = this.video_memory_base;
|
|
3879
4068
|
if (this.last_width !== -1)
|
|
3880
4069
|
this.ready = true;
|
|
@@ -3883,13 +4072,28 @@ class Screen {
|
|
|
3883
4072
|
this.cursor_x = x;
|
|
3884
4073
|
this.cursor_y = y;
|
|
3885
4074
|
}
|
|
3886
|
-
flip_cursor() {
|
|
3887
|
-
this.cursor_state = !this.cursor_state;
|
|
3888
|
-
setTimeout(() => this.flip_cursor(), this.cursor_rate);
|
|
3889
|
-
}
|
|
3890
4075
|
}
|
|
3891
4076
|
|
|
3892
4077
|
// src/lib/core/rk86_snapshot.ts
|
|
4078
|
+
function rk86_snapshot(machine, version) {
|
|
4079
|
+
const { screen, cpu, keyboard, memory } = machine;
|
|
4080
|
+
const h16 = (n) => "0x" + hex16(n);
|
|
4081
|
+
const snapshot = {
|
|
4082
|
+
id: "rk86",
|
|
4083
|
+
created: new Date().toISOString(),
|
|
4084
|
+
format: "1",
|
|
4085
|
+
emulator: "rk86.ru",
|
|
4086
|
+
version,
|
|
4087
|
+
start: h16(0),
|
|
4088
|
+
end: h16(65535),
|
|
4089
|
+
boot: { keyboard: [] },
|
|
4090
|
+
cpu: cpu.export(),
|
|
4091
|
+
keyboard: keyboard.export(),
|
|
4092
|
+
screen: screen.export(),
|
|
4093
|
+
memory: memory.export()
|
|
4094
|
+
};
|
|
4095
|
+
return JSON.stringify(snapshot, null, 4);
|
|
4096
|
+
}
|
|
3893
4097
|
function rk86_snapshot_restore(snapshot, machine, keys_injector) {
|
|
3894
4098
|
try {
|
|
3895
4099
|
const json = typeof snapshot === "string" ? JSON.parse(snapshot) : snapshot;
|
|
@@ -4156,7 +4360,6 @@ class TerminalUI {
|
|
|
4156
4360
|
terminal = { put: () => {}, history: [] };
|
|
4157
4361
|
i8080disasm;
|
|
4158
4362
|
visualizer;
|
|
4159
|
-
toggle_assembler;
|
|
4160
4363
|
on_visualizer_hit;
|
|
4161
4364
|
on_pause_changed;
|
|
4162
4365
|
refreshDebugger;
|
|
@@ -4344,15 +4547,19 @@ function printHelp() {
|
|
|
4344
4547
|
-m <\u0444\u0430\u0439\u043B> \u043C\u043E\u043D\u0438\u0442\u043E\u0440 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 mon32.bin)
|
|
4345
4548
|
-p \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u0431\u0435\u0437 \u0437\u0430\u043F\u0443\u0441\u043A\u0430
|
|
4346
4549
|
-g <\u0430\u0434\u0440\u0435\u0441> \u0430\u0434\u0440\u0435\u0441 \u0437\u0430\u043F\u0443\u0441\u043A\u0430 (\u043D\u0435\u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C \u0441 -p)
|
|
4550
|
+
-G <\u0430\u0434\u0440\u0435\u0441> \u0437\u0430\u043F\u0443\u0441\u043A \u0447\u0435\u0440\u0435\u0437 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 G \u043C\u043E\u043D\u0438\u0442\u043E\u0440\u0430 (\u0438\u043D\u044A\u0435\u043A\u0446\u0438\u044F \u043A\u043B\u0430\u0432\u0438\u0448)
|
|
4347
4551
|
--exit-halt \u0432\u044B\u0445\u043E\u0434 \u043F\u0440\u0438 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0438 HLT
|
|
4348
4552
|
--exit-address [\u0430\u0434\u0440\u0435\u0441] \u0432\u044B\u0445\u043E\u0434 \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0435 \u043D\u0430 \u0430\u0434\u0440\u0435\u0441 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: 0xFFFE)
|
|
4349
4553
|
--headless \u0431\u0435\u0437 \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F \u044D\u043A\u0440\u0430\u043D\u0430 (\u0434\u043B\u044F \u0430\u0432\u0442\u043E\u0442\u0435\u0441\u0442\u043E\u0432)
|
|
4554
|
+
--turbo \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0431\u0435\u0437 \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0438\u044F \u0441\u043A\u043E\u0440\u043E\u0441\u0442\u0438 (\u0434\u043B\u044F \u0430\u0432\u0442\u043E\u0442\u0435\u0441\u0442\u043E\u0432)
|
|
4350
4555
|
--timeout <\u0441\u0435\u043A> \u0432\u044B\u0445\u043E\u0434 \u043F\u043E \u0442\u0430\u0439\u043C\u0430\u0443\u0442\u0443
|
|
4351
4556
|
--memory <\u0444\u0430\u0439\u043B> \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043F\u0430\u043C\u044F\u0442\u044C \u0432 \u0444\u0430\u0439\u043B \u043F\u0440\u0438 \u0432\u044B\u0445\u043E\u0434\u0435
|
|
4352
4557
|
--memory-from <\u0430\u0434\u0440\u0435\u0441> \u043D\u0430\u0447\u0430\u043B\u043E \u043E\u0431\u043B\u0430\u0441\u0442\u0438 \u0434\u0430\u043C\u043F\u0430 \u043F\u0430\u043C\u044F\u0442\u0438 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: 0x0000)
|
|
4353
4558
|
--memory-to <\u0430\u0434\u0440\u0435\u0441> \u043A\u043E\u043D\u0435\u0446 \u043E\u0431\u043B\u0430\u0441\u0442\u0438 \u0434\u0430\u043C\u043F\u0430 \u043F\u0430\u043C\u044F\u0442\u0438 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: 0xFFFF)
|
|
4354
4559
|
--screen <\u0444\u0430\u0439\u043B> \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u044D\u043A\u0440\u0430\u043D 78x30 \u043A\u0430\u043A \u0442\u0435\u043A\u0441\u0442 \u043F\u0440\u0438 \u0432\u044B\u0445\u043E\u0434\u0435
|
|
4560
|
+
--snapshot <\u0444\u0430\u0439\u043B> \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0441\u043D\u0438\u043C\u043E\u043A \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F (JSON) \u043F\u0440\u0438 \u0432\u044B\u0445\u043E\u0434\u0435
|
|
4355
4561
|
--input <seq> \u0438\u043D\u044A\u0435\u043A\u0446\u0438\u044F \u043A\u043B\u0430\u0432\u0438\u0448 (\u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E): KeyA,Digit1,Enter,...
|
|
4562
|
+
\u0442\u043E\u043A\u0435\u043D *N \u0437\u0430\u0434\u0430\u0451\u0442 \u043F\u0430\u0443\u0437\u0443 N \u043C\u0441 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 *200)
|
|
4356
4563
|
|
|
4357
4564
|
\u041F\u0440\u0438\u043C\u0435\u0440\u044B:
|
|
4358
4565
|
bunx rk86 \u0437\u0430\u043F\u0443\u0441\u043A \u043C\u043E\u043D\u0438\u0442\u043E\u0440\u0430
|
|
@@ -4424,6 +4631,7 @@ async function main() {
|
|
|
4424
4631
|
const exitAddr = exitAddrValue !== undefined;
|
|
4425
4632
|
const monitorFile_ = arg(args, "-m");
|
|
4426
4633
|
const headless = flag(args, "--headless");
|
|
4634
|
+
const turbo = flag(args, "--turbo");
|
|
4427
4635
|
const timeoutSec = arg(args, "--timeout", undefined, /^\d+(\.\d+)?$/, parseFloat);
|
|
4428
4636
|
const memoryFile = arg(args, "--memory");
|
|
4429
4637
|
const addrRe = /^(0x)?[0-9a-fA-F]+$/i;
|
|
@@ -4431,14 +4639,23 @@ async function main() {
|
|
|
4431
4639
|
const memoryFrom = arg(args, "--memory-from", undefined, addrRe, parseAddr) ?? 0;
|
|
4432
4640
|
const memoryTo = arg(args, "--memory-to", undefined, addrRe, parseAddr) ?? 65535;
|
|
4433
4641
|
const screenFile = arg(args, "--screen");
|
|
4434
|
-
const
|
|
4642
|
+
const snapshotFile = arg(args, "--snapshot");
|
|
4643
|
+
const goViaMonitor = arg(args, "-G", undefined, addrRe, parseAddr);
|
|
4644
|
+
let inputSeq = arg(args, "--input");
|
|
4645
|
+
if (goViaMonitor !== undefined) {
|
|
4646
|
+
const hex2 = goViaMonitor.toString(16).toUpperCase();
|
|
4647
|
+
const keys = [...hex2].map((c) => c >= "0" && c <= "9" ? `Digit${c}` : `Key${c}`);
|
|
4648
|
+
const gSeq = ["KeyG", ...keys, "Enter"].join(",");
|
|
4649
|
+
inputSeq = inputSeq ? `${inputSeq},${gSeq}` : gSeq;
|
|
4650
|
+
}
|
|
4435
4651
|
const programFile = args[0];
|
|
4436
4652
|
const keyboard = new Keyboard;
|
|
4437
4653
|
const io = new IO;
|
|
4438
4654
|
const machineBuilder = {
|
|
4439
4655
|
font: rk86_font_image(),
|
|
4440
4656
|
keyboard,
|
|
4441
|
-
io
|
|
4657
|
+
io,
|
|
4658
|
+
log: (...args2) => console.log(...args2)
|
|
4442
4659
|
};
|
|
4443
4660
|
const machine = machineBuilder;
|
|
4444
4661
|
machine.ui = new TerminalUI;
|
|
@@ -4511,6 +4728,8 @@ async function main() {
|
|
|
4511
4728
|
await writeFile(screenFile, dumpScreen(machine));
|
|
4512
4729
|
if (memoryFile)
|
|
4513
4730
|
await writeFile(memoryFile, new Uint8Array(machine.memory.buf.slice(memoryFrom, memoryTo + 1)));
|
|
4731
|
+
if (snapshotFile)
|
|
4732
|
+
await writeFile(snapshotFile, rk86_snapshot(machine, package_default.version));
|
|
4514
4733
|
if (!headless)
|
|
4515
4734
|
process.stdout.write("\x1B[?25h");
|
|
4516
4735
|
if (message !== null && !headless) {
|
|
@@ -4524,39 +4743,49 @@ async function main() {
|
|
|
4524
4743
|
renderer.update();
|
|
4525
4744
|
setTimeout(() => doExit(`\u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430 \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0430 ${hex16(machine.cpu.pc)}`), headless ? 0 : 1000);
|
|
4526
4745
|
} : undefined;
|
|
4527
|
-
const armed = { value: entryPoint === undefined };
|
|
4528
|
-
machine.runner.execute({
|
|
4529
|
-
terminate_address: exitAddr ? exitAddrValue : undefined,
|
|
4530
|
-
exit_on_halt: exitOnHalt,
|
|
4531
|
-
on_terminate: onTerminate,
|
|
4532
|
-
armed
|
|
4533
|
-
});
|
|
4534
4746
|
const armDelayMs = 500;
|
|
4535
4747
|
if (entryPoint !== undefined && !loadOnly) {
|
|
4536
4748
|
setTimeout(() => {
|
|
4537
4749
|
machine.cpu.jump(entryPoint);
|
|
4538
|
-
armed.value = true;
|
|
4539
4750
|
}, armDelayMs);
|
|
4540
4751
|
}
|
|
4752
|
+
const tickEvents = [];
|
|
4541
4753
|
if (inputSeq) {
|
|
4542
4754
|
const keys = inputSeq.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4755
|
+
const TICKS_PER_MS = machine.runner.FREQ / 1000;
|
|
4543
4756
|
const settleMs = armDelayMs + 1000;
|
|
4544
4757
|
const keyDownMs = 50;
|
|
4545
4758
|
const keyGapMs = 50;
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
}
|
|
4557
|
-
|
|
4558
|
-
|
|
4759
|
+
let t = settleMs * TICKS_PER_MS;
|
|
4760
|
+
for (const token of keys) {
|
|
4761
|
+
if (token.startsWith("*")) {
|
|
4762
|
+
const delayMs = parseInt(token.slice(1), 10);
|
|
4763
|
+
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
4764
|
+
console.error(`\u043D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u0437\u0430\u0434\u0435\u0440\u0436\u043A\u0430 \u0432 --input: ${token}`);
|
|
4765
|
+
process.exit(1);
|
|
4766
|
+
}
|
|
4767
|
+
t += delayMs * TICKS_PER_MS;
|
|
4768
|
+
continue;
|
|
4769
|
+
}
|
|
4770
|
+
const code = token;
|
|
4771
|
+
tickEvents.push({ at_ticks: t, action: () => keyboard.onkeydown(code) });
|
|
4772
|
+
t += keyDownMs * TICKS_PER_MS;
|
|
4773
|
+
tickEvents.push({ at_ticks: t, action: () => keyboard.onkeyup(code) });
|
|
4774
|
+
t += keyGapMs * TICKS_PER_MS;
|
|
4775
|
+
}
|
|
4559
4776
|
}
|
|
4777
|
+
machine.runner.execute({
|
|
4778
|
+
terminate_address: exitAddr ? exitAddrValue : undefined,
|
|
4779
|
+
exit_on_halt: exitOnHalt,
|
|
4780
|
+
on_terminate: onTerminate,
|
|
4781
|
+
turbo,
|
|
4782
|
+
on_batch_complete: () => {
|
|
4783
|
+
const now = machine.runner.total_ticks;
|
|
4784
|
+
while (tickEvents.length > 0 && tickEvents[0].at_ticks <= now) {
|
|
4785
|
+
tickEvents.shift().action();
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
});
|
|
4560
4789
|
if (timeoutSec !== undefined) {
|
|
4561
4790
|
setTimeout(() => doExit(`\u0432\u044B\u0445\u043E\u0434 \u043F\u043E \u0442\u0430\u0439\u043C\u0430\u0443\u0442\u0443 ${timeoutSec}\u0441`), timeoutSec * 1000);
|
|
4562
4791
|
}
|